public inbox for [email protected]  
help / color / mirror / Atom feed
PATCH: Graphincal explain integrated in sql editor
10+ messages / 3 participants
[nested] [flat]

* PATCH: Graphincal explain integrated in sql editor
@ 2016-04-21 15:08 Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Sanket Mehta @ 2016-04-21 15:08 UTC (permalink / raw)
  To: pgadmin-hackers

Hi Team,

PFA the first patch for graphical explain integrated in sql editor.

Below are the few things which are different from previous patch which was
sent for stand alone graphical explain.

 -  Now user can select Explain/Explain Analyze with four optional
properties (Verbose, costs, timing and buffers)

 - Initially graph will be scale (according to only its width not height)
to fit to screen so no blank space will be there in case of very large
graph.

- Along with zoom in/out button, "zoom to original" button is also
provided, by clicking on which graph will be scale to its original size
(not same as initial one which is according to screen size).

Please do review this patch and let me know in case you have any comments.


Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/x-patch] integrated_graphical_explainV1.patch (661.5K, 3-integrated_graphical_explainV1.patch)
  download | inline diff:
diff --git a/libraries.txt b/libraries.txt
index cd01c24..c337881 100644
--- a/libraries.txt
+++ b/libraries.txt
@@ -10,14 +10,13 @@ Modernizr           2.6.2       MIT/BSD      http://modernizr.com/
 AlertifyJS          1.1.0       MIT          http://alertifyjs.com/
 CodeMirror          4.12        MIT          http://codemirror.net/
 aciTree             4.5.0-rc.7  MIT/GPL      http://acoderinsights.ro/en/aciTree-tree-view-with-jQuery
-contextMenu         2.1.0       MIT          https://github.com/swisnl/jQuery-contextMenu
 wcDocker            0f5690318c  MIT/GPL      https://github.com/WebCabin/wcDocker
 Require.js          2.1.18      BSD/MIT      http://requirejs.org/
 Underscore.js       1.8.3       MIT          http://underscorejs.org/
 Underscore.string   387ab72d49  MIT          http://epeli.github.io/underscore.string/
 Backform.js         5859b4f9db  MIT          https://github.com/AmiliaApp/backform
 Backbone            1.1.2       MIT          http://backbonejs.org
-font-Awesome        4.5         SIL OFL      http://fortawesome.github.io/Font-Awesome/
+font-Awesome        4.3         SIL OFL      http://fortawesome.github.io/Font-Awesome/
 font-mfizz          1.2         MIT          http://fizzed.com/oss/font-mfizz
 backgrid.js         0.3.5       MIT          http://backgridjs.com/
 backbone.undo       0.2         MIT          http://backbone.undojs.com/
@@ -27,3 +26,4 @@ backgrid-filter     01b2b21     MIT          https://github.com/wyuenho/backgrid
 backbone.paginator  2.0.3       MIT          http://github.com/backbone-paginator/backbone.paginator
 backgrid-paginator  03632df     MIT          https://github.com/wyuenho/backgrid-paginator
 backgrid-select-all 1a00053     MIT          https://github.com/wyuenho/backgrid-select-all
+Snap.svg	    0.4.1	APACHE	     http://snapsvg.io/
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index f461cbe..e2c10aa 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -9,21 +9,45 @@
 
 """A blueprint module providing utility functions for the application."""
 
-import datetime
-from flask import session, current_app
+from flask import url_for, render_template
 from pgadmin.utils import PgAdminModule
 import pgadmin.utils.driver as driver
+import config
 
 MODULE_NAME = 'misc'
 
+class MiscModule(PgAdminModule):
+
+    def get_own_javascripts(self):
+        scripts = [{
+                'name': 'pgadmin.misc.explain',
+                'path': url_for('misc.index') + 'explain/explain',
+                'preloaded': False
+                },{
+                'name': 'snap.svg',
+                'path': url_for(
+                    'misc.static', filename='explain/js/' + (
+                        'snap.svg' if  config.DEBUG else 'snap.svg-min'
+                        )),
+                'preloaded': False
+                 }]
+        return scripts
+
+    def get_own_stylesheets(self):
+        stylesheets = []
+        stylesheets.append(url_for('misc.static', filename='explain/css/explain.css'))
+        return stylesheets
+
 # Initialise the module
-blueprint = PgAdminModule(MODULE_NAME, __name__,
-                          url_prefix='')
+blueprint = MiscModule(MODULE_NAME, __name__, static_url_path="/static")
 
 ##########################################################################
 # A special URL used to "ping" the server
 ##########################################################################
 
[email protected]("/")
+def index():
+    return ''
 
 @blueprint.route("/ping", methods=('get', 'post'))
 def ping():
@@ -31,3 +55,16 @@ def ping():
     driver.ping()
 
     return "PING"
+
+# TODO:: This is a demo url, remove it later.
[email protected]("/explain")
+def demo():
+    return render_template('demo_explain.html')
+
[email protected]("/explain/explain.js")
+def explain_js():
+    return render_template("explain/js/explain.js")
+
[email protected]("/sample")
+def sample():
+    return render_template('sample.html')
diff --git a/web/pgadmin/misc/static/explain/img/ex_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bfe75d909f5092c4d3ba8978c75fbc57d7f606d
GIT binary patch
literal 574
zcmV-E0>S->P)<h;3K|Lk000e1NJLTq001%o001%w1^@s69zTe&00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10lP^=K~!jg?U_w#!!Qs=pNrR76nc`JAiEyfyPUu$F?5lg!9`tE
zrVdorjI6O#*B=N1Q4~GTk7uODImZ$7QhEcqbb{2T!+^AsNlnvqz}0v!OZCpVcg+u)
zSl03oH-ylcGy!)Fj09u=UN>$mMIX+&H|b<ajP!gzp{f<N2t38e1(}OYz(X)^Z9SDm
zaL$Pb&;cXx85twc3D+9}YYwWtX(n61tgLAZVhA&A0ZDox`m}f_o&;Lp=3^|TZAm4?
zB8D-uw2Hk&77xL~GD+H8Yh{L+-D~onRU64N$mC{z9Z`Z<4$%uyDn(tUuBBqiTE>@*
zne6>YDVVIT^|Y|u&2%+YKxQ4H!ZKN8+Uk0kwJKPjW&<(>@$PjAe4RCOnSn%Nr0(=P
zYi|fJ04V_hnL$cH0K3&%;sz_V=BgQD)cm$)2vvhs6@*_isdrBfc8kD{yg=7gktITF
z+PGFu2!0OeLWgu>5LFp3D9xourL!bQu%a?wd{rRqFIvi++{=Q!&>aaV%KTa{dS;2c
z$5o3IhEOTyT37x61pK30-JX4KbAS7Pk<5;R_SRus>jbGyCrEAj0!#X?PWurOy8r+H
M07*qoM6N<$f>VU-$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_aggregate.svg b/web/pgadmin/misc/static/explain/img/ex_aggregate.svg
new file mode 100644
index 0000000..198bf04
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_aggregate.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAABWVJREFU
+aIHtmU1PG0cYx3+z3vViMAYcTIINCUE0NBRqkElQIhSpbS5VpUg9VJV6zEfoMR8gp6pfoOf2A0Sq
+0kuVS0QEwYoRhCSQEt5Dg8H4hRd71zs92ItppZZdsKNG4i+t5F3PzD6/eZ55ZnYGznSmM53pQ5Jw
+WvD+gykZ36ylKRXFQnDvTtSxbeAQ5OFYQv68rNPc08Z81gTAA3hE6VIBRRHle3H4XFPEYTm1/Myj
+CFQkHiFQlFJdu44qoFlTWFhM0e1Jc/f2oGMY1UmhoA6FvImwJOmCZGnXJGdaqOWXq0IQ1BUuNagE
+vS46Ugr2ihYHRYsDs0hRQlD3EGrx8WY147wdpyAjsUHx469TcvLlWyKGxcXeMGObeXZN67BMk6bg
+W9hg39Xroa25jvbWevQmH0nDwpQSTRHsF9214yoOoTJWvL3tPEkW2DWtQ4+MhnSSM2uuYvzB44R8
+nRZMbikM918gAwTMIutLf/LDN87HieIW5N6dqIiFoPDqLaMhnWat1ETGsHiyecD5/gjxzRKwk/bu
+jA6K77+KitvhIpMzGwS9Hnw+jZ28O7tcg0AFZv/lOjdDOn611HFZUzKezBMecAcDcPf2oBg+Z5F6
+l6WpXiP1PkCgApN7sc5oWx0B2zOmZGK7QGfUPcxQq2RxYxfd4zriTw4CFZjM7Bo3WnUabc8YFhNb
+Bl3RDlcwn40Misy+QYP6nkGgApN6vsaNUB2BshE5o0h8O0/PkDuYywFYTe5zOeDOjlODQAUmObPG
+SKtOYznMsoYkvl3giguY4ZDk6fwWwyHHEQmcIP3+l+zUHB6IEE8Z5E0LTSnN1tdadWbjK45S88Ox
+hPzypvNZHaoMAhWYzmiExHaBg6JEFdDi9RALepl2CONWVQmto7LDbGVqjVhQx2+HmWkxncozeK3T
+dTZzoqqDQAVmcWqVWNCLX7VhJFMpg+EawNQEBCowr5+tMhT00liG2TOLTO8UuH79YlVhagYCFZi5
+Z6sMBr2H88yuabGYM/jui26q9Y1TUxCowMzGV2jzlRbbDapCl1/jl98XiIWq8x5Hy/hqql71MNCs
+MTGxXNXsVXOP2Om4L9ZJzrSItmhMPq1+Cq76PHJUNsSVoQ4W94r0+tUPZx6xZUP0DHUwnzXpqSEE
+1MgjNkRXtIM/dk2uNqrMPVutGQTUwCNHlyhzGYPe9wABVQY5umh8uWNwJaDx+hQQj8YTjifLqvWQ
+DXG+P8Jc1qC/WSutt04Icf/BlJzPaXzkNxzVr4pHbIjW/giz6QJXA6eDeDiWkEt5nW9vXXQ8858a
+xIZo+STCq3SB3iYv69MnhwB4kRJ0tzeynNxz/KV4KhAbItAXYTZt0Nvkdb2v9U/9NpaQk1sKoVAD
+eVPi8zird2IQG8J/NczzHYO+Ju3UEOPxhIxvCqLdQXKGxXbmgPZ6Z+P9RGstG8L3cZiZVIFPgzqp
+5yeDeDSekDt5eJMV/PQCOsPNKI115C2JYVjUObTw1Fum5zRB10bSbTOH8vs0AvUqoRYf/sY6MkDG
+hPY6heWFLT4P7vH1reO/31155CjE42SBnGGhCYX0pTaCusf9sYIQWFJSlJJs0WKrYGFYpf90xcNW
+Jk+405ltjkFsiIa+CE/eHZAzSjvxO4ZFLi1RFfPwiEFTSoZrR36risArQFVAK5dTlcqxhHoEVhHg
+9QjyhsVIzNluiqPBfhRiamOPbUNiVnXr4O/yegR72byrTbpjPWJDeHrDTG3ssVoUFKwaUlD2qnA3
+fI8FiW/CwPVLzKYNIs06F8rPa3X0BqUNvZW1NN2NzjvsWJBYCOITSwD4/qWMBIrlq1qKhSAWqWKD
+ZzrTmc70v9JfbO2FoTKhMLgAAAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_append.png b/web/pgadmin/misc/static/explain/img/ex_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..017a2068b735f13cb89a3bcb2832e5880d17df56
GIT binary patch
literal 1162
zcmV;51a<p~P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(LcQrnzUg(&?|IPgRl@0p)bM7=>}tvEZ_4YI+3|bM?Sj$mrrz?7)b5hj
z@2}zW1*hRn!RZ^W<6Fh+V#n(^x8+d2=WEC4a+aaNk2du1$nfm9@9MSh>9q3g#qH&>
z?c}iR<FN7S!|>?B>));G-mL1~tMBB$>*k;D>$d6On&{t|?&h=S+m`0ol<eWI<I<1f
z(2eQZsp{jL=-rs*){^1Oi|N^?<kgYn(~#oMjOf><@94Ab;k?JHb@1lE=hUR;)1&O&
zx!}r)-o=I9!-U<zg67eo-^hpM&!OhgqTIiL<j$Y(=D_CCqwCqW<jtPr%%0=Rp4+{D
z+PZy)sbo@(8Kui)sL*Ju)oiZXakAicwdHz}mC<Hv(aX&7yuR$dzwOS>^3l=q!NTst
z#P8G8^VQY#$jI=?$??j|@!8t--ro1u*Y)Ay_q@IAr>W+ss^`DI?yaxsud(UH#qY<+
z@3pq;x47%Lx$M-`^vB2WiI(>{00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0zpYcK~zY`?U3tJ5>Xh&?SzGeqJ$LLD=fGO%SB*ek((>YO)i2W3vOk#CSYi4
znRfSIZ+9mfuyZIJXZldjhj-?kd4K0Q&oeUeU#e~(MZ}3i&`phI^cK3U)oRDk*qyt&
zaWp=m*H5C!DTCo2!Xg@@(Kw2xO(xTQ^uTO3>(InQtyYhwW@dB-G{|N8&s`p=e<n^G
z0<#v2W%goPDar;m`yB0nd8dnU0~WD(JRXZWy+HYV3x2Q%f<YFXp-`9`j6@<<8Ch7g
z109S;oxp^{vG^dw8;L|H@Gk}eG_cV`k^ych7US{Aj}#QOtfZjT6pWn09q0KJ7I_Sc
zh!gMP^;&1a=J(qj;9yzj3b;8go`O_5oyp=qCa3T%U!+JRLvo5(EXPASzgj5bk-lP+
zO0n@=u9Sw%YN1djA(xBgOQn1U)(VwM6_42b_BpvF*6CW8Q^eI2nT;%D%hhV_+8T4v
zEISV?48yr0#q(+T{k3Ab2DQz)fOi2p8cn$56iaG~e0~Fpl}e)u^=7jv;1M>FxKwPp
z(r9dgKt_How%TaO#{-achU4Ux__UIuSXNThl@v8W5U#DUt7}W#B5ow&NzYaPMkJm-
z`+3#B5v1H~KNqnZ(M8-Adt7=qvQOWu;_p1vqZcA^BYthzl84euNfB}45NYSt?ruwJ
zclP#POWpm0H;2+;tKB}5j=J6Bw-Oe4cXIOnRO+0aefTJS`uyeVj?_BsblTFl^ZkS4
zzljW=<qD1clll(R-{b2g;$(6F001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@z
zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUh
cIxsdXFf}?bFv^!ztN;K207*qoM6N<$g1#AbUjP6A

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_append.svg b/web/pgadmin/misc/static/explain/img/ex_append.svg
new file mode 100644
index 0000000..4f2a827
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_append.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+RK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_and.png b/web/pgadmin/misc/static/explain/img/ex_bmp_and.png
new file mode 100644
index 0000000000000000000000000000000000000000..64d5869dcfcf9d94d031fa068cff93ea929180b2
GIT binary patch
literal 1006
zcmV<K0}=d*P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004)P)t-smD2kF
z003rd(R6flb#--ic6N7ncY1nyHi*G{dwYC*e13j@fPjF4f`WsCgM@^HhlhuVh=_}e
zi;azqj*gCGmC%omkC2d%k&%&&j=z$UlEIHQm6er`lHGcqshOFXoSdAVo}QbW#Gjv^
zrlzK+r>D86a;T`NPQK>5s&lKWtGldpyRCMut*u(Y=dP}<ys&q#udltad8@4GzOs6W
zx!ba`vSh{Rx3{;mwCZuo?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?Zd;v-NJ*w!S2S!
z#*5VM-^PaD$cK{E@5aaP;L3^0%F2}3@Z-y#<IJAq&7R`WjpWXs%+2x5&hq8Zq2|$|
z)6>(Y-ty(vljhW<)z#JK)upZ9^VHPy=+~y`*Qe^(w&~ia>DsB<+S=*ds_NXT>)W{8
z+}z#W-Rs`0?A^KE-rnrst?b~g?cclJ-uCR`ui@e0?BlTF;^Ob*zvboS@aDkq=fUUa
z=jrL`@$1Cu>gw?D@bU5S^78WY^Yiuf_4fAm`1ttw`T70*{r~^}C9&%N00001bW%=J
z06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0q;pfK~zY`?UY$l6G0S(b8}-Di3kcP
z+Oo)^h$4bwltoqvL?DD{Mjf_nBqD~O2?A~ymixDzo*9a*Gm})MC?7Zv{c`K8Gv{>Q
zvDq$ko~o9(s?QF(wL!N4k*52GJqwax@WJEtiUu~Rr}eShD?&VOcaK*pj!Vugb=sg#
zfY{%@e)DeKes;iqrqk&yCPD_D!r-C^Nk4<GWMRlx_z0E==`xVDE_fnFj<OJC>PwuJ
zQ#!=9lF84TBBa*NRjVOi9LP1IA^nW2-@@fKw*1L<;8opaGaip`h_l>+MlB1G6SG9S
z=+u$;BX}4UBk&Ro<O=>E`h+(O1ZE*><<f@Dv{HsEI<ou#?nSH``|ZN(SURV-%r>Ht
zNXoo1qP+&hYdgl><kJ{stMHBkrzooyKsJ^Ng_cqlS=#YAjp3zO3@bPinuH0(v@xPG
z-th~(T!n2MS(<=x#ngpg%P%#>ef9pot6A9mcrN5H3(-xi$?R{pAeQfQB&8_Is%c|H
zG5v{QDc(Jx{2HTgO)iJ4;r-tV>{PR?%CelW*cUn`^~2;*7z$c$rLkbz!Q>%$6)bF#
zgDMhW1^r<X!9XBkv6Uy4*H)f(3HbeC^EY-H@%KXSjQLiI5MN;~GdYo*S;9V_FI=R?
cF7zMiA0?@qIju>9W&i*H07*qoM6N<$f<qfLO8@`>

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_and.svg b/web/pgadmin/misc/static/explain/img/ex_bmp_and.svg
new file mode 100644
index 0000000..4498984
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_bmp_and.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+kmj4Fzs2o2mBbQUEAAAAAElFTkSuQmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png b/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png
new file mode 100644
index 0000000000000000000000000000000000000000..2657d8c39328bfc80751c60db9d3ab4341c0de35
GIT binary patch
literal 1106
zcmV-Y1g-mtP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s00000
z003rd(Nc{WHi*Gol_FY@Wn`8)38~@|tKwvp&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPGu5^~NHbTAWN5AP#zUE86=}y7vTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q^_*?AyJGyuIvu(eH!N?S#?pz`^c>
z)9|d;ZHv_Ik=5>!)$gv_amL5*l-Tf>+3~XAcCFUw%+2wj+wtexE9lxP&Cc@b+bZna
zD(>4V@7yZ!+$!?iD)ihcx!dfw=Y00vDy`r1`Q0h{-6_=6^RD3Y``#)1-YNdxDZbwA
zx$A$x-|oWT?z`@S?c-qY<6p($@9^Ybyzqqb<X`jTU&rI{$m8(y<zMvWU&`a~_2pmp
z<zM*aU(4k2-rn~3=3n~eU(Mz6{N`W&=3mk0^3&+^^6H1w>GRa-^z`e8*6H;2>xcO3
zhu7-$`s|1O?1ujAhT-q`mEcQQ00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94
zoEQKA0&7V`K~zY`?Ud_N(?Ar(NfQ+@uBZsUQR*A(3k7^uiA_xlwb7<XX$T2Wl46zw
z3Z<omf1TZggd}Y;GR`=D;C$H3?B=&;&b@cGr{_ffM12%iU&sC(Yu~pzN7RRBuO_}z
z9SHSg=<4;C)SHp}k3Ldg#wI3zy8lp*uid=+whJ=U+k5r~HGFAc;3+jWJaTW6`U{t^
zEElbP-|8I2KEHnd^>^&(-vmu3&<E7Y<#G+wL{Y4Rx+F<;;36;16PJH@5PE-~#z=W;
ziYWt;VNw#1SeBJN2=S2cA-lUa!Z3^o#8j#a5_H+w@gP!)Wi2G_L4sKv#G9fn%W}g-
z`eRt9$y)*BIl%I*K9?(GoOF@xZQI4ZUy^0CDl3(yC(CW(0vV5!j_A!z8h-}~mT*$6
za2%)l5JX@-7#$sd_le%vpcj!ymO#PfbULko3dd#C2p5DnE;2^GRe;K6G8zcaf)Jd=
zPSH>*C`D7%v}T}UXUKCdvcCQ&74!AQMsP2b2D)EWk&C8PTM^wqM7$}qY%Zrq%-GtR
zg(zuUST%!@YA%<95iYB%7Gn28&1ADxp!<=IEX&Il;!V+l5Vj&_Y-#D(Gq$jB=z@FE
z)OFo<X@_?I6h_E^1__=pLT79oBQrC<=I0lWMDj$zh?ucbsf$a&>(Y@0AyyV$hfa#N
zVHoX*8Jn9s_Kewxb3cRzb}`Mpu<oOWZ+aR(egNk4c?ck1K*3ExD4Jn2MsLj~le1g2
zh42s<1wlYavFJkrE~R9$WV`UZ0SrM9%pelTlE5>D9%WyOJ=2@Tu2_G^GagZ~6a9ZW
Y0Mz-{B6#`Cr~m)}07*qoM6N<$f){{JGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_heap.svg b/web/pgadmin/misc/static/explain/img/ex_bmp_heap.svg
new file mode 100644
index 0000000..0f061a0
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_bmp_heap.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_index.png b/web/pgadmin/misc/static/explain/img/ex_bmp_index.png
new file mode 100644
index 0000000000000000000000000000000000000000..23b9733b56792533e4db25ca9890a0a0140c6ff9
GIT binary patch
literal 1172
zcmV;F1Z(?=P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004@P)t-s000{R
z003rd(Nc{WHi*Gol_FY@Wn`8)45{LDla^$a&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPPu{MmZbjzYxL%irozv)iC=1#%sTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q|V#etORA+r5d~zJR>F?0eDg-NAx`
z(d~rM?%l(Kz`^c>)9|d;ZQjL&i`4Gl$A^*C?%>FXlGX37+HuCm@8HUbl-Tg$%!-%U
z@#4;m<IJA2;C8Ln>CDaX<<6kv(T?ZZE6vXG=Fy>}-SXtrk>=B+^4u!t)TFuF?6>E9
zt>5$1)bp<3^XA!==+~zI-YMzWr@r3px$A%D+?T-L?&;d7!r<=c-k9s!x9Z%g?c-qY
z<6p($@9N#Gyzqqc<X`FGo9x}X$K&wG<M7Jk@a*8N%jEIi-uC(CU(Mz6?BlTi=3nmO
zz3t_((dY8+=Caf1^Y7)q^6H1w>GRa-^w#P0@aMtr>9p7C_3`P${_KYG?Z)Bn_g>Mo
zbpQYW0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_00N9jL_t(Y$L*BsQxZ`a
zhh26nvhB^vO1oLo-JmFv5|N5XBA|_nRxB8D7XcMR7F@v#0{+$QVK1_a?Bq1l^uhPT
z*_pHZ%x|7^ezOAuC-YBckU%rovwx?vFI&Y|#D{0EroRzA2=QcObo3?hX8iu6kHpu>
z>6stWH^k%XH}AeZ0vXY2wKs^dOT)uYiOI3?do#per1Wz++u7&Wi~K6S(tLjX{>v}T
z;kSB{)N>E0r_<>=v>S~^8`><(wn0K(oX0MI??T9f0}>%=uh*M~Mn0c!0Gmiet6d28
z5R)PM`wD~wHX4nVRZ@0$Wk@2yLNyu+bs<U@5fNNE7R_?GyeA<;8Z@WzTMFbFpyAi&
z{3=ViitBI*+1Zh$RI5#B7K_EbE|=Tb1ze}Y#UZ!0Nc6mdd9k!$QS|wISsB6+XdX;V
zOuhR=Zf=sx+~8h})31g?q2dvUJcUEVlnj&w#N$ape-{qnT{4-vU{TAaQZ>biox#sZ
z$i~K>oS5uhcm(RXT&@m##cZ|)wNxxtQMr8q$pr#|oL~`iI2^P}$JW;+qy$+HkJ#<B
zO3K=rgfNiu%+AIjVz=9ZDji#0?I5jEsnmj63|UlYnl7kxY-Q!x9a~xwQW&NMl}LoH
zKp`0P7y91*DTMI1AI2EY!p2zyEfzD?w_{TXnV<i;jEAu>1GLR%4T9fnRv_|@#p7Km
zwAE@sh{;5$bc{nfE(~(vEeaGxB~?1MxOnW2@ran>r>FuX-EMcX-|cfhzPUN{^8+Rv
z=Ja_Bx6xp5_UjnAz2I^!Y?C5FnM@`(xD9edkrH>g;)f}e$!P3B6fSzyF}>u%TO^%M
mr}D&xdVb?7Cw4Ob-~0w&gyX$CR{j?N0000<MNUMnLSTYde~VB6

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_index.svg b/web/pgadmin/misc/static/explain/img/ex_bmp_index.svg
new file mode 100644
index 0000000..4b52956
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_bmp_index.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_or.png b/web/pgadmin/misc/static/explain/img/ex_bmp_or.png
new file mode 100644
index 0000000000000000000000000000000000000000..c22fc31eee32e53abf634278f9d57751181f15c0
GIT binary patch
literal 685
zcmV;e0#f~nP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700030P)t-s00000
z003B6SY~U{Hi*GwmC%ijzrl|-kCNSbo~fFenwy-&xu<eYzUI5CbGxi`yRCLw!RNfN
zcfGNBtE}k0vU-WR+hoP)v$X2Ey1H@8?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?cKtI
zz`^c|)b8KLhTq7ClGX3V$M4|EiImvz<IA4o%%0@Up5oAr<j$YW&GF67^3l=J<<Oz#
z(W0i_^5xc(=G3I;)upZ9^VHPy=+~y`*Qe^(w&~ia>DsC3+^Xu_s_Wah>)x#F-MQ@G
zt?b~g?cclJ-uCR`uk7Qn@8rMa<mB+?!0_k6@$1Cw?CkIF@AUNa|NsAqxAFG?0004W
zQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkPM@d9MR7l6|)NN0KU=#*mj$o+0
zXGv;SRwxw`6%>^SK@x>8z5V~+lCW40^I>$hY<=+l#CCS=?A!_rGtXZp&xOfP4=T~1
zLLuDg&VhK#Q3h9{B+&*8S6f~eBpML~p(b&^vn6@U$0T2m#b{8Z5cd4&_~MEE7O~-9
zf*=_4G_tn|`*$&UE0u;Z3AUi@Ws_kpcNvpsxCSJ7EW-w!ByJ(e*z+DnG*V#06sAdo
z57R(x899zKpx?3pi_}}3HCVOj1h#=r;0$csmirZ0vT%(JY|HXz-k5KiT_1Ogc>-+%
z*I2g=Ed#gZrj<t0Z!rv`Kl8@=x~{vp_eDR1riLU<*hLa;LR4I1uBNJPc4P0=>MO1>
z^3%t=s-pBVfBn$JPrOoxdMEQgMkXTi54I4blS&e|kfbNeaxb$nGU<)Y^N;cg@O4qi
TF88uo00000NkvXXu0mjfLbGwY

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_or.svg b/web/pgadmin/misc/static/explain/img/ex_bmp_or.svg
new file mode 100644
index 0000000..1588cc1
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_bmp_or.svg
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAAA75JREFU
+aIHtmF1om2UUx3/v8tWmDUuZjbVRET+QbZJ0VIQNb/wIu5mzUJl4s4s4vRFBVkYZgojidMMMQfTC
+jAkiWCeBigMZarwRxerSJoyOOYbdNHHt0rUxadp8NI8XabNuS/M+z1y2t/D+rvIenn/OObw5f84T
+MDExMTG5DWiyB/d8NCYS2cx1cZ9rPUde6ZH+nkYcPHFWREZT18X7t3QzuP2hhjmsskkS2Qy7+/yk
+8mVaLRotFo3722wc/DJ2AyXXJzKa4rVdW0jmyzi0at1ep1Uqh3QjAOeyZc5li7RbNJxWrZbsZpLM
+lzmTKQLgssuXp9TIAy4rrRZqb8TrVJJLsfydK9+IDNKV+Fzr+Ww4Xjc+0kAnhBArnzVt9df4tOci
+HxwbrRtvlAMUGlk3rvFxsIdcbhG7XcPhsNDhtvJe6OyqmmubWI6t1sz2TIjBFw9TWkhfKdDaQXwo
+xAG9+iT7AGB2tsSlS0W2be1gZqZEPr8ooZklHA4TDodJJpO650sLaRKjI9xxTx/x2K+UC2ldDSg2
+ciPMzc0RjUaJRqNMT0/rC4TgyZ0HSI5/yFPPviudp+mNKFMR/PD1frybXuX74UFpmZLtuN02rNZ1
+/PzLDA6HBafTolynbkGODfj9j5KeiCAqAqt9g5xONsHGh9v55OhE3fjNotvXT3xoX904Or4l3cjp
+MzleDt6n5FqqpBIR/M8fopifqsVsLZ0kvtL/iSm71smTcbZt7SAWS0i5lirF/BTx+O94HtxFfGzk
+qqYaoTzsA3uf4JvjU7y053HlImUJ9B/iwliIwHPvS2uUGwkd/pFndngIH/lJVSqFAL47NsC9PQOc
++GLvUkQfZdfq7fU31bXsLZ34eh5j8o+haoG2Nepa9QZ7TbrWI33vrNi1BLaWTk4Nv66rNZxrlRbS
+LGQv0LXxBeYz5ynMTUrpDOhaAlGpVD9W5AYdjOhaFaHUwDLGcy2nByEEqVOfV59bO6V0hnOteoO9
+Jl1r8863KeauDLjN6WH8+Bu62qbfEFUp5iaZz5yn27+b+dkJis1yrVvC0lVfLFakJYZr5Nr/K2pW
+rIPhboj2tjsB+Dv2KQAO111Suqa7ltvtJhgMAuD1ehue7fb11x3s2+5amqZpQggRCASuiq12PpWI
+sGnHWxSy/9TmxN7Wxelv39Str+m71srCGzWxTOHfFPnpP7m7N0j+8gSF3EWp2m7JrqUtoZoLkL1X
+GXDXUl+zAAPuWo72LhDw129Hq8+uLimd4XateoMt41omJiYmJib/h/8AXT+3etbbSJIAAAAASUVO
+RK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png b/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..e99f57478842f61cf0582433b659d37f0c532d5c
GIT binary patch
literal 334
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_@3?!2S6O@1yXMj(LE06{PXIHn!e64$cEXI-`
zzhDN3XE)M-9L@rd$YLPv0mg18v+aP48c!F;5RLO&CtC9zP~dS+JaMn}cL58V&ewnH
znS#1o7niJ>^>*TX*`QzgMdD7POzPaTo+w<9uwpLL)1F%W#!IqdhoMSxhwp~0@cf02
zhkMjmROfZc`EZ)w`S;|i<*e@Qr8gQ^@9R11|6Rc8|AQ9IRLjSmUOFeWtM)%%ZozPh
zRpl$&!*g?h?ocgpjVMV;EJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0
rRpn4L<mRVjrd2{T7+8WefK*!<m_an0njX3asDZ)L)z4*}Q$iB}@40B!

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.svg b/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.svg
new file mode 100644
index 0000000..622ca78
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAAARtJREFU
+aIHtmeEKwyAMhLOxNxvb+7+R+yUU0dYkp7mOfNAfhal3Rk3qRJIkSe7EI3j80ryb9bAZqah1sRo5
+MqUx2kjFbSjayIyBlq7m3UYswiuhEfEIF1HoezoHainNY+b9+ap+740Iaqm484m2wbI1fujbNLma
+RloTWw+SF7Cv0KPcYyQ6ByU7MB/bTMvDdQSjEyISVWSYIiLi+D5ZmUfQwIrGaCOVrmbmPTKiO6Fs
+e0TkPPJDvWxGtmz21fRMMOlTsS2zwy7U0CCP31BTrB9WRdveOhjsPuqi72l9qOWAjFaokZGQWdw3
+Kjs2KKJGo0yIS/ZXdB7QmKK+jW8xFYwi9yrjT6PHFBFX5YC8aVwF02RP4/47IkmSP+cHUnUv9QVV
+5FQAAAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_cte_scan.png b/web/pgadmin/misc/static/explain/img/ex_cte_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e1d779372f7fd212d2096e7701b0f1af05a5297
GIT binary patch
literal 1955
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_cte_scan.svg b/web/pgadmin/misc/static/explain/img/ex_cte_scan.svg
new file mode 100644
index 0000000..6eb0efb
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_cte_scan.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_delete.png b/web/pgadmin/misc/static/explain/img/ex_delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca051cd5d01ee2f3ac17779b362999d82a9285b5
GIT binary patch
literal 1129
zcmV-v1eW`WP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003^P)t-sKLD^>
zTU+Qs2lh=8Hi*I5b0^MyED5RN(19;<mZ6f8lEIHQ*N;guwdHlD$Lf$-LA>b8qFCae
zX-dH9$EtPXqi$Wq>SM<0Ysctl$n0**>~+oT+Pi++y?@)iiQK+`+`oW((eK>Bf`rlT
z-NS=~)9~KJgx<x4-o}QE)b8KMhu_GEk=5?t$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc
z&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+mlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#
zr{>z0>DZ^~*{J8-m+9K6>e;sG+p6o@x9HxP>fNgB+_>rBn(N-I?A^NS->vN4y6oVs
z>f@a4-@EMLuI=Ev>g1j5;;-%DyzJw!?d7rW<i73ZvhL=y@8!Vo=D_dhv+wD(@aV$u
z>B8{rw(;x4^6ka*?Z)%)$Yy(c-~a#s0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkV
znw%H_00Om1L_t(Y$L*8bdl^v_#f8bG-PLL%nhMc~q#`mQ2qBD7)FomPgc62MQMdg6
zk3DnFFeXNthkjrCko~Zp{O0Vn_M*|e%s-g7lsHRO4WY~igJFl-+ccRDsDn>t^C@+@
zx4(Z*ogcZ~Uny0@u)e--pf-`3sE>NR-c0Qve4jen-Zs0bzx2v0yz^33L*QXJ96n67
z-|vrMEC|8~Mm*0Cfcyr)IFAhP?`wTY`?t5ZJrIhyTpRdgGF}BjOnw=S3Z#aS8bOKz
z2)SI2hh{Q9MZQla-3-V`zlMK|wO1@6q@DYg<e?sq#VQh#$r!5>kiFpjXD)tV7tH|-
zs+Ce#@?tf9p@%c(J&3b~b$3yv?^<i0Wd?NOY>az?FosdP&5vMEuU5Oz<v6Z0{jh=o
zomQ*WZOAT~qKnHCa6!L6FYiCcNQh7@l3<f{sKzcYIN<z#Z+<Q&%wTg<1P9CGU@I5F
zAO{7nm(GGl$rxFfd0BMP$!D`Im_IsL#iN4e&4xICNNma=3fVzdXx0akb}J=xi%ub*
z??5Y^PBlQ;M@VO0uV|$Gl`d%HB7Jh~V&PGT0ag%FkH=9lh;qT8zW{lfrqhr*b~zs-
z5-3Jml9U9SrB6;$kZ?Mkj5>n$=YC2mx@aL6sYC-BEW?yl5z?^_b4}Y?mYDns#}ztj
z_&TfPMvVl^-a$DU3E!wMhWB5?9atqzKrRj$h&b%_kUD~kp+H#Yp(6@myWO?~!I3Hu
z=Fs5?L&$Ek1=NvFHk)oKyp$u6@HJeA0)gu%65GN}d$-s()mK`n1iAvzJBEpt;U*Lc
zp;rPv-<A4eq!>gQ2X<i-UHB{(%b7ZoaEZkr8pXlNh({e^(G{YSM(iRIu-Pm=I9ra~
vF;TQX(RmkF9*^hhiNtoF^RIlF|7m^$H0#Fus?kFU00000NkvXXu0mjfhwW63

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_delete.svg b/web/pgadmin/misc/static/explain/img/ex_delete.svg
new file mode 100644
index 0000000..2b4f372
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_delete.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+Lf4Lm1DRiPrg77MAAAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png b/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..acba49c4fbfd9b871444c9e8d528deda3a37dda4
GIT binary patch
literal 1607
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
FV1m**ONRge

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_foreign_scan.svg b/web/pgadmin/misc/static/explain/img/ex_foreign_scan.svg
new file mode 100644
index 0000000..40f1a59
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_foreign_scan.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+QmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_gather_motion.png b/web/pgadmin/misc/static/explain/img/ex_gather_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_gather_motion.svg b/web/pgadmin/misc/static/explain/img/ex_gather_motion.svg
new file mode 100644
index 0000000..6d18a73
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_gather_motion.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAAACBJREFU
+aIHtwQEBAAAAgiD/r25IQAEAAAAAAAAAAMCjASdCAAFkjjE+AAAAAElFTkSuQmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_group.png b/web/pgadmin/misc/static/explain/img/ex_group.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d5de31bc129a9abcf4a73e19a48739139f66271
GIT binary patch
literal 1228
zcmV;-1T*`IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=(EA5f2CVejTwfiWOA0Fgt11U#9qOVHuUhw@a(tm>9z9h#qH&>
z?c}iR<FN7S!|>?B?BK2I-mL1~tMBB$bEG!w=AY@{n&{q`?&h=R*_GtekmAse>DsC4
z<DBHxlHtsY>Di~^&Wz~SrlQAS@94Ab;k>5IWyh*@@aDkh)THLqqwL+ecBwj^wTIx!
ziQdJ9-ou35!Gh+|q2I`d<<Fq3)@|IrfaJ}di@s3r=D_ROwzcJZ<IJAp%bvUMgW9`&
zd$dE9%UpG!OlO!lj)kjcYthWj@w~q5zQ66y&+^gH^1{OI!^Q8@)brKV^vKBY$;t7`
z%kkOT_1@n1*Vpyo;rG0~?5C;bsH*3`zwW@o?yaxsud(UH#qY<+@3pq;x47%Lx$Mi#
z@y^cjz`^d+)bz*4@RF6$yGLoX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0)t6JK~zY`?UZR(+CUVB(*d`NQfXa6^~z!}1Oy^sF^V-vQ6PniB6gWpu~n<K
z*4lmj>zxTpNrZFE={Y_9;C#4z;o;8n<_!#Ueg5^if#~TvJ0ZwowOV^nZ(o1^02;Vy
zu?(WYUYl(Q4Gr7vw@^pKIx;e1MSTv3<2Jf8Iy!1WJp{L*;juA`9sTu|@d=i7GIzB*
zcbx`ea%zfYVA|z!O*g08?e>7jJHz>4miG$*&>@5fk}cqxo11erraKf0N5CtJA`kxg
zXjIZ8>{QT6K3YgD<|aru>;+DY$NeBgqlu&uVVO20Mv+K3EONld<MRSY3yFkmKo;vr
z%VLRIaMZ#t5Q{)kmQw~~SwrptJzZxz;S5eXn_Jw|;E6=MKJf88zXAbCN~R#CDCt$u
z5zCqTb(dHyv_vdKafVpTQ;S3*D}%DOp38&oS{ZE#LxT<JgH2|$1qIUUxtyxol6lbB
z@+Vln5v*J&tfe8J%N0vHeP`xj-Nh5}&d_Jg`!|S1QnN@Ci&a%El?{n)noX>UfRE$&
zSy&MSDF7*1R><$?^J*E&rLBr_%XS^%;`MqgXclXlMUhx+RjbvC5!ul?)~H8Yuxhbb
zEkWfnGVy48hc@|gu%#mXatCCbFywBqAxIKQNmeuqm0E03i*lu6bQyolvg{M)DW<3E
zG`wPq%cPn3+4J4qJzU%0fAJC@93H(o#>acL+6g{6I5>Tc4doBgcOSnwJ9~SM-(6h1
z|A0S!`uyc9{&xO-zlMMOJUluzB3ffiAkFe$9ch*S>MpJF|I(7_Iy;r$L}JbIn@VhR
z$7Es@G5cI-#kS2oefq~wwp=~2>+`>z-(ue+J2o$;LI3~&C3HntbYx+4WjbSWWnpw>
z05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppn
qF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTX@`Go)g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_group.svg b/web/pgadmin/misc/static/explain/img/ex_group.svg
new file mode 100644
index 0000000..9fccb4e
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_group.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash.png b/web/pgadmin/misc/static/explain/img/ex_hash.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f35c76538c3a41920a7b93b94bdf97443df7aa7
GIT binary patch
literal 1169
zcmV;C1aA9@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001n
zsbo@(8Fi+|Hi*HY#9nXB?oYw#V#exs&h4h&@<F}n38~^Nv*by<=84npY=q5bYthWj
z@xa0E#>el?&hp;g_M^vPyuIzKtmw0}>eSTprp#qVzv+I_?y1pfi`DRv*YI-8?0nAc
zpxg0|)b5kj@2}wVOTX!?)@^0T>qEQdP`>AE$LX%xagvqMa+aa8;C8`}HuUky@a(s=
z<azV%$M5K~?&h-b>%{Eiu<YTk?BK2N=fUgdp10_I@9DMZ-I(RolH}8n>fNlm?Sban
zmF(iL<I#@k+^W3rgyPSQ>Ds94;H~fF!0zF_pG#z^7Z=B>b%2f~b#^4RQ$FKkW936b
z-9bUQ>wu4ZIPc`Y=+~#`)}`#;yM|jA(>_1o$cW#@hUV0yu-$XX5E0BgJmJiW-o%9F
z(xSc&4#hh=;K_*H!-U<zg67eo%w1jR)~4*;x#rQL<<Oz)+qdxO!sgSY<j$b#*|yug
zf7-fz<IJAizk%e<p5x1&-i$@K00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0!T?jK~zY`?UU<U5<wWn@pV8EQt5^jN;fSkZ!Z^txZea>#3ciXMG^ue5lb*`
zBLBL(BnC@M_{lzaKFl*S`~03W=gh1~bf??Z7>o&i8z-E`2MDn+LnITE(kU`Ph=my<
zli!=3;UOHsGB<N!4<_g`tKc-t-v&Vskw|g>!NW(76$6NJ2r~EN>D;qx<f=ddDj?lC
zzfvATB(nK=DOmtDJSQ(+N?*MuQ;UmBZ^#ftCYQ_Kk_phjJ0f{M{b7O3OfD}k^&$OP
zSQlBxWkuL!wbR&Yce&O%`5J+BAl-Rq6m~(aPN&nN7W801!-mmlLX-KU#frjNP-nB*
zbf^P6IMC>HI?ZTyx!j(ABM$Jw28;N7n`m*n{ee-CEij_l=W|)m<M#(C9G=C&reF~^
z#ik`>#q9t^g?o`5n$u2q89~HPuuHQD{TVq$evdr}9gW5IqV%<k!Qp^7d|{C&ZS{Bp
z2@Eq#a=+Jwj-`OFxEFRI^_BMcgKP@sAf3+iBOq2Pl`555t<kJ}Ti5ID_PBQove_IR
z2<Gz)Cew$9g+65OuP&+V_amCh=Zi_q93GcW`jBWYcl6^=7L(b`W$A2&nV)Qm)AC8>
zY=p)*2{p#QmtU5UXB!g~#e{J`Q!Sk0S*=#954-)4+s{4^DqlQEV)eLGs$i|rXr2#4
zu8J*k#g<F0Rt+y2&1QQT(pzk$)oRr6yvav+SrQb(rKlFlofH>!thd|kzAU`IGSb`9
zXpM~`UY7p|xp@{|V|TiBT>>)4pzcQhBc}iW03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfr*~9W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash.svg b/web/pgadmin/misc/static/explain/img/ex_hash.svg
new file mode 100644
index 0000000..0d248e2
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2a4e9369fa78c6fa19bf5c7814f8a586aed5565
GIT binary patch
literal 1571
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
VFfhuORjdF2002ovPDHLkV1kgxQ(pi8

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.svg b/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.svg
new file mode 100644
index 0000000..256f039
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..0051f996783873a4a27db4b971135e95cac8d744
GIT binary patch
literal 1452
zcmY+CSx}P)6on&<ilC9wDWY}j1EW==VG&s^MyMc>VE_dIWsf2%OOQ>VP^3a>#ezbi
zQ$WfpN-;oW2^b&=k^l*WB?JiB_w`TyKVY?Ov`?Km=bn3K?)P@ef&wq>vfO70gTZ$B
z`}qW$(($7$%uVxaUUo4IW>yn~4)ryiwS?^8pZAsxthkhpJ|kUEnO0vS%3>*s%xPsS
z2k<7yY&!WTdW3!iaT!ycQNDmlo}HT35l7XO3C;8jz~MlDOvDm~m7Oo7J-u?muxfr@
zpW4Z+8|Svr5=g76*J7xhadTK|9F-Zn1z?W=#EU?ha)Ykg7?VO|88jh>7B!m<YlhB+
z+ihIKOFme=tgB-K^{c?37$i!dE}kC8*Y^qZlX7DhJFTT%m|NeG{<NMuQHSGZ&NB!^
z;v%k3I!Km}R=5~+lUn8L%?)9nD0kB+*n|W|{W4?{z}f(LBJsvL7vc+yV)2Gr{Z1in
zFoNuUS%J~8tXEIz)ik||2I-d8IN-v9etFrj#(`wAO|24dG_VI%MS2w#P*Oqdl0sB3
z7yho1^%ArX0Ocg0m;^P88U<b>@6$+o03~@7;%^vuMm@JJ<aWoNJuAayg^%p>Gmnsy
zuG1*Xb4;BYgOU392vO(P&!Kp6@p=_SEyk&Y-2;lk%6vCay<jj&HE-0dOV0b0N7*Ii
z>@A6~WuE;;C#7g56s?$SP%?QaR1+n-c`~kT8uNVa*2|@o?lo)=KN~N}8`2cd*w{>1
zTTgj<YDFZyg1O@D{UE}YyxV#N4ySl{7?kr`5m_T5X@ui?`P^!3EZxhC>gh@L@R+)G
zO(PhQvf3nzE#>#mKw1u{SyOTb#j{N!#xvnegJKyEY1bgl8X%hmqzq8Kx;j$0LMUJl
z<qK%_8X-v|7+3Q~)m)Q^fP}#wDqs)hYxrZTbs`|9t4#~%jZRFLv+$sL4Uo=)sui7h
z#-L;~yHiyh($Tl5Qd2dzWCRC>z`VsQjOo~s>UR}uO21G3b~5>_sl;f1pL3yp6Y(*2
zTg|QPYVFdf4n2i1ryt-~R&&EyEH~GIaAZa|e{p<ldYm><(Ym$Sy#1kbWFdS!sl4#8
zg+-&46R)?p=v<IrxKAkB`h2<TL4-|Z@SI~R;xX*W)A-68l_8JKw;U~tw3<);5q%L&
zO+WVhmV94&T?E^GFg?51+bpQUzhTmQhVhm~3Z0o+9h<ZCwR#@S#fJ;<<Zb)oT##J|
z)~QSg4M#(dpu=3@PP3E!chc@*v$C?VU<{A|;Ea$C)Mvphc3Q&ID5^bSMg}7&YYEw!
z+G=rs{cXF{PQvQnc;JJIsEn$r4gNJx`JpL<IafAY`AhBA`O0bGjYCz{Rlhy-wHk~D
ztSrl;%8Mf2s{-cqz(t1-!B^mBiO9Ppot??{A(!2P0&-5fl5P>dx>DZULg*TK_Nrt5
z-2iOB@sKY=Jc0s3a*{6G2s_h?5P1!wrHzP^j*5=&P&pEke^pz|yOz9TpDUboGQjBx
zMo!1otB@1eyV;Rc&(=*V+v==K^@N)awupf0xSm8APIR0a))g^~LD(V`iz%%B9lV<9
zNW9qx+^!S-`8sx=M=9LFn)pSWRENVZslD!B8wv}&N}w0~Q)Iq<_h*6eL%R>T<xbtB
zHVKX_vJbiD$PIhPc3|ksSjnS-lh?8Q_gnrdZaWp#?-x#cg*}W&rboNyF#et@q1F6p
z9T*>0alj*U0b~~`wx_Vx`_n~Y1o};*=weXmz#wPa!-WHU)K7MW;+k{KB^hrTOISzk
zQem*}7uv?l*3EgQg4t*IhG!(iW+b}Drze^M=7@AaA{-nMNQY2Iq`R||yR-9g<Y{*#
t(zVw9;QxR-DG5oKoc{o%+b7`Tw>#41KY&_u`GpC9`TGX?bfIFt{})E(L^%Ke

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.svg b/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.svg
new file mode 100644
index 0000000..730c6bd
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+0gAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png
new file mode 100644
index 0000000000000000000000000000000000000000..76c546a4dadd7fdae310dafcec2ecdef9649d4c5
GIT binary patch
literal 1377
zcmV-n1)lneP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006>P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZJydFPPaY
zjqWG6vC4&kzm|uj!nL^a^ZV4*@~N!lj*;EL!|c+nY?{_9i1sFWzH5(?-O$nT?A*cg
z?Z)orvhC!s?BcKR=fQ#6N$usa=Gm3y)|2GZkm}s3cd&fiz>e(VuH@B`<I<1f&W!2U
zr|sXy;?RxZ&5Pj5iRaa&`u6Vc<G$$Fr{~tC?A^KS-n!+`q2$h=>)E#4zJS}je%iZz
z<IA4fyMEfbeQRKz9{>OV0d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U00K`*
zL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U6fJ!G0)j%qd?aZR
z5fu}c7>rs-_lqPS2uKb<h(XgGuGEK33lp=Hw2Um1oV<df5)(cx$|@?VY7i|P>Kd9_
zOxilSdivP3!2P0PU|<N*L%<e9h$*WW0rh~yjKRJz!Ik=uLrmGoKm|mbnweWzTAAVs
zOBC;`SVOd!+t}LKo8n3rs9GE#S{$96U0jLO;s(*;?&0a>jav({i+z0kltEhjT>}Dx
zaQX!$jRpIKgaY-1nSww#aj8!^Gy>=})5xgkm{?PyQeSXrL>$l-)A)qMq-30aK}mc*
zDRE%Gq^6~3Wa89<QYfipK|(A$CpRx2w-)50Po)5&rLZW!xCEzPP;Ds%YatL~kR)gf
z39qtp6+&Uk08WDD=4PfK4Ju{j6{^H&F|VwwG&cpi3aCSkSS=P1TU3bB0n$=sQ*DYQ
zQd3)3UtibI*wozI)Y6L8FP3fY?MPZWI=i}idV2f%Crq3;Y4Vh*m|7qK8ivp@ZTgHE
zGiJ`3J!e`6kUM)8rXJI|^X4yD2)CuC6QpC&l-Udnb2@84dZu8w&2;gSrOTGXwbXV2
zb<9{X8N^;$3nC}CU}#yjdd=E(a4mJ+AT3RkK<xE(AaYU@hL*q$8#ir+YpL%6X=$Da
zVsEJjkrSISv~1nBJ$na9TF^p_D2MG#-nDxVY2M$pci;X4qy_TsgNF_wv=9}Q2M(cV
zAtJ$;mK->6poFAkiJr!&3jm~}-@X0I9(w=)03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfh#BXQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.svg b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.svg
new file mode 100644
index 0000000..acf1548
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+SUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..ba24ed16aeb0d93bd133902b0b2218e674f84528
GIT binary patch
literal 1402
zcmV-=1%>*FP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006^P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YN)&~
znAt0h?kBgg%7uZymWQOmwYc*0`_$F)sjTIWk=?<=?9#1ln$|0b_9lA1YmbuM(9!Yi
z+`;qh#_r~_?c}iR;;-=M!GYRI?d7rN*_GwiljPHo>fEY#uzcLWj_l&D<kgYm(vRZK
zjOo~??cc`Y(2e2Ei{Q$M=hdb9_U`ZFzUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyHkc~n00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~
z0%b`=K~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4
zBxw;56%&^jj9OrRk(88VkYoTMLO_ZS1f)P}iPb`eEpWepZ2==#y2F+Fuz8<}Sz1O`
zj!9lYQAwEzpB5EWRW)^p77h(fEo~+pT|IpRY+B&4uWD#$1kppl7I=sOZ7~Mw0g0J_
zePN0#^&zJ*6=OqH5N&2|VQFP;hAZ`<cwf~9qQ%12&fdWcSGqve;t0{=<m}?=Mx+*Z
zh!zh|FK-{*T994r>*ucm(h}en7!-`tFNhEWf)M}EFrc1rGZ2VCmcb2F!XklAGmDCj
ziH$QOD!+z=MaBbdF-u5HN>0J)7nH>3n;H-HOImtHW)@B@D20-0HYCJya`W;FaBD#>
z`cw-cT8fGbN=kA11=W@^uoeO#21$Y@knk$6P$d+m4B#YaVPS3t(x6&iQK?3Z7K^H?
zDho5PtAIMxiPd5Wu|<_A9Uv{$wl!u*BDHn(4Gr~;P0cMW&8=-%{bJSb(Sf9;v#YzO
zx3{l<!o*3FCQq3<4O0swK*JF_rq7r;bLOnsbLLL(1ajxh#?)grZ~lUXi{Q4@c7b#(
zo;rttVQyC~NY7LZx0x+jx@`FhxR$zZppKa<r-0b2>Oka_Rtznx*Q{N)9<HUn2c)HW
zGKjsQ9z;%V#?TV9ant54a4ikJAT2GEK<upzAaYU*hL&yHcjW9ONefzt5#_L5DZBUV
zCC&T0_w7G$khDPFbLjA4gchQr^59_<Ekq<3v(keH50;XYEYZ^#bpZet8{@o}32r3-
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f<D*lWB>pF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.svg b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.svg
new file mode 100644
index 0000000..b1a51b1
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb536b11b68fca6c2feda14a8011003678204d62
GIT binary patch
literal 1389
zcmV-z1(N!SP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006~P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZLlAj_u#Z
z`u6U(vC4OVr-z4&!nL^a^ZV4*@~N!lj*;EL!|c+nZ0y{@kCNSe%Uhb(E6~yL^X<m&
z=CbYNu<YWm@aMsZ_9pG+vF6#8<<^tr(~#=is&%M_sJt)i;;!V?k>k>j;?9ie*r$Ha
zR+!l<;?RxZ&5Pj5iRaa&h3hnp?kDf#zUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyg@M18hosTq4gLTC00DGTPE!Ct=GbNc000SaNLh0L002k;002k;
zM#*bF0006~Nkl<ZNXKJfAR90;fdC^ZT9}wwSlQT_NYcW=$;Hh>nikS+;RXAJmlQ30
z`~reP!h9rY5fK#=ml%v%NcW2*9|%YeK!`!p9j?@eO$!sVl(dX2lbpPQq7oB6Ey^k?
zs%j7|9O@dHT1?tHx_bK9w7~tMVqjnh(L=x%M2IP?7y<Qw#EikdFu|4jkV8z_$Up@|
zo0^$hSX!Cl3QH94t5`#{nA_Oe*_+}@7pPhsAX*%qoLyXr)Zzxw;_l(;<&9elvWtCu
z{ggpk{9OYAgK+u<C5;99g@gk2gqeasIB}^@IWz+3G}Fka=$KejqEcUQXha;)7Ss5I
z#H3`LenClmJ}Gfvzoe$6XJq2kf>J1{WI;kKJ0~|UAGa3dqEDp&qNT7XzqkaaUr=o+
z1#2M?Vvr<g3<<BYauq^h$^cG+=H_OmAPp*I<rS*LXfdy>tTZ<Ty9%g7jaV%f5L;A;
z(gD&^Wm9d6BvMmbS6^S((Ad=6+|<&F)i0K9?(IlgIy$?$dwP2N`X@}BIBD{fshC<I
z0UCzTF>N}KoH}FXtZ5xU#_SoGdQ9icoi~30+?JY7kdD?VGZ!wL)ma15GX=wKri&IY
zS-K3arM3%1wk%%=1S@Jm?8z+{T2`)Fy=E<3OI<gJY+47>vc3+)p45b)C2+&WO`G9b
z>U%(B^A?bnt@R-G#AXaF+qUn>-bs=cv=Af8VY`xd@7YV5_jm8xf8Zc#fxPF?;ll_m
zL`CJn!zfx{3FgRAoC(IX<lw=BB_t(F^fX3Y007N*+;lfqX*B=<03~!qSaf7zbY(hY
za%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0
vW_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfuI2JN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.svg b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.svg
new file mode 100644
index 0000000..4cccc6f
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+TkSuQmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..0018157f64a5ae601a2db08fb1e4a553385c33b9
GIT binary patch
literal 1417
zcmV;41$O$0P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700072P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YTUq%
z?cc`w_U^Z_%6EXLhlh*8wYc*0`_$F)sjTIWk=?<=?9#1l?A*bRlHGmFTbkA@(9!Yp
z?Z)orvhC!s?BcKR=fQ~fChg_1=Gm3y)|2GZkm}s3b*P4@yf5tHuH@B`<I<1f&W!2U
zr+&{?nAt1h(2e2Ei{Q$M=hdZ!>okq-C-39F=-8*{)}`#-x$NG$<<Ozz&Y$bqw%ope
z+q{0-yM5!!p4z*9+PZy(fxniAqyuE0`2YX_0d!JMQvg8b*k%9#010qNS#tmY07w7;
z07w8v$!k6U00L`CL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U
z6fJ!G0)j%qd?aZR5fu}c7>rtAevy=vWRPS4Awock4+Nw@YKhfChAnWvfNcRISh~ZN
z`mlMQiCJ1kR*p$tK~YJW37-}fRaG^0h!zeFO)YIE9bG+r18iF0v9D@qXavziz!rFj
z0c|k`>H&$FfPG<#EA=6#F%@G&RS<1vZeeL<ZH6oLp?F`_2BO8n*3RC+3|G29)#3=z
z;^ge&>PDm%cZe1bPcLsD+**)b?Ca;R0@4!T78n$a(=Uh+1A-9$&@iB$a5E5yK$gJ`
zRKg;GPBV*&j){#kBPzd!ghj>!Z81woOiE6{=@*p5=bIW2_DfoNMrIaHEhvSOYBnUq
za&q(X3vg>eF8WjpAzF%x3rb3H`UTaNGO!i`AqGi;CXnzduTUivrVQXDXklS)2GXEf
zUQww=j24TkswxXJu&aPN)QQz%39&_$C><az)wVTeNFud$^$iX6jZMuhEzPZMSp8zv
z?$LpyrL(KMr?<DSf5OB`lO|7@It^0`BtXLvI;PJ6lGA3+nmxS}$e1$|Q;*r)dGi-6
zgxgZv1=7(rb=IOqv%6|RdZuEy&1~_KrOTGXwbXTk$kr8$fM8`Eh&`nhL(8hwYu2uV
zYpL%6k<IHtS~k>!*pr(vv;=M3w0R3$OG7V+Y}pFZvaJEcp45V&W&4huIlD;Gf)-*#
zIc#^zp1u39dcPZ&_xJ2SaPSamfxP$dks}B#L`CJHBPd#63FhcAoC(IP^w6P0r6eUw
z^fX3Y000U{;7!~3Lt6j<03~!qSaf7zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4Fh
zG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bN
XH99ab%9mBF00000NkvXXu0mjfCZzUl

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.svg b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.svg
new file mode 100644
index 0000000..33b4dd9
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a78fa6a1d35d8b2666ee3ffa5583db9662a67e4
GIT binary patch
literal 1490
zcmV;@1ugoCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70007fP)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j_U_>5)wA5djnuJo)yKTSzu&2=<&>D;#mDaG==ksIwesx6?A^lL!H?z8tMcd6
z`}p$9v%Rr~oVtpQ(zUVm_Wa-A^}oUFs;%YG((&EGk@4lw_t?(XP%76v4Bcit{POSI
z-Sn%j=JV~w?&h-X<go1Gukh!=<GQ)mQY+$cP1;Eu-)lwf<+0}3mF3ox<kOJq+^Wd6
z(8ivy+*T~?;;!V?k>k>j;?9ie*r(rPJ=a7P;?RxZ&5Pj5iRaa&@3EfMdPDEyzUbJe
z=hmg{-MRG3zUrWd?4*wR<=^byy5-QJ<j$Y#*|yxifZM!&+Pi(@%bwc1e%iWy%Ued<
zo^8}&Lerv`vZEDM00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~0*y&T
zK~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4Bxw;5
z6%&^jj9N(dizFWiNDe@VLDL<s)Q3$A6SI`Gj4YF!yn><<6Fx1<Dk`dK5G@?)8k$;6
z+B&*=`q;F<{i0%EU<lDez!pS^DXSO(^?<~T!M-rTmHLoFOxegl1w@;gnOj&|nc@md
z6z{88L$sLN*xK2f;z}2&S{xu+9G#q9T#3};2GQc~;pyd#TMM#_eSH0tL0bG>0|J9^
z`UNG81^b1B0`-KMf<QQNsZTjH0_Zf;$f)R;SW}`>UvOwd9MBfi_=LoyWSo9MNqjyj
zabUlsrln_O;?#mtD5+#YLM%HcH!mNz7UZH&r2wL(uqeN{1gBq6Z7Bt7ArNAaBxno?
zud;F#LSf1PPJ-s<W~LwwDrMyrs>En9udJ*zHwC*2s6&ldEfx@4REW|6(o$tpZHgpP
zQ(ISGU)Rvs)ZE<E(u&nDmTm6sNLo5NySjUNdi(k(Oq@7r@|3BVS|9-$hR`u>Is?Ou
znX`ak_8bO=x$`jfn9g6YaM5D8Ej67$9SlomEnT*J)~ppk=G-Y5ZZloEYW146a4of6
zAadP$28KBsX3YYzC%0f|*|=%*maT9tb=@GcX%djwHfuYGJ*f#pOW=;3yLQ91)c1hM
z=7|gpduGkr3t~@f#?Z2F|AFj-7+MZ(n>FijJ$5Z<AqI2Uky*2jcGux>*s<i}Cr+Yy
zzhyE|3qw~e4(}g7b^6R%v_Nj1GM9m2dS?xeKt6Hq{CR{HNK`URy)bLm#SR7>QF-<}
ziWX>sxio9mWn2lywB+pBvn3=YOY}5GT>t<le%ij3CAg3P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f^-`z-2eap

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.svg b/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.svg
new file mode 100644
index 0000000..4c9332a
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7764b74f5e5e5af1a205b2ecee9ed184727e1fe0
GIT binary patch
literal 498
zcmV<O0S*3%P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70001NP)t-s|NsAV
zbaXa|!Dg1x!H+h2o~cd1>4~}9VaDrk&F*&1?7h9crNQLey@{>X>89TD^V}-D-R=M0
zDZt<E!r<=3;qS-e@W|uv%H#0M<nj6DU(Mz6|K?xO=kn9&^VI3|*6H>3>xcgAhT-q`
z!N@d;00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0Ut?3K~zY`?bO>2
zf-npQ;7O;V0?Nb_&J(=<J35L^UDqjzCdSySWyu#<`)@-0>*s42VGTQwXf%l9@iclL
zNdOXeuiySypCM4!;O@y&X{wSOIUXq~XQs-}6QvYS4hKrA@L4=0l~^f9_IpA^Zz9B`
z$BhkkJMf8s@sL*VhLWpb4Q#i-2+rIQ+9-eP?noQ0lYBNH*ld8!i7ZVDly=E_-I=Ub
z0iwNRxdislWu;A#vn&>!J9Z<@Ox#J(okv2`=*=e-1+Z!K=krEr8ku~;d=B=YX>j2X
z3`^5Z>~y=h^w%!z!<XmH<A-BxricT_g}zMDF0MB}&$8Uyetc9c(E*Y`k!1ybMZHLj
oF&RsOM{v$!pYbjo``5SC8S!5pi4Z+;&j0`b07*qoM6N<$g5#I=_5c6?

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_only_scan.svg b/web/pgadmin/misc/static/explain/img/ex_index_only_scan.svg
new file mode 100644
index 0000000..6e10d79
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_index_only_scan.svg
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAAA2lJREFU
+aIHtWE1IG0EYfWuaFYqXYi0l6c+hoSlIQ3sopYdGWFolB6ElIFTaSwtKL8VDNhd7LYJSEA9KL14U
+C4WFQqCFtik14sFaEAOiFi0GMdiDivSHmqSZHuLqJjub7DpjuoY8WAiz37yZl29n5s0nEEJQCaj5
+3xPghaoQu6EqxG6oCrEbKkbIsXIM0vtuiSgzSeq74FUXws0egXWMsghRZpK4cfsyvv1I46SY+wga
+agVcOSGi/9UMws0e5jEq+9Mai62R8bkNAEBTYz3a/W6m1N86tY4P73O/v2vaP+2+4wGqkPG5DXTf
+uwgAePbyK9r9bqZBWraf4+mju3CkF/LanaIXEyOvAbQx8QNF1sivVJaZXAtHegGp39NcObWo7DVy
+mKir2b//pDjyGgrJgu+Fyyl6AeRPPtc2z4W/LBlx+YKYGFEob+bh8gW5jGG4Rggh4HUN9khhIbLm
+R8bXA3/XlODvmhIyvh5E1vzwSGHmUx0oIiRLCLKchMiyTAKBACRJ2pu0JElCIBCALMtcBjn0XYsm
+QgVPMYcqpJgIFbzEGArJZHPPQWFGhAoeYph3raWPvSQZ1+9IrW7AhQYAkimec/iCVncMsf7rOjEu
+X7DkpsAsJBlXcO2+3kf9dV7C9KgCjxQ2zXPzwR2kU4t57Tk/VprH+EC0sGPRfJR43HT3PaRTixD/
+fLbeEUWEHLVSahGLUn6IDH2NM2KBRPVR+jZrPoqIXhSuaGKSxzgjWXNSePkoZh7VU2mfjsFZMpnY
+IpOJLdIxOEtoMYVPKBQi0WiURKNREgqFTPXhycPF/VIOPyLLMunr67NkCJl4jDISW9kksZXNkhlR
+/8HCdqv/KCsPkxCjwa1OggePQDsvOofipD1wFgAw9nYVLx779lJrZEm0NqKcMSosrxGaJSm0ETS7
+YRQjaGLIAWKoQrSFuYxm++0cihNtoc5MaafQbtAKDUJqEdDE0Fa0mRidkPG5DTwJXsD6zx1s72QA
+AG0tZ3C6rhYDyrKuUKdWRMxUQ1hObTPIu480NdZjQFmG6NjXLToEDCjLaGqsz+uoLevYAXkZ2f10
+yOibVTxsPQ8AGI4kdPVfp+gtWdYptC00q1FoSQ4aoxOiFTMcSQDQF7HpViLfRpQzRgV1+z2KqJja
+b1WI3VAVYjdUhdgN/wCBUjPUN8Zm6wAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_index_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..d44eff429fa9a3409776ea88a11421582f6f4598
GIT binary patch
literal 1298
zcmV+t1?~EYP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001q
zx!X2~!Eep(M7`;C&hL59?^VL-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUOPl=ya2odY-AJ!Q^I^(ZP>4@#=@|<6!gf$nfm8
z@9DMj?Zxiqv+?W1?BlTT>B8af_vhLx>g1j3<DBW>n(yed=-!#<+?Va;vF6#7<<^tz
z;H}o__3YxW=GvC*;jZM>lI!2C?dG!U-mLKEz|`sV%c5A@y@~4Gs_*2!)9CZ$)RE)T
zkK)jc>DsB$=kny!kmAma@8e(T+p6Kris{*?_Uni3;k?b|^7G_h=+~#{*QV{@yYAa6
z^V}-n$%)^{h~LMD=G3Ii<nj6DU*5)s%H#0o)urasqsZg%{_KYD<-qLSy2s=2#o_P&
z=3ngIy5`cN=Fy_&(V^_zxc}ZM-o%96!-L$xg5}Vm+`fRq;O^wkpX=JUz~AoV&7SJn
zw!Gc#+Pi+^%$}{*>Ep|u5BdaA00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0?A24K~zY`?UdPD5>Xh3ZKAN;Q_C#*KwubLStTT}1_fLIkws7h8A(MzWmN>>
zU-!(C!g1n>o1QNGE)F-m&yV-_9Zp~0%lzwkMX>Y7Qw$+qOC*vv#9OIU+E4Vqdp|Hp
z3=Rzsj}RlHV`Co(QA9F6K0ZNAOioUIBtA_|O$`u3pQX}aVsv_XV2tRNGV%8*U%21Q
zpI@J!9p9ai$!5?8$Q25O9A=eD<t)sqR4O&7Ns`onhN37fbRe?1IT?#6=I7@Xpj=p3
zRDo(~X=xcqonCJM%4js1F=S<>16kw|9SNjfucyFbG<8Q*i!8FN(;>v5|7N7XYFb^j
zi6er`Z;S7og+&Wmv(4_nkhL`y`2p<k=g;S-7$T&omq}6$8iRq-fYxd@JHSEHv=d8N
zUvG1Hvaqlg8+M>wZnp<RHaFW4Qm5mKHCn;Ey0JwA?RIZ>L3V^<DXv)CmdizhcYE9C
z7f0lZot<3~3#Z5L4`9gNUeAs32*`uMAWESuRxLPecC-tp*W+<QAQTEmL{lKRzaI@^
z7CvMViX9xrG34k-K%&tED%OOq2kmmXz2Ncrd?5%Q942w(_?SncEW#FR7mD@!VzDr!
zlF1~4AtxssvYSXG(yRsji?awIi*PCekvPMgV#wK9&yAg*b1CUWCX;4&;YGXf`$GYU
zq~h@lIL+qrmmrE{uCA^!xWzRCxk9m6>W1*wBNU4rq#&NWE@UBJES7PkQfWgTi$%L&
zt_y`cl#1nYRUAoYSeFiqTrPhDH>GOz219Oddv1(Jgp@!yg?4elFxgYMER{+qrCO`q
zwWpwmuU=;#Kf#5+9!!C=D6<x|Mx$}xj>y*P&1N0_`voL*$X<^uSL_qg>P)FLo0X@O
zINC)Pazz%YmdlMA+~Wvh&>fq3bh%)%n8jVI)w=6!OnAn7Y3ybGuXzBr6b33x=w6}#
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f@j>KSpWb4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_scan.svg b/web/pgadmin/misc/static/explain/img/ex_index_scan.svg
new file mode 100644
index 0000000..03e7e84
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_index_scan.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+5DFOtuJ6IQAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_insert.png b/web/pgadmin/misc/static/explain/img/ex_insert.png
new file mode 100644
index 0000000000000000000000000000000000000000..862d837277c99e17d2b66232de79fce010752e7c
GIT binary patch
literal 1065
zcmV+^1lIeBP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003yP)t-sBmgK|
zTU$1W!3nA2a+aZzl9IuXHmaF8ou4@}wdHlD$3eX4%c58k;}QztMoPfx$EtN*#Oh<l
z>TAd7XvplT#d&Va>__Y%vc!A9!GLwm?AyJ6+r5e0zJT1nfP2yJ+`)o`(eB;DgM`!Y
z-o%97#f9F+hK$ti-^YjF$cT~E?%>IZ;mnGc+417ejN;IZ<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp;FQ>)N;I-kIv%s_fjj>EN2{-mL81y6fMq?B2TU;H~Q8obBJc?BTBM;JfPNo$TVT
z?cu!a<FM`JvG3%*?dG!X=CkkR!0_h4@94Ad>9z3a!tm+B@a(qn>%{Wy#q;gP^YF+M
z!Lnrl0004WQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkQhe<?1R7l6|m0NSt
zP!vXK&`lBB2CJaOTA)a|q(Gri2tkO1TjWv@3IQV^xBmZM;7QKm5)32GIHM2U59@)+
zO7{1iGlRkKH2+{;P|ED2o<i9y7RwoRabmSzQkSo7wrlG8!sWW5Zmt4>Pn51=F&d2)
z>IBhBy)v84HtG!NF6!#^)E1!r(pR3TJMW~P0*}+_^l{1Ycsz%>EXy;PNs=@H{0soS
zsZ1XpT4Ta-wOSp4TqzX#AZ4@ZCSYUngQhh~OI2D}smLG~3Iz$e+4NHRx>yYJuvW$`
z^<kBxN(CwX!so06?Q|;DAWA+@Hy5zZqVB&+@l8$9oxr5os0~RL2mO(muAG;k%==v=
z?{&03tR0w-WO+_F>-VJ@Oxn%n5Qd^C4wf(0IAs5IimsC4t_ET>94@Z+-*8Z+6;vrE
zDmL4OX6o)%1Th>A6*p>99W@-O6jM@*ZQMqqB9uZQMgW7>p*i#m+5guRgQ6hxVDr$y
zp&Tmg*VxiQ7K(#Tq1&Di4jVOe7K2i;IDj6{=Q^OhkHF`j9~j8~+7t{*!3X_54z}(v
zzyVU)=`^|+m|`><E<urD7#?zdpLbg!qEcy%5Em;j!C($DUayzcSJ3|AcS0pa52eUu
zI?&-*wyvxEL1XK$h+=6#I1mKB2b~PZ-9tT@h=0*v4DY{{!oxzs?R3WU6?`!;`C!nW
z0nY1jCn4^1p6^g_rK@7mS+E|rI}S1D`FTWN!TW1cBogs5lw*-d`L~U|Gby8??pJ!b
z3_1msrXytzkCh?RRq(}7X*#mo?MOj9Ce&3jKILMl+*jeyDI_uXRqokjvW~fDmd(cA
j94hm6lTUy1|Lgn&xg@`Em^A9100000NkvXXu0mjfS$;eB

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_insert.svg b/web/pgadmin/misc/static/explain/img/ex_insert.svg
new file mode 100644
index 0000000..2f7764b
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_insert.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+RU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_join.png b/web/pgadmin/misc/static/explain/img/ex_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c391233c449bdc5a0d52d50f522bd3e35c649c36
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_7<@W^2*R&GEgz?Z3h8!ou#;)brTc^v=)n(b4kM)b!xs_i~n@
zTZg#3z3iu{=c=sduCM9E#_!9_@xH(9#m4Z~*7U)THuUky@a(t1!S2S#@6FEg-rn}>
z=AWyq=(DuyyuIzz)bo;+(T1sHQjHmZrCYPXrlG`Mg|bG+s&$Tqt8$|?qQ_wH?6#)N
zW$Wdh>g1j8>9p_bw(sb(^6bTgsA6`iJD#<NuG(>Uu0Heb#_r~_?d7rT;;-=M!L{Ui
z=GvC!*OTPakm}s3d$U61)sf@UkK)gb>DZ@@!Bgwrtm4p);mwQS%8BRHr0(Lq=+~y~
z-MP8zf4c61<<X(!&Y$Yow(R4uyzqqFzJS}je%iWy<IA4v<DBW?o0iO7=--*_;H~M}
zsqpE-@8-bX#f9C$g5=GfwL|Xa00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0r^QpK~zY`?UUJC(?Ar(V^BbpRmDM2l-2b%E>$$61+gq;DJ?Apx>_)8HEF30
z1qv<yI!QY65i<h=Jn+JOn3sINdveZqt{_~`MdpTR4te<yvEk;e#wM}(cFUc+BHuEx
zwXMCQQ|!8T|3SCNw~VdAT=visCNG}~>Uq@L_ZU6tfBI|y*^ohN&5&~$8Xg&a{sO&x
z)%W_19RZglNiNj$_TBprh!c_AZnuPnKYkkfd})N~g^7GY=8#tqWQ@OlJIjk*1$#W>
zgqZ+yzW-as^djR)gE?nn@}vi!H!=L<1mfjN3{M>+(=)SYCw3BX`FwNp6fe$M7+;i6
zP0Ts|#ifABKyWz}Ug1&^<UqvyH3+S)tw$+doagEfL}HdoIG#wRD2Vfr7JgcU0O^g*
zUkVLzoGXry#4@R@lB0NWs^H)F^p4ffmArN24^hdfs+Omy80GW&f-oN{gjc}IE4D=t
zwfMKBQ@nt8X1hFR1{zO;+2q*P4r#JhD$x<&c6oOh3TyF16wJRei2A*N&O&C(yQ>A*
zTTdh_V86<|uIp4UMlA~aa1gDeGGHqKGqL&y^4bu#%6@n+eE^xPqU7u<Y%fNWHY$*+
zR%<FRl1?Nyv!GOKHH{aECXMoeS}PV0|3jEwtnxul(+=~OPV9QFW{v><WVgBso0Wk8
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f?exDGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_join.svg b/web/pgadmin/misc/static/explain/img/ex_join.svg
new file mode 100644
index 0000000..4256493
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_join.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_limit.png b/web/pgadmin/misc/static/explain/img/ex_limit.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc3efd59d70436349e5413f8c5d2673200cd74f0
GIT binary patch
literal 1237
zcmV;`1S<Q9P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(M7`;C&hL59?^MF-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUNiI=UKt$YscuhV`Hfo7w~d&?QwDGaB$~uaOH1r
z;cjl-ZEe|XY}0FNhN)yyjTym@HuUkx@a(tm>$dan$M5O1^X<m+?8WZpv+d@x?d7rY
z>%{Ehuk7Kj?BK2N=)$7LVC&_c>f@a6>b2?Mn&;e?=i8R-<FMt|ljYTt>))-W%w_1_
zndaG*<kXSm(~#=js_*Et=-rs+*p%bakE+va@8-bo<iD=jaL1~3vEFs<<gn}Btm)gT
z>Ds97;k~rudA8?#xaxo6&Wz#Ai|N>>x$A)A(2n5BiRjm+=hmj@)urv<yUl57?A^NN
z(xc|lqU_wb-^hpG#)jt9q}{@U-NAz8(4pSMh1|e_-NS_2zJTP;pYP?s>)N;G)1&6m
zq2$e;<IJAw*|yrdedEiX37wJX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*pySK~zY`?UU_O5>XV!X``@E)R3}Nf|O*B$m)u`ByS4@xQIz9FPadbCWy#_
z5QxIR-d(t0;9^epp=NwO+}WKuzd83g&$%MegZ^zjB6tlvg%IMgSS)@*Je5c!L&VUt
z=h9(fctj=}B}T`_$6pYFh*&O{i-`$^Lh+J#H90vcB}QJqnUWD>)6>#%;<hdmlBqj%
znNi+NGuMdnZJ$&sm0G1%znh(%otvA}Xf*GU=I537>Y^pJA`vX;bh-upT-1Y}qNpWU
zrdJHBuxd1!Er6>!B0rD_(XFkm>CkDl+JLg#?KIF1r;`Dr%kB06uIeHmuaQNo)k-16
z>!rcq^!bcncKg>^z*Rzst%U@<-VFmVK3~uTX8-!;P2>~V_R@9{;9VHHF1A>f4FRs|
zBA?OE7vMGU#8sVnk=rDx1^uFp(!)}Krk7!LhhZ#WF?+&W5Q;>jF~C)w9^1QnUrFAD
zodO!W2)fMJMPxr7PXMm!blKhehPqHzyFCE(hQql7Owi>vLwGZpj6gIVPo)7@b#KOh
z^I4$5t~i`N223uuKMZVge?JO|R4R1<xT-gO?1Jfc5lbH&9`&dHC!2h8X_3C)(ET>K
zCEkTm$i;CclRW{Uhm5oEtZyb+ez5U09OO<<^N=|!6gXkT=yIVYXK@xOl*Qp`v6z8u
zp;Q`xnA|;C<PITUES_h9E0xL>VZ`DIvp*md{nbSlxu|lLN>vc~?IsI!LGL0Gf><J*
zK7ylszIF^J7aUiGYNdX8C48}77w26U^+u!76h_)hp3ddaU~A`Rg$v-yWrSP~M%qh`
zvMAR0EYM&p<#MBb8`9%~Yq_ZNF0Pu*=GEVt*n|Fa{RGa817HRj*Pj3Y03~!qSaf7z
zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6W
zZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjf-+GHC

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_limit.svg b/web/pgadmin/misc/static/explain/img/ex_limit.svg
new file mode 100644
index 0000000..5472b85
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_limit.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+YmJfAAAAAElFTkSuQmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_lock_rows.png b/web/pgadmin/misc/static/explain/img/ex_lock_rows.png
new file mode 100644
index 0000000000000000000000000000000000000000..41c1148bb185c87898b3fddaa76be36a15703336
GIT binary patch
literal 1520
zcmV<M1rPd(P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700078P)t-s1prKL
zYin<9ZE<dHb#ijIWKo23T)1UXxMxwgX;FK4c)Ds)x@%E3h{3vUQGR@Ux^Yo}etx`j
zQG<Yhyme89f`YwxQHX|yzI;)Nh={*{QH+X;38~`0f>Di(jE;<qz=Tnbj*fDcp^%S{
zz=%=8h*6S~l9Q8@!H+hYm3+dEQI(aIm6n#4mzS5An8cD%#F$YswdI<go|~SYb*9Ih
zp`pT_QO2H8o};6lq@>5AQ9-=u%c5Axr%}nMQA)t+%BxY=s7}hQQOB=S%&$?Wv9Zju
zQmC`DUBv4BtpTdFwW_wZW5(*uwpDA#=xE67&bnBwy1LG~S+BgjZp-YjzP@$M?Ap72
zw8FyMy@}(!bKJgw|GhAK(eJjz#N5GxgwgKZ!-Is=@ZQ9P-o}QE)b8KMhu_GEk=5?t
z$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+m
zlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#r{>z0>Dj2~+?VOvsp{Fb>D#L7+PCQ5nd;rD
z?A*BN;F{~+tn%Ha?A^NS->vfAr|jOk^xmoL;H~Q8ob=zR?BTBM;JfPNo$TVT?cu!i
z;i~N8u=L}t?d7rb<gf4KzU}6+?&h=a<-qpkvhe1>`Q@?i=(G0cv-an-@9DMq=d<wX
z!uaU6@a(qs>bm&qxbf@6{pz;(?7H&p#rf^N`R>2@?#1))$p7!Z`SHN{@xlA?!~gNV
z|MAlO^v(bC)c^F+|MtiK_R0VD+W+?3|M<<SyU{8D0004WQchC<K<3zH00009a7bBm
z000XU000XU0RWnu7ytkRE=fc|R7l6|lj&CyQ5eRxeH+_N%WSus*<!m=*`Bu8B8iHI
zC~mlcpojwm1gMN_hN04AG^HkyMp;_fzWiC;xpx#ij-Jk`bLxZdhi7KancsQe=eh3<
z3Yw{ZPzMOMrc^$J5DEl>R3a-OF)@e8xgr!66UABC*%d@ZUSZ)af)^1)L_`RP1gMF`
zrP$b5A(0C4Y$7i?Sy)K?L6_Y-qqanb<M+*I<|&mUcAZbVnRe~SO7sCn1_lO3#?<5S
z41pnrVSK<x)3g_0A|~ZR%)0Q%xVS|K8R+b6_i*ZIY;5cUjNNMO2DH(rb8yJniyPR-
z(tX=Gq#Ysc7_l>ev0AM(=rZcYkf@Xxj4t0ln?*eB=x3yTb~{A6t+$OdaO!GmS~0Te
z24{TE!C>SC%|Gtq5pvPx1ztyMQx8I}?^v_?^qCXK;t$OVo);YybP}+Yfi!)xvdGo<
zqo2Nf{PF$s@L+%cR@fzzEilx_FhjuC-A(&|*XeNdfF6pX+^Eatcca5>4G#A9K3UHq
zNl8hW0WP{&7u8LGQYjQRgq(YWkWm(S7&eYDb~_H%=md_M>PiYw3PmYKPQCUc4?_J2
z3YJE}wpOZC6sRjLm0J*U{FNWMzhd0WWEXCm#o|Ip{0l#FH{`bxJN%qQMiFvnG3?@_
zyPRH5ds>^|S-9(LHaBpY&8802W$)8xFF$<!`R#4*z2@cx5W(|LbRmZ>%tZzHdW7tW
z-nMaVSm>%{Axjp{p9>MEP6rnQE2UB?>VQoymz#m9AYY~jd{+!b+Qcai)*>$|G6923
zCL{R~I6w86Q`UtGx-c0!K!=_rTX>P90f}WyPLIC}Mb){v)4|$d(Cgbki&j(Bz&{wC
zAMXHG=pvT{8mUC0=11^i1R{D_u2c)EBogry2#z!mA#<hDDxj8##Y%p}ZLyeL6TGx&
zG*$JWUaeHt|Fy(iCIdVLtZgKzZ2=8xwHkI&mY3J^4~7>5D=9@T#H@>Qkw{d=j~MdV
z#lWgX!TOOBenbyXLCdK|!XiqsSX2(mrbgOGEu0@~po`j)l9JkK5=)=%R?gJ_Sib;}
WuicyymX%Qe0000<MNUMnLSTYGrAb2o

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_lock_rows.svg b/web/pgadmin/misc/static/explain/img/ex_lock_rows.svg
new file mode 100644
index 0000000..5889bcc
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_lock_rows.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+z638y17wPvQAAAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_materialize.png b/web/pgadmin/misc/static/explain/img/ex_materialize.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd0bb90dd2ef1cc1baf7f932817d1b6bdf463b
GIT binary patch
literal 1221
zcmV;$1UmbPP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001^
zyUc^E$aSX2Hi*HV&EAa1-IK}Ps?y-H)ZnGh-mTK#zS!b$%<V(H=}o`rQ^M+n)bNzp
z@OsYdhtuw@-}7s{*>k?y5Ub*3#pqna>5|p&pRKHao1$`-p~$a;;^52R-owF<HuUhw
z@a(tV+qd1?w&2~u>*k;8<eu;8wd&)Y-PyJ2;hXR1v*6sp^6kaUy`#sfb>G{;;oZva
z=CbkX#N5}k+|{$*+Q8@Bm*&}&?BlTA*tOf#vFYHN=--*_;H}=+zU0-B;M>TCsbo@(
z8Sv)7tJG}W*1h1`$FJLRv*CB}=)&sVtLfaT?&7`L(XZLhuG!A5qQzgO%Ve<JbGPSw
zxa)u8(U0QJjOp2^+S0Jx)VsRuf#J-G=+~#%%&gqgy5P!*=hmj**v0JKy4%va+tIk@
z)THLqqu0r)-^hpF#f9C$g67eo*vzZg%d6JMsM^oB<j$bnzJT7<!|U0$+RnGy&9~&u
zp5x4(<IA4a#irW2ecQc%&=gc%00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0(?nCK~zY`?b6p*8bK5XU_ww#jGAI1rVXt0A|?yUN>Pg78bgs7LkqpPC|yAQ
z^~}hc;|y@}z&Sa2@IKs^*>CRt?k<n_Q2(eN@lkOdJ9#WX#r69K{Ds9u;S&GJ)1~ES
ze2fTIR@a`t5WIY~zW!Q(k;OL~8$t+$3(Evq6^TTff|a$mo9_g3PI><U71uHH@iqn5
z?;ilML?RJ$9V671R4U!#Iz}WinM{JZTtN`2QmK@pu2N4Wa``NxQfX!ot#$^{Xmr~+
z;j$<c`Wac&>g^qjd;+Mrj*-t4Dy|b#6bd*+Pt%5pDQfi=wPQ3IcZn=8g&xp*CKE$u
z(db|b<Nm(cf)j<-YNY{fGTBI?)*T!gAu*fn1hHE8Xuz0kHU~)#4k59a%}#=dX_Ugi
zI2^7!a^yZjWSnBxZnt~>x-s`LGP5^kU>L>)TrA6;0C@jxeL01O+O=3LUn!5*>+|C;
z#@J8_HgFbX0nh#I{QUU%`{~6GH;+r<4TZxIl0>5{V9$cV*q<pEKhYGs)9Lh5zHmIA
zz=<Oo2tX1{CR2Cu-AEu33i+r=Je|(qWSk-!OQo{J#ynmhOyN&tGP$|`u@jb!WkEKd
zFJy^N>4`)VekxZimGCKCihQ|TE)WE!sAQ;OwN|U+B$oVjnFob(`Km#XM7mNfQuSJ`
z*2GEb@)F)(xVoAknM$=<f~3_V2ujfg?M9=~CJ0PX&rzLbv)P$@W7$Fh-rw$adlOUY
zb$Ea0x<A0Da4EXO;jl-LdJCoK-`v~`aMFOJ3wl#Xvo#uBQ-i4lK0K9-M)2V&WSpYi
z>rG_o^ihh=U@(|_V-NMe)o+jt(ePu5AK?H103~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfVJ3t*

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_materialize.svg b/web/pgadmin/misc/static/explain/img/ex_materialize.svg
new file mode 100644
index 0000000..c6845db
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_materialize.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+WLxZKdhpAAAAAElFTkSuQmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_merge.png b/web/pgadmin/misc/static/explain/img/ex_merge.png
new file mode 100644
index 0000000000000000000000000000000000000000..3fd8299fdcb72604d0c97816d38ee25ec51699ff
GIT binary patch
literal 1127
zcmV-t1ep7YP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001z
zmC<Hv(ap~CyuIwd!0pk|^3&Aw$H?!;$neU_@!Q+=*4Opn;rFJf=Bcaa!NTsv#qYJY
z>$$q@!^H2!#_)!zWKxY8p~PNwrpGpj!Eep(LA>Zn!0CI@?}XFvW5()e$n0**?3dZ`
zpxg0;(e8}Y?vd5*uHf?tsp4J4>N2(Eb<ON+$LOQSVRDwC!H+ic@W}A&w(se*^X<m&
z=CbYOvGMD~>g1j2;F{;$m*?A-?BlSe%w_81oao+}=Gm3(;;-o4nC97(<=2z!;H{?6
zXYAmv@aV$Fs&%W?Z0p~x>fEaD<-p_8kK@pe>DsAbl`meHCBewL<kgaJokg0;U3jiO
zp0$U%?SbUekm1dYi>qviyiJ|ZV|uVayzqqY<iF|Ir|#mtzsI+$-f_6=fAHqO=+~#`
z)}`&=yWz}=;K+#I$A{+Bq~67a=F_9#$%)>?h26u0=Fy_;-MZ$|qU_wb=Fy?$&!Owu
zx7@#g+r59}&Y<1Fg5%7dI?;o_00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0v<_3K~zY`?bGX5(r_3DaGDn~EzQc+f-=ht?ef4F_ZT1uwF%R(Lb^=WWfKN0
zFq@$K>;1-ob697`-_$p~@OkmP`<&<Z{qD@+xE#k7!Wh%W5n^n7Vq%hTUY)u&O-wtT
zGuH_$8NV?*J4f7{pT9LmOy0gTHS@oavu8O+V;7hOa^b9myh~n)+@p*!Z5*NQyWH*v
z)WgL`OOL51%THIHQCQ;gcsy=ub#2Y{oLXL8e6fNj_O84fA+P%HH*FjNug~Z6f|sUg
zKlm9Y5Ckmog+d`3=x{j9027HuW0vIgKO}AtmSrFiO(c>wNIZ@R!?LLW#1iZ2j3wDH
zWf`OhpcKJmHj~4WH(-ouTamY;SNt@?_#qgJC3BGDIDQkbgke(=lwv)NQgHcvL9ilQ
z8{6+3!+!vnR0I{9Os9(+@P&dk*?IrrBQj*kFsRrlDmGgz?(&F~%a-KR=PzHkNs=sy
z$c&`egFQj0NLJ(<GT(ocN}?!|HXB1`XA4~s`&V+=4EGZ@c|njsl4WJz>dLS<v7aT=
zPa%}c)e6WpRjr>w290sM`GP^JRT&hu(P*AR2H%Cx8^sD$O;e!W_|<CT$uNb2QXDjY
zw-2$zW@Dx+l2DN)*ss@{2XOdD*N^ZIt7uwnm_qM#y2p4TYc*7Cv(-A$5$Se&c%rB(
zDz@D^>F99WwM7OgbR)%4uh+w8IY(ocV>y2TWnrZACmaDB0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1n&OPyYY_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge.svg b/web/pgadmin/misc/static/explain/img/ex_merge.svg
new file mode 100644
index 0000000..eb44bdd
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_merge.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9aa51fce9d775f169f70d115761e9817541c48
GIT binary patch
literal 1599
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.svg b/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.svg
new file mode 100644
index 0000000..ac395c6
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+RK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_append.png b/web/pgadmin/misc/static/explain/img/ex_merge_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..12fc55d76f504140935d5fa422f9a5075431bbc3
GIT binary patch
literal 980
zcmV;_11tQAP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005JP)t-s00000
z003rd(Nc{WHi*FosNxW-;$)T3a-%gGuH(UvHg>8zdY-9xu0FMyetNM%gs???v_por
zNkF{mMZW2fwrozm=1joprKsemspnI|>RQ3)jlolwyLFkoc&e=DiMiWd#p`6n=&rBn
zp~POI$6%JsU2)6oq{?G&&F-hoW_r%-+rEF>y@|ZN?A*VB+`xgozwN%i?cKqGgVF8X
z!h^rT?cT(M!ou!_)bQTLg~G${i`4Gl$A*&C@5RRNy3wP>#_+D%ag^Bb;mnHS&yCB>
z@#M{(<j$YW&GF^VpySbw<<Fqw(vQy1^0ec5<kOJl(V^tjk>=8)<kgbV(emchr03M6
zw&#51*OTYfrLEud)710S)b!}rrs&tF=Gv9%*r(Rk^ttPQ>Dj2~+?VOvsp{Fb*xB^D
z?t<yus_NXT=--*^+qmiAn!NCY>)x#C;hXH;y6fMq?B2TS;+*W@t?c2h>g1i+<;(2i
zukGQy?BlQO<FM`Hu;AeL?&7`e<gxDKzU}3)?&h-Z<-hOd!0+g@@9DJg=)v&l!td&~
z@9Vbl>cjBtw(#t?^6bU)?Z)%($Mo^Z1Vq=200001bW%=J06^y0W&i*H32;bRa{vGf
z6951U69E94oEQKA0kKI$K~zY`?UPkg!$1^;v)KUULUEU1#ogWAouV!7?ykih{xC_%
z08P8uzzo9+=i$EWd~@#Ev&%65`jNspQS#J=aPdC=LHS%|Vph=rrxEdXaCDB~V*P?6
zOSoTiagtKVQ(H*3K0T*!kezPn9y(&R*|RBuD5{jL$;c}oWwnJQBj0x+hE!(ZG4|b2
z56J|uqU6ai*kJH@XNdN;E;@msASoyeeLdlI2K-Hg!O>Vfiyb6RLfk-<JSpwt1POuH
z$A{Y&cse`Wy#}=#6BO#@!i&hmHd?uav*Et3WuQFNGM5Uyz`)%Gnzz7yUq|yS4PkhF
ztFRheEux77*lcU89;dqKTir#MXUoaK4P4Akj_d*D?}vtngvyQCK3LpK>BaV2;4y5!
zFcp^>3gRlIkf(HCo=X%&Q8JG`mtuU?vnWc&GXk+=|5<z>)+K-bO1R(MTt0_mbAQjO
zbuYw{VGA{*NiY=`WVb62QpvD}s*xFtx!GwA)=08cuRN`gIaG!Ep))fzsaD}aS{Y79
zOvZIsXsWB7QXr(2iS{Zh6GdK`j1X5QioEi_{zPxhzn!H%4w>%&0000<MNUMnLSTYP
CgEgxF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_append.svg b/web/pgadmin/misc/static/explain/img/ex_merge_append.svg
new file mode 100644
index 0000000..992ee02
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_merge_append.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ce4839e02fd3f17b5fd8e358ff204046c38d26f
GIT binary patch
literal 1344
zcmY+CSx{347==TqEn;1Yb)kzRwvO5s8xTS1=pdpEBQS_s6{;)(R?#BJSj4Jq3RJBX
zQDkYPh!_@AR0IrxkdTEC5=cT4vWF~}EH}AX?oB|$Ort*a;rwUL%=w=FQ<a$T<6{5M
z{81>>;#h1<lGj7u%Fow(H%%9op-|{MiE#(_c;jQdE<1-e#<7zI!9lWxOtDPx9D+$F
za|9Y;+1PBmM2g7e2w&iwoP>oUczW7R8US1I0KR>uy>o^_wbJQO_e)bxulW@b<ZvB4
zo@1B>(HS=8h)pa;>PUr2w*vp69j|&N3$LV{>k!lrt7&peF2@=_WK3t6FL1zCrK1;c
zH7nuAD!5GzQ;lwx*-Zivior!Ux<t@i6v-ISq51BG4od>61lETNxJ+QH5ZZ2v>;w(c
zr$;)~&Sx6uh{@f|NG-y(<=?}pne`Kl{^u_ZM7rq_Go5lf9JaG;VDH?l&W*_Qz^qE`
z(rDcPFl)8GZeeAZfrp4=T%kmSLZ?ImE0ivyaqclYvz3#LAOEF|e_A$BCFnXQ9V7Dv
z_73408`uMxdz=u{jY#L*+Na`NlkRVmw$Z4rw*oI<I~TTdU}((ckeqDT`iPf(toCb@
zT??6djMEJURo#rD7SwjRVJU3mIH578m5tcNm+*=8qsIyfJBhqUufSr+@WP;cFeAGU
zOx1wur*5ajX&H5xSqLO*8Y-zE#x-)&i@KtYKUhz+mi9wsb~6oB6U@p^NJpMUrV*>q
zZCA}es>*=`951s&nb&8&N(4(twsIRVWR$lXrFet*iAB}#cBo*>Bn%3iW`PS*7^Q7$
z_8+Q|3MKs-z-xxVNk~tFw8K^n)nS-0h+9>R>&oGB1*KF!-sCj%Aq~|!O|hzn95ds<
zWQ&4YCLb!14P4c6a1JBSs-{>}Ll!034sgw~K0UWdJBBk&^&l3R0~ojKxv*KPW#gp8
z%b<J^8ZM=HN9j|LlyCqQ1x#go!@_geqvySjeW$&woSohR5F4}WAeMO|ZS{hWZ^o@H
zjt<~cX=Bt;2A4)*(K*93HuL+nBQc3L{_3j4ufE$?-cl2G_hx4!iJHcXCZ>+~h4>dY
zSFdYJuBo}Vk>A|xk3MZ$+vF3p@YH_cBwxfkBosto!jdAwL$+=?ya7+A@<*k6g(9w!
zvR@G^meB8Yt<;>!+s)T3aowoc;8%FDtaxd7@E|tX<MBnKJ@e?Y(o*z}vfa_c)PZNG
zs-i>z%K`%f)vG>Tv0{ZT@XO_&ueyC`AKI845G$^ZA*NQ}>28@ViTY+6jXxB0|Ihe+
zAGQbA9hF3-<mczxr>ZM7wVqPyI&zbynjb2^vgu!ZXGXE!his~s5{t;!8Lhxdd2+H$
z`a^M1P2{d350QAO=#A%f!-lxV@)Wji(}5@Phm(@(Lzd&2xj)T=+t1GfFXQUTsNU3-
z3CFt&S^TUm*_%@dF`1P<NcVl;vpy!2^m&H9U6|t$5mLzMU;Ubr=F|HHSR3Eadqx5D
zUS@KS_2<1=;NKL~a}pYy|H!jsP2}&ojk|&;3b$YI&{zkqJQ;7?@f+?)@T$!?Vu1u2
z6*|R)<?x~pmIY>P?D=^xPs_+6o5h4r&fZYnYKX{H+WIe;MNe~RudnS)#<~{;28H`~
zp;7y0eV6*_*%98?uP@khq#!fBAS?1jewNozp_mX%a7bt{Cgfl!CNgYGWLVg@n5~f*
t%(lAqYyJnE&CNWOeepej+5QfA+m6J9zXwKZ4|RG0RP3IF7(#Sf>3<opDS-e0

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.svg b/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.svg
new file mode 100644
index 0000000..3af860a
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.svg
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAABUBJREFU
+aIHtl11oVEcYhp+ZOWezm91NsjHqbrQlW2OpgqbBi2JUCqKkvSroRQu9ECxUtIiUSpEWijcFFQIt
+pVILSlMoLYWEXhQlVGlEoVCwMUHUqm1qK/n/dWM2+3PO9OIkm8azCew22aYxLxzYec+cmXlnvnn3
++2AZy1jGEwGRjfzqmwf68pVBF//ijhXkyr/+2tqsc8w3jGzk5SuDvLm/ipHRFFIIhIRQmcnpzzs5
+fPAZxsbSCCFQUlBSatDw8T3efbua8bid6R/wKz48dacQGmYXAjAykqKvP4mUAilBCmdjH46mGR5J
+IaVAKYFUDv/okcXDmOXqXyjIgs62gFgyQmYNrbIyEwSZmC8tdbqWlBpIReaOBAIKAL9fIaTI9C8u
+LuweLRnXerJx5uIxvachos9cPKb/67VMIa9Abmlv5JUXdtPS3jjf68kbs172+cbJlru6qa3Lxdet
+K2djOOjiN0aC3OyOZeW3V69w3buCCWlq62Lr7k38HktR4ZGsLBI8H/Lw0bdt1G9bR/+EjRJgCIj4
+FDfv9FLz3GpiKRtTCgwBIY/i6o3urOMXTAhA+3CKjuEElV7JWp9khceJ7J64zYPxNIYEUwhM6Wz4
+cNJmKGHhkQJTgpojWyiokJqQSdAgcyJP+Z3pwz6JEkbmRCqKJANAyCMxBJkTKTFnv9J5CxEyt7+H
+Xat6uPiD87t3kmsFtgd/Y8P9X9nwWP8SFeXPn70zuEGgqqw46/j5CxG5GV79aAPvHfyEdGpoenKz
+nGvnPiNS24xtxTO8VD5oO8/K2h0kkzZKCQwl8HolV38ayjZ84U4EIJ0aIpXscfG2Fce2xlz8xITF
+RNzGMASGKVFq9jnzToiEXFz55pIRkndoyTyEGGZ51rZUvpljT7a9XpUp4AwlMM0FsF+hchNSuXkv
+184ddvHl0Tq62867+KS5no62ERdf9bTPxcG/OZEchXR1NLFl/5RrabQGwwjxyxdHePalo5Ou5eSg
+Uvrovn6BLbVbF961pFQ5f5NODZGc6AKt0ZaN9liA41pWOuYIsTVaOYIW3LVWlqxlPBnHX+ynb/Sv
+Gan8XCm+th0BdtrCtpzHeaEdcbZGa43Wds5ryktIRXAN8UScNasruf5H6wwxLe2NHNh3gJb2Rlxi
+bAttWei0hU5Z8A8h2rbBttGWDXbuZU5eQtaHaxkcGaa6ah2tnWc5+vVO9jRE9J6GiAbHmg/uP+QS
+I1UIwwxjmmFMTxhlOK4lpBepAkgZQKkAUk67VrFf4StW+LxyTtfKq57uHu7ULe1f0mNdY1XFKoLB
+IMFgCVJIkAIhnAfg9NlPaX6nW9y9dFJ3dTS5xiqP1hFcvdHFJ8313O59PANzXGvzptL5qUcioahw
+xMDdW20MxNrpf/gg8/7QG29lRNTX7KOZE3R1NFHz6ikS432TIaQxvRXc+O59qncewUo9ckJMgzKK
+6bt9qTCuFQlFs57mVHhNiTiw60SmX2Ksl/jofeeeWDbeQAoAK/mIVGLEcSytM26Wi2vNez1SX7Mv
+qwjAEZBKO9ZrOw/guJRloyfdC537ZZ93IVOLb+aE6509ab160p0yQuxp63XCaxEImQtmUQU6kEZr
+G63B4w8Dzp3AY6PRoEGZAaBAuVauqNy8l5vff+Diy6N19N/50cUXLNfKFV0dTWx4+TiJ2HRhVRQM
+c+vCcaLbDmElpwsr5QkwcK+1MK6VDxKxHuLDnS7eSo6Rirt3vyAV4mLDkhFS0NAqCoaztpUnMIOf
+ai9a17p14biLL4/WMXCv1cXn6lrLWMYylvH/wt9XlkmZoNULOgAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_nested.png b/web/pgadmin/misc/static/explain/img/ex_nested.png
new file mode 100644
index 0000000000000000000000000000000000000000..15c47316d5d4163d97b95cda99b2e3a2049341c5
GIT binary patch
literal 1108
zcmV-a1grarP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002N
z!qAqP(SL){b9d2ZYtf>m<h8fzyT0th#P7ht?ZCnA!NTss!|uez@5RRN#>el+$nVI>
z@W{&W%FFS~%<;_4^3Bfj&d%}9&+^dF^3v1u)YS9U)%4fb^{cMtuCeH+s^-7I?YX<_
zv9sxtmEUxE)|i{&!NTpPsL;d2(7e9v+1d5IzwF1wx2vUWos>nDi!Y6V8N<SK)!C8X
z=A7p9r{wXW<M5#4@SfuCp5pDD;q08@?3>`~o8Rb}-shO$=$_f!mC@3Tzr1kL*o)26
zebU;8+TxSc+KR}^cFoU%&Cc<&uw0mqEU~Ov&d-F$$aBxniISDkwXlcC#=6<xjNRgy
z+TW7e-jUhekLvTc$jR@iqGZ|JkFBog*xQf2zwOuBjJLS!xVh`s*o@`wtmEsW*yMoL
z*Nf}&z3K0|)z^yM=aJRciPhDL-sP6m)QFdjE7;qU%*}nTs8!L=iO9rx!oF?G$b{0>
zhS%DTytZrH;g{Fej@H)nL!K(600001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0t-n*K~zY`?bYj3+CUHha0E^Sm0C-!f`UTD2bF{cA89RMMd}k&#45_8X`@6D
zMDcCwzpmmq!zFho^0nPBncU28_IB=WSEKnK57DI6=@B#ZI&Bj!nhh;RQ)^pWyU}cZ
z)A6?RoyGFL>qB>s)oQibdVBlq-DU@7w9Wkk{RS54bh=CqZ4J>G20jkDS^IJi4ZF;`
z8lrbO21iC&#g2{)nR@g!3&`nlGsS~(TRx#$;_)EO3xVtkGWPWA<#-<`!pC#oDzTG@
zm-7e4Cnl!^LHPV-`fD&4425UD%|;##g>e44==XU{gcqWVOA$^@mY1u)d>}fv62tcu
zi?6P&^D2@+DDn7(31MX;iI<(+h;RM?6?qPr6k<u`VluY5#Uwyj-DZ&~GD!iwmYp3E
zNvTN!?e11h8X!#~86`=e_H5Spl!p|M$tel4d9vA`ebie9IZ>Sj{rZg<Sws=27lu-c
zC{alo_Vx${Bq~Y6em=jiHXz9~0tdl-K6I!$8ITL~EO2y0s0Io}dif9vlTN=_Vvz7L
ziImF>GJZ_B{3m6ZPJE}8xcZ5m`Oke+!p|<Q#uX3w<m}?|il?`W$kJNndTTqC$=!&O
zB+G?jsp7x9xaAm4DY72;gNf4L^Q&7%Q+ieFtE0i*;iR56euwqsx`u@l8x{?Wqy`3G
z1LL)UA^xw%J#C$lG-cRqEC2uiC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$c
zGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLe
aHY+eSIxsNGmsP9)0000<MNUMnLSTY)!c1@g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested.svg b/web/pgadmin/misc/static/explain/img/ex_nested.svg
new file mode 100644
index 0000000..9ff1db4
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_nested.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+pGM4BAAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1c0763337617f9ffe8cf13ba0f47f4cbc228c5e
GIT binary patch
literal 1741
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.svg b/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.svg
new file mode 100644
index 0000000..083e373
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+zFy1AyrM+wAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0e8a17d409343ea937e43ebb76e8ebce3cbee49
GIT binary patch
literal 1679
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
ZD=;-WFfhuORjdF2002ovPDHLkV1g+E(G&mx

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.svg b/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.svg
new file mode 100644
index 0000000..48ec1a5
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_recursive_union.png b/web/pgadmin/misc/static/explain/img/ex_recursive_union.png
new file mode 100644
index 0000000000000000000000000000000000000000..66952ea454e3703dcb08251bd2273193aacaeb28
GIT binary patch
literal 1224
zcmV;(1ULJMP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005?P)t-s0001q
zx!X2~!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_`zG-s;qsyLGzJqh@Q-%+2w=zwN)l?ZU(F)710W+4RoO^3l=q
z)YSCg;P-*9LUN@xnY?(sz3iu{=c=sduCM9E#_!9_@xH(9!ou#w#_-nG^mC;*r_X10
zsycS7JFwk!QjHmRt30ycc6zZujKETQu|cJ%<dC*(*yYQi#9oB3MRKDxqQ_vQ%44U^
zW`?#&cB(tB+HrWUKD6U`wV8gwk2a0LQ}pr4@a(tn?6&Xfw)5}D^6bU!<+1JKu<YWm
z@aVy|=X~nqo$BM9>EWC2=(Fb9mF3ry<kgbv-mJOnfA8zI>f)T}-<j^_vh3rp<kXSl
z(vRufs(PNOy6%GS>9pzKn&{q{=iHa<<FM@Dt>n{?<I#@d&yDHXsC=|Tyzqqc?Z)lo
zvGC}^@8-bj+^Xr?sp;6K?cuzZ%w5~PiSg>g@8!Sl;=SnCr{~qB=G3I@-MZ}IuH@B`
z;?R!h*QVdchTg@6-NJ+A(V^?#t>e*;;mwQZ)TG|TgxtV^<<6ku&Wz#Air~qK=F+3x
z!Ghesf!n@+<jtP$<G$?Py6fAx<<Fqx&Y$Ypwpcc+1poj50d!JMQvg8b*k%9#010qN
zS#tmY07w7;07w8v$!k6U00Ih0L_t(Y$75g^1>^xnCJ=y<#A#t-W?^MxXX4=G;^tu@
zQVTC1zknc<kg$lT7_nMNw?!Q63n)p*ixQGjKvG&pR!&|)T2V<^MOB)R<&tXZ8bB?Y
zTG~3g(t7#^hDOL*uzFF-*u+!=rxxs9)G#x*z@-Jd7o{w%tTk}?1-r1MjV;hGcG4gK
zB^fj<EbJY?TCiG<EyOGw9i6~>aJZKN(>M(aXP_REv;f1z+0g=bbYa!PVBrGNg6c&y
zE$GHsxMJD@@iq)#7-!*Tfnhl$q2L6P?j9KF2$E260x1tqFN|~qPAIs6hL^VmnqMFZ
z1!u;P^700z3Ljq(fRfOJf-}iV`hlc<{R0Anf_+0m!@?tc(LxL(V^~CjT<see6&(}n
z8yXj%km!r%atvE6lE7M$Q({uneADAIGPBUNWP^PHCBa&9z*=(i@(T)mi;7E1%h0u;
zIvN;W<rQEpm1$Ll)xI^gb@dGhEf7DUI$Fb`yrL15lAFL`1tk&D3{C~8j<(1F>Hy^z
z-{$b5#1`Myw)T!rge{O%fa+*?uJrBd&g`l4?d|KIFcHZw;8cL!(G0$mCYMfW^PM_v
z!t@zPwt!Osc1JV#*3O(YyWMxr+<EgCAhbYI0d_|-_%5vPow~?(@sgzrmSKb#c1Po|
z9FL>%0_={)uLY~4@dA8~9tHFV07F>Y9b_N@c>n+aC3HntbYx+4WjbSWWnpw>05UK!
zFfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GK
mIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTYtrHx7e

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_recursive_union.svg b/web/pgadmin/misc/static/explain/img/ex_recursive_union.svg
new file mode 100644
index 0000000..353e1c7
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_recursive_union.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAABd1JREFU
+aIHtmF1sFFUUx3/zsbt2KWy/dtWKESgqVdkFm/oBBSUCJfFBY1+MWjeisfqCRqM0xvjgA2JMNREV
+1+8Wo8FYgwYTClUjGsVW20ytREoLJlXUtQKltLTdzlwfZju07s5uWWcxmv0nm2zO3Jlz/+f8z73n
+Xsghhxxy+C9BSvXw6ZaDornzSIK9ZmkpdvaN1Ren/Ga2oKZ62Nx5hGvXLGb/YAyPDB5Z4soiN807
+Ne68OcSRkQnyFIlzFIkFs1w8vb3jbM07ASmJAGjHYnw9MEaeAl5FwquYAe8bmqBvaJx8RcKrSnik
+fyURFtISCRW6cMlYGVnkc3EAKJutkqdgZeQCb9pPZRUpvQdn+/h6z/fTbP1xe9MOLen4thk6drr+
+VIBD0W7RojVSFgiyNlRrDZb3S7y0fgmDJyaQZVBkmcJClS1bD9G0oYKREQNFBkWRyc9X2PRMzwxp
+OF9/KkCL1kh06DsORj8n0lov6lZvtsgcPx7jj4EYigKqIqMopn1oSGdoaAJVkVBUGVmeMQcLTtaf
+DFAWCHLi1DCVly2l7+heIq314syndeYIFbpYGfBQ5fdwbYmHRT4XYNbfFQVuygvcXOpzz6j+VIC1
+oVop0lovOnu/YFnFMpp2NGaXAc7XnwqwW9smPumJUFW5nH1aG9WhMB+wGYCCAheSLFk1MmeOGZ3Z
+sxUURbJqxOtVzoiI0/WnAvRFu/D7A+zT2rlo1jVM1sh1K4p55Y2fEl66bkUxzz7fl9T+yosQaa0X
+LZqZ1ZXlNawLhVl0QWWC0J2sPxWgOhSmRWskOG/6qvX5F39y7/p5SaP20IYy26i1aI3cdPUaAPoH
+DrBLa+THX9pFMjJOQQVYELjC1kEmUasOhfnwm0bmBeYSml9ukckmsrIdT0oz0hoW2uFPCM0v58Nv
+mhPGOVl/We0r6lZvlm5pOF8sWXh5wrNM688OaYk4ETVZSXyWaf3ZdSEpiTgVNSUJEcis/uy6kJRE
+br91btJFwC7Fyexrgndw+PduFl64gEhreFr7kwnKAkF6o3upvGwpnQf3WmQy6JDO3PHxkye4ZslV
+HD7x1T9uf9aGaqVLzl1FZ283yyqWMblfZZ3I2lCtVFa0grbu76iqXG45BrP+AgE3fr+bkhL3tPor
+KHDh86n45qjT6m+3tk0cOvYlVZXLaev+lupQGDgLZ/bd2jbxad+rVFUu56uOfVw062rqVm+WPo48
+IfJHdiV846R3HXb2G+uelLbueUQMu/o4OnjM+hachTN7X7QLf7F/GgmA/JFdVKzfwkTsKCAQBqiu
+QjreeoCltc8RGxsAQyCEQHUXob37CGDfhWT9zL6n623uue1uXnvndTY9/Nm0QROxPxkf/RWEQBgC
+DB2A2NgAY8M/I3QDYQg8ebr1jl0XclbO7IqafPkVAivqwjAnDZj/9fjPMDAMI900UxNZHfiN1j3T
+bf1xe9OO5OOTnRkkm30Ew7AICMPA0OMT1g3EhH6anP4PiVQPNvDY/cl03ED9Pcl03MCmKe9HWutF
+/2g7sk0frqpFCHeciBCoriIAFHcx7jw9LjcD9ZySmRGx2/YhMx1Pnkf6R9u5vmoVPb0HWFlewwe8
+YI0pDdbQ0fRgwoRKgzV0bX80qZ0UZ8S0lw+Z6LhFa+S+u+5HlmV6eg8wHvWxLhSGKUSOdDWz5PYG
+YqMDcYkJVE8xXe9tZHHNU4wPR00fQuDOC/DDR4/bkrCI2G37QEY6rg6FefnNrUDqE2Ls1B+Mnuw/
+nVWvmdXx4d85dfwn06duIHz6319NTiTV5UMmOp6aUVNOpzMxFcIQiInJQJnZBaZk2gweevquJuXl
+g9M6TiCi6+bPMBB6fNKTBCeJCAMhZrhq2V0+OK3jBOeeEjxe3aw/3cCV5wfAlec3CQoBQuDOP29m
+ROy2fXBWx1NRGqzh+/frk9r373wiqT3tqpXq8sFJHU/FxTdstPFpN9nUkk3bojip42wiLREndZxN
+pCTitI5zyCGHHHL43+EvfgI/0ezUgS4AAAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png b/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.svg b/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.svg
new file mode 100644
index 0000000..6d18a73
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAAACBJREFU
+aIHtwQEBAAAAgiD/r25IQAEAAAAAAAAAAMCjASdCAAFkjjE+AAAAAElFTkSuQmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_result.png b/web/pgadmin/misc/static/explain/img/ex_result.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfd7b5904f9b304709ac6aab37e24dcc06d233c7
GIT binary patch
literal 1320
zcmV+@1=sqCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002L
z$<UU#(67SKeXh~4!_bAX(Osq0Zn@x(wb8-K&}gpOjmqed%IIXO*vQS$YP8;l)bMe>
z<bu!aVX4@+#?YR;&}^sCf579B&FEmJ)}GYufxh8%tI?>y&|Rq4YRc_yz2s%c>~px_
zSfSIA(d>o8<yxZCm(uK6qSJ1{=25}wjkVEryyK13?q#joj?L+~#?YVI@LHwSTcFW`
z#^!Uh-Idtykjm$8yW%;y=Uv3<cDmqo&Fy`{<dN0xWTeqv!RRu#=5@;IZp-X#u-cN-
z?P09iK)mRBz~p+$>VwejipJ$*z2zga<UG3PRl(?g&g_)c?q;&xVZi4cujDnj=ViCx
zTEpph#prd#=v=(zEVboDyXJq$=zz%RWX9`=(d|CD<!s68Q=QRq$?2xQ&}6XOaJJrj
zx!-lf=SjZld&TESz2|<->y*ywR-w~O!0LI->S@2`Yq#KKzve-^=xegvb++DV$m?jj
z<BZ7Wg3arCyWyqU@K3knEo+Lw00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0^dnQK~zY`?N#Yh+CUT!A%sLMsaGh-LM_OZl!8#apdwKLmBRyy7FwkURz=05
z^{DN?-f#qVLv*HJI{o0AVTQN6zxUqnUBR$t`S+s2aTTPh2@R$Ze@_Uhr6?^eX#=!w
zkidu+dYpM2@p6b79vK~LYpmgw(Ik4&kDGzz5roD1dV(B&W5e4B`=q1wly#1~+^jML
zcsL#Z*85J~R5W&<&+Tu6-BT{7A|X|P;{qx^*y6$v?}zk~X*x7LLn!g#XXoaFl0t_g
zAIP46<ARx1fJ7H#9?<#k*9uEIb&ut;cg5uICm&DDQMi=5<os$BHaEGpPRal*X-K6r
zalBmum3AqcgrAV>8=EE-ir5<7j^*+nDazi&@X2h!2ImzwkBpdc1Oj$;KNWK&&*v{K
zHc_Fp2T>6_XL(<6MG=Sv0DC5#0-#mAvC+Nq*9v^kIK2l8vdFM%Et8knvxnJo1c6|c
z&4c}KvKHW}Vy0H@L~i3sM*z8wP6~1|m|;789O!rkC-wRX3IT({=^1*&oHufd5<hTM
z>Vx<$E{y1rNF{wq{1pVhD#p6C2Rn5oh%7L0oleB1N|4Fp`jMQ=#pw)z990@QuA5do
z%zYPv$i2ZQ&zL%%$z6BtR(}-AOZ|1ZsiIV3#aX_aKKpI?Rtwj<S$$bw@^{6#TF;1K
zr{3RVX9kpAD%Y_0MwLA*6vA-iVkfW3ieG7@w;>~pUC1Wk{1EpWn@(i<$E=y+6>NkK
z&qpAySa4-uHvH^XzT03OH><a{o{ehAyMj8w$h-WttQ&NZpV96kV4My?L0ABbMGMDq
z*$yScYY)=&)1fH`v<WoU-7VJ2U7}9J&`8I$z8yYm`gKnZK)LWFQpDw+wQo`T$%m=d
zzQj%A4ia8s<V50kDpFI}g$%88-LN9{Um<<~F?K%Tp#n<Vv$^~qaIuPR*#L_H0A7VD
z3nOkq=7|r|v-~&m2P92<JOAsn6#xJLC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@u
zGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<
eG&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTXtOrt>n

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_result.svg b/web/pgadmin/misc/static/explain/img/ex_result.svg
new file mode 100644
index 0000000..51954a8
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_result.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_scan.png b/web/pgadmin/misc/static/explain/img/ex_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..396dfb4feabdd85e081bf8336304129058633c5c
GIT binary patch
literal 1320
zcmY+CX;4!K6or#E43U(&OiX7G0Tmtd5DXYmgR&F@acWZxiv&wwL>3hgrBoq~lr@it
zLJ^duAZWq5fw&ZkC=h`X77>I3W&snjFJvdI(x|^WbI&<*@7(X#z04qTpskIa4Gade
zJw!T4v82aGA-}XZR*;$vgTXU`4u=peEwhE|na4_cJ>&OMPHdC^GN?%#(geLygmo&i
zN3_g;wJ~hf>0VV*pQ>a+SI*I098_lwt84kXyOa8Y(V1&wnnwbI)t>@VyL>~#^s#Q`
z-ez%7n>4gTN@YnI!|M1RSrS`*eOyDod9RKoioG$+D)iJTAoVnDW}2o~Kn4Y*nFci?
z(4d5jN=PdLbt2HDg!CeCRtX6-W+}%-_OSp37f^A587`pV0tPWSt}w|tfRYQSEfXG~
z<pD-9I3_n5EVvjnE1@|h#C^#el^bWp;E2qi;{kdeVBi5}F*q#M8+pK-7&KX20?sR;
z1r;<X(GQCC^Ad1T#TXFlOnku12j=+L1qs+M(#`ULMG4q9t)1s%*%ntpODaaswDupN
zW=R4rs~Ft^&Eh0>X%f3E0nHYQkNqG4U%j7Mp2U7oF*>GZTDYnYlh_prI4U=^O{(k1
z<RH8(z;Xk43WXdD^Oa1*TAEd7Nz|V$N&2LK-gs{q?2D;G2mL}wJ?au}Fvk}SCEl-Z
zn!Q~=7IE=$y}e(yGh*kDXdI~~U{jVK(F*Yxuqnx~>1&_l=Y&sWTE`Nqyi*-+eA9Md
zK3zI6YurED-VlgxD2VgQq4T)o<{Uc52dBM4_ns4qT@<Dvt#*S#&6df$?z_JA^7Nd&
zsc>|3T=BIfA3pq|mf@Ygu#{_8Ub(bzb>X|nwg*!@UBbMkgoVpZr*Tf=$$yMQS7cHv
zJl|yI9uG%_?<;ufhl(nz-b3s9yRngm7!eBB**@c+iOV>**WUgtxir4qH!Q58sBY^X
z+t^lqCrqiOuN6j!FCm^Z-Pw}A4cEdyv#acY!)jFJxKh;d#_(duq1<!#u|_inUhRb7
zZuWs4?RmPM3Lb48C_72VNCv(-0Y~+p3u~M&y#z-hvq<OPO#~HPL|HeT-JBnDa|-Q?
z%<Wv4nUHu7scz!F?nr$`t8(+1oBKW|Pe5|!z>mbS2==EN8$unCzu)=|NpXJS+8k(y
zsf-o0Hs=vz(jedS7h1^)Nk6Egj#I}j#|fGRi^DszZaa6=0t<S~OgC$5;PxXZf91sE
zOWU(6NzTED<doV?9Qs3{X(;P@)9MkAuHxQkhHpV(D8foPn!Wy&AP<pDsLJyP%3oh&
z_cAYs?`!v|IYy!w<2r@-;@--2?03|0W>xir$OP}|H5oNdm)-MvQ!!rm(9c)!#r=_M
zBHea|)}|e>!%!27`lYDuS>)H4O$vGq?bkY|V%O_yDBIEKFR-P%ozXV=^u|^833oyO
zaZUL45Q3B6k&=}B<!$*W;*kcVXyLiC3y+ARgakaz>j~!U7+UU*(%Cey-DHB+7FHqH
zPj|}S-*ndrRToh|R<X8I9r-f-LcEN{x??i2OZ4-b(JqNaSpSxvc22t`6N*Rz>U)~O
z3XiIz)B{0V)^^ow^e^-{bxVdh`d7qN(nH;q3&j)u%b;lk?cGMHsP*lX<>$Ai5W`cV
zkEg^CP9(=z0_K5p$GN(DxZ>PHJa7d3E&?9!g4<2N;r5nqb@(5Um=t{~HtjQj+xH3h
axJ|%$eg^tVLmyfI*dZeMV8gextp5Q0yRj$$

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_scan.svg b/web/pgadmin/misc/static/explain/img/ex_scan.svg
new file mode 100644
index 0000000..a6faa4e
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_scan.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+61uL/QcEs4M494QKngAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_seek.png b/web/pgadmin/misc/static/explain/img/ex_seek.png
new file mode 100644
index 0000000000000000000000000000000000000000..130fbd8f53ff5c2c757c8173eb62a1b604555f34
GIT binary patch
literal 1326
zcmV+}1=0G6P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=yH~!hN)yyjTym@HuUhw@a(tm>$dan$M5O1^6ka$=CkqY#O&j+
zp~PP6<(}%}obT$j>EN30=(FhFndjV>?d7rN*p%$xt?c5j<kgbw;jZi7t?lNr@aV$t
z=D^3Qb@1lE>fEaD<G$n4kK)jc>Dj2{(~#oMjN#0S=-8+2;k;g#B*DnK#F|j0%w%+@
zIG(kKuGw+%>WA&)VD95x^XP-`;92wMfbif}@ZVJQ=6#RAQ?cH30002z+A8STDCyWG
z>eeFb)Es@XL#xzmv*LH^)gSEB81mgwaGgbAl`r+=dg#}t=hUR^+_~n_qTtDi-^YgD
z#D(V5q}{`W+`xk0$cWs(faK1gsoHLy(PX^vg!0`^^V>}K<977gNQ<j%pw(yY&=l^^
z5bw?o^V&-A%?rQBx1!f+zBxuV00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0_90WK~zY`?b6v(+E5e+U>k6&Tea1i1BihN$R<*($RcX1MKFRGK$JxVf+$KW
zhy}F%>%F-wiB95-Go4N!ybt#&-#z)Ab9#DS&%Zx!2<}MUVuX;%<?>#luYX`*kQjU`
zlMN9=eZ#{e#K`E_*gHa$$j8UW<wXC)#Ke2z!{p?ojOaytm>8X!l8q66sme$E&ysih
zt7&qYC!kO&l?v`wDPTseR?osLMNt~iXti1mw7()hkpiNeo10UDQm51DL2WP?=7BPr
zOlHtpEEel?M1_c&BSs1+lgXro1&hV@3{im|lX;^N5wpo;(ZZt5X0wYEg;J%O;ZuCZ
zDHa^yu-ffQqT~y3NAec0qSvcYm3fLnRm^78CC#GM>VRdZ)43x4V<?4zPhsh%aJt-X
zk2q25T8%Zf8@mWro6YM4r`x?MMbIodjcv`s?sd5kS@roO$Sh8w!6_Vq6e}Lj+B#1*
zzIONK8}5wc-65M>-;p8xoPq5?Ah6xNgTWoxp=o*tc0-}?Ubo6-WNU*YN&h}F5MZKA
z00PlyGyuU^EFJ{<;NXCUP$H2C^W-qHdF1!+CzB*1Q6`E>EC|7PJWeB$N`)YtNT)MA
zA&*Z`6(sm7u~>_s_vus$y&p~s$r0`tnG|LTK25Qg$z)ESkQa5#FPw|RYPB!JQhOF_
z*>zaY<#Kt*vn-njQ6gLqkFQY3Lhh_sJO}ooRAReiUr6j;FJ6yTv>w?);W7ugV)23n
zwp6+*OAwdK?L_bUd~1-sJUh$5dGP|1D*;IgiByVwxm@mMIr;J89XtMch<EH7&xM=C
zF6@TGtyxq~;j~(<-oQ<*R=ep`xsCif<aX?uGe9#bCe)sFre3dCpxS6Q@1S;ne_!j6
z-?x#6<TXBM$LJK(U5`q=-l)P|v)QaYCdl9&YgGv`2__7gbiIy9qk#$c@nh0%Tq{c_
zMWvPEu9M<XmiBo`$6n9>cm4nf0rwvzb1L2d001R)MObuXVRU6WV{&C-bY%cCFflMK
zF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGP
kFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f_kQ{fB*mh

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_seek.svg b/web/pgadmin/misc/static/explain/img/ex_seek.svg
new file mode 100644
index 0000000..22510c1
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_seek.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_setop.png b/web/pgadmin/misc/static/explain/img/ex_setop.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3a9b1983b5b47c96ae910e73ab4260ae9c28b73
GIT binary patch
literal 1143
zcmV--1c>{IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002E
zxzL`V(1?rCc6!ljZ_&-ct;w>B#-wM%nNGrzJd>5te}mbYo#C;w>b<`0$jI=)!tS)S
z>Yk$FfrZ(}s&>qySJ}LM<k6by-Kg#6v*XQ`qNLELspq@A?YOz?x47%Bu<78)j_l>D
z>*J&8;hW6M%aoVjwzuoBvgxw3>bkq^zQOKonbT;Cw%F6E=-!y;+?VRzrRLP5*SC1B
zujqQE*JQ2NWs|?=+Lq|snys$rs;uaQsoGkq)?dWwX2$7un78KHmFC!#<=2zt){@q>
zb)U22NtVh}z2-Hy=1RThb;#&boz14Or{vX=<kXSo)|jNH<fy9WTeRLOv*bFs<#o5*
zUyi-x(~#rRkKMwEAh6_t&FbUOj<dAt7_H;t&Wz#Ai>j^WVVTb!u;hfp;!=vh;meBO
z#*ekO>Wt0lK!(EL%8BLApgM`e53A#l)9m2Mh~LPG-^YjE#f9R_n%=~P-olRE!h_qr
zgxtV_;K!5Guy4}DpWelaCqTqQ00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0xn5JK~zY`?bK^i+CUTrVD7hwTp9u~3K&4efQ155Dpjm4f~c)k&`L{%-n1>)
z8Vm%m{&hD%k|mfKr!(y*&NpUw=DfQ(dlZUEP3k|EQl-{twHmcbIry#98;mBC(V*AK
z0c6TNZLwNy_D3iJkj{ZQUHHrlPB<~gy=WGlv$#E8ug7AuTIbPOJx2O`et#ek@cVEQ
z(~p4#WO2zm9bC3Ac_=qPq43IMANT)2Boc{6p2Xsg1qngSR4_5mlTc_inS9EFXf&02
zhC3aSREyMFdQBwH*EcfBO%4E&da?EL)fS!|$)-f@i8MsEbNQXZ?%w{O1t(s=IdEa{
z9UyO)4`C8M+9{SY$0rd1ygNNTC_~YdQ=T+TwsVE|#Zvym-aY_BQK?j7P#cx`;~Y#@
z*NbPxQepRaGbMm(wdRV8r%``OjF8VqUpSKa`fZqr1GRJF!XOaM_w)$K<<I)$$4{|E
z?Yv%V7zLtrAHsyXEd1ig#_vn9Mg5AVV<tJ`7D*pe`=HruLPQdb$`$xve5NY^u4%^j
zfaCL-0Jv_EZNmdDS!~f_Sppgv`A=CNwrBy-V&I993`~e0m^z<{1x0q?S(Y2i<{%R%
zy5-);Sjr4PEwt!%S>{IaFeOjS2A<dne{2A{WhN_mVq*{45?=X3(8Ek!_M&$)_K+p(
zBEhYTPJ<wC!A;%vn9e=xqQ8_-b8vJi#oJ7y!L+k7OG)t;t8Y@1`X_Y<dEniZp!V7a
z0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFU
zC3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ov
JPDHLkV1j^qN`L?W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_setop.svg b/web/pgadmin/misc/static/explain/img/ex_setop.svg
new file mode 100644
index 0000000..8c31bea
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_setop.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAABmdJREFU
+aIHtmE1vG8cZx38zy11yRYqUaIkS6RdZiuQCLQoorRMjLYoeerBPvRS5FfkAPRUo+imKfIkgt8Io
+0Est9AX1oQ2c2K1a10ERKXIkR6QkSxTfd8ndmemBL6JE6oVFDfTAP7BYcnZn9vnNf57Z2YGxxhpr
+rLHGGmussd6UxKgVHq2tm3wBDl4LisdQq7fLE3FIT0Nm1pDLwoP7q0Pbfvb4oQnLW6jKJqpewDSL
+7UCiaax4Fiu5TCS1xHd/+JORYrvyzY/W1s3GpuCwGON6LsZcxiURt4hPWAA0W5pqNeTgyOfVK4/0
+lM/KsukBPXv80LT2n2A3t5nM3iE+exMnkcRy4wAor06zdETjeI9q4QuC6ALO3L0rA13ppo8+Xjcv
+t13uLCfJzDpoDUppjOk0IsC2JZGIIOpIAPJ7TZ6/KLG44PGN5O+xyk+ZWblH4uZNAIz2AdCdM4CU
+MYSMoX1F43Wew40nqNRd7v34l5fGeeENTz/7zHzyqYOQSZZuJxACWoHBGIMUAilBypOz6JR1gRpN
+zW/+vUtQecXPH9SYSQuM9k8FD2CUOvmtDUIKpJkk9DTlVy8oVTX24vvcfeedc+OVF4F88qlDNDbF
+neVJWoGm4SmUMpd1DspAo6n5w9f7eDMRwqVFPvzzLCosnYIwSvUgjDYY3W5bh5pQlZFRj2t3Vkmn
+owQvf33hM88F+ejjdSNkktu34lSrIa2WgcsZevrr3iFf2YZgLo3MpjG5eT783Y0BgC5ED0Zp0Bqj
+FCrwCYNDphe/RSpuePLbX50bwVCQR2vtnLh9K061FqL01QEAtit1/nJUonltis+riq89xfT1ab60
+M/zx74lTAP0QmPZhemfQoSJslkjeWMEqP+XZ44dDYYaCbGwK3lqKozUEwQg2dPS3fIn0rRl2WlAL
+NaXAUNaCpVyKta3MSeCccaF3dAA7/1XoIW3N9MLbtPafDH3mAMijtXVzWIwxk47iN0e0AtipN9hU
+HsdujNd99Y88RSKTpJaa4k//TJ0COOvCKWd0+1CtBm76OnZze6grAyD5AlzPxQgCc6XEPquNSo3p
+mQQvGxq/r74H1ALNUi7F+t5Uu7AT7DAX2pCm55IOFVp5xGcWCctbA88dADl4LZiasmkFo7sBUKj5
+iIkYdTV4rawFqYTDZsVtJ/wwgI4LA8PMGEwYEEvNoSqbl4MUjyHuRlBDArmK9lSA78ZoneNmbCJK
+MZQXDiOj253YzaPudW0aOO40ql64HKRWB8ex0Hr0YWWMoVxt4gHeEEObGiKORcsSV3ehV9Z+vwjb
+6q3P+hUZOdoLJMTV13ndXu93oF1+dgoWdKy7sL0BRxJxaLUUUo68MMYYQ2oyCoA7ZGKPSghbCkcN
+H0YDENqA6V7XICSh5yGi6YG2BxxJT0PdC7EsOXKeCCGYt2xano9jOUPzxG80SUf0uXlw2oU+5wwI
+bEKvhhXPDrQ70G+ZWUOpFODYFy7DzlU2EcM0fOLW4LWUNJRrLZaT3vkQZ10wYFQb3LKj+NUCVnL5
+cpBcFnbzPrYtsKzRh9dKMsHxYY3FCUmsr74LJGzJVr7M6nwJHaqBZD5523feI+bEESElRts0ijtE
+UkuXgzy4vypm0j6HxSax6OiuLCTjLFsu057PbF/9a65F7aBColzie7mdk4C7AOZkidIF6J8EZMSl
+WTkgiC4M/dgaGunKsuHLrTpSgm2P5opRhu/kpijuHHLLgUREMmULUtKwlS9zf+mg09vDh1G/C+1y
+A9hgbMr55zhz94Y+dyjIg/urYnHB46udOpOJCNaIxiwk43z/2hTRoxLfnLS44Voc7x7zVnDAe/M7
+IOTpYTTEhe4sJqTAdtNU9zdRqbvnfvqeG+IHP10VRlfaMJMRHEeMtFXxg+wMtwOBvV9EF4oE23l+
+9vY/6IylHkR/MvcDAAjpYLtz1A42qLWiF37yXtjX773boumX+GKzimNLJlzrShOAJSDqSH6UzeAe
+hkS2XvKLd3ewIpMdNwaTuQvRBhBYziSWHae8+4JyXWAvvn/hM9/Y5sNuwedfn5dPbT6kct9mIn0D
+reuooIlWIUY1O8FLEDZSushIlGblgHL++f9m86Ff3e2gYilGdj5GZjbGZCLyX20HxWcWicYzRNwE
+VsQBQIUtQq+GXy3QKO68me2gs0D/jxt0Y4011lhjjTXWWGO9Of0HnjJ2RiXDWZIAAAAASUVORK5C
+YII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_sort.png b/web/pgadmin/misc/static/explain/img/ex_sort.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d46fd34beee098f997529bf0b7714c52232a2df
GIT binary patch
literal 1157
zcmY+Ce>l?#9LKkGD0JGb!<|bOQ+Me`PIBs@X131ubm*x$mvXaab;*yACb5J^<yUCU
z=?G6fnfz!u<i}*hkYy7xW3|nXjiOR_@wspHSATpy@8|t_KCjR7c|Ol4eIL!oZ1p#*
zQ7Dudg-rBA)cHdg8z6b}L>w1|LZ92`8{m!DQ{|nmX`=An**#5DzO7UKZPUMtXP6Il
z5fWW|pN=clXUJv+$_1mFs&#^Aj(62w4Qg7mCQzhhh$nO8bCLPQz+!<`3u9yCLG%bb
z?15U4$&kq)mBQ#~6BITAuO9LqLIsSB)IkpP_6i{rRI10&2s?LDwdh7Z8fJ@NCJm;!
zFd4t3ITfD?Q+k-tfKm+#6{x2Ho*jVkUqDF&&9OlR$ex0982Sc5B7<QifWi&bjP3!r
zaEsx0$?8cB`nK#iITTstVily{c^X+e5er>i<q%g4k%ho6fRJ1W5&*YE99$-0RE<W}
zX*u<}b08K&QxnwJgHU)0{BOZo5Q%sYYAS}-7f?P0MUzmdg?qD*qJqS6;3y$x4DvM)
zseqCx$W=qd3>dXTM?bXpLR$}rBv8e3)XBr99y8x4qpF)h&;TP34lIsGOB-m{TFz%8
zmqJJT(fm=Knjt1)R-Gi%qYx#33{uyzT`1HF4+_yMfZVAcm^py)vTU8m)18%`sKnhj
z*<uksYUi1eVOfJKPs>_|AHuUzvW>}&>XtFnD#12=5jSD-l}O2Xy6AEAsv_w~<-a~=
zR<a9*e9KC+;FgGmLH*CMbBgT*1FGqRFG(blkIk<5_s=(bzrrV0ONLT{JvvNXj&wR)
z>7XZDI#BoKl9GL|_MWroq;CqTDQRU3U&c7L*{b^OT#I7a=4*Z|__`WB7ft(h<#lmL
zwVU<juuHW=)EJqm7n3feC-+#%HCxw=uD|m{yYOyl`E(|KL();vVdHgcQY}`jeA$Kz
zd^GU|aTDfZM~Hc9Sy{U?g_H0ClY?C)4vFx^B<8H|$3?8%P+fzuPdEAthoRteGj^qv
zp2y-%Cb5#6EEi(d{<;x1@Ul9Xj(^`H-CwfeY>d+eFuzNvy3((g8#?Rm4)+uXRZ{0S
zek&Lj2&|vqTX>h8z4p+Yu5=*Wb*%X-%fX;6ZCeB97F{VJdvHeYIL!geO(qzY#fd~4
zgP|L%u1gYzNTmVY-#^*ix*}V#)w7h0IfJh~v<cg-uAtgBZSQdJ-+WSx^D$TOOB4TV
z&ougHZ<*gtEH4SK60NlsIlMIuW@DxNaj!)u>tp7<>2HN#d3gq@q^0Jug@L!_)33u3
zSD40j=<&?;yenlDmRu9KMFjKSyr<uxY0vOCm{D>(4e9y6VRvwCmgKfwx9qwK@bjUR
zis9q0OCHVF-XIovFSc|R*2(*Lx0j!tMXN^#U$e&f2a$)ekM=$o%{USr>i$P$C?Y6l
zf)l~f$=Q+M6yQv7ciHCd;_?IGCwBtDjc339GvLo~Mi}$-CxEcyBk-Zyo#6Tj=q}h_
PfdD9qH;q`i`*8CAb@5|F

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_sort.svg b/web/pgadmin/misc/static/explain/img/ex_sort.svg
new file mode 100644
index 0000000..0b11003
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_sort.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAABUlJREFU
+aIHtmFtsFGUUx3/TvXXb7lISaltoJEUTUwo8aB8kQDRBkYSUEFCJxCAoSPRBQ4yJaWKMMWmiBN7U
+VJsYjIo1Yox4a4UHAga5iBGBhoZws1bKym730u515vNhdndm25nZ2e0SY7L/ZHK+25xz/t/lfGcG
+qqiiiiqq+A8gGTW++tkJcfRGyraSh+52s2fLckNdVqikHadR49EbKZY/upTzE2m8DvDUSNQ5JLwO
+iXoHeB3gdUjUOiQW1bt4e+BsqRwqbseQCMD5iTTHA0n8LnUCWjw1AMxzg88p0eCQqHNKeKSSF+KO
+2DElsqTRBVB0phbUmaqwhUrZMexd6b/G8Z/UctSGMyv91zhVkvuVt2NIZO+5E/DGGojHi2v3euHN
+A+yz4ch0XHx4G590nCBCoOhYP028NLzNtL/GtCceh6kpmJpCWrRopoxG1WdysgwKGiIECDNe8HRJ
+3flyiDFuc4Mgo5Z6St/gslxYV5SSVdiBgoxCJl8uBvtEphMAyGRsv24XOacVMrrybIh4vfmiuHIF
+FAVx+bJaHxnRxtXWluNvHn6aCuoZkpwRh8iQLGivY46lHsPgLFatEnR22vfmwgWkY8dKvlC6/5ZE
+OmR/vGsuHGoV9m/2w1uP88im7SDfUhtEWutUtEimKGlqajs4fLAPjtl3KIelnyu8vBUS2clXRGG/
+ottRtbXw7qdwyHjuLaKWfAup6TVI/IZ01+uQPK/KxO9ILb0QP4Vj/h5I/Fo6Ax0SSZiMQ3QSJqeg
+fYFEJKbKcAzCMQhGIFYkOJoT0a8CgBzLtuemb1q9TChCC3yyQQA0ajNC8ahlRqDC0DucC5CyAkLM
+7DeCORHnAsTNHlXJ2GJgCjF2H4gk4q8lAIg/F4JrYVEnewd2iDOj39HVto6ezf0Fm9ztAsWj1S9d
+FQUyR8BbJDganpzhbyQxOl7UvzzamqFjvXE0Adi4t1U89dgmDgweLCCzdqcQ9y+2b+fsRfjxQ+M0
+2HBFdt8L73QPEeA6MPOWzT0yMi3cw+7hJwBt5g0Nedxsf3wrH335Mb0DO0TP5n7pB8cLsKkHIhF1
+0PRLN3fhZjIwdy5c2mcSsyy2VoDrjHOZNOoXXJoEO6T36BcvkiZBJtsuo33hnRn9jg0PrsHhVlNz
+d3Y/uGo9OFxOnG4Xu57ZRd/+Ps1QJALhMADSihWII0eQVq/W5OCgrQzClIiCXEAihziRfDlFnAxa
+dOtqW8fXvxivyM4tz1HjctK3v4+utnV8Rb/aIcvGjupXwwYsiGTyBGTSM/KdFPFsn7Yi0w9yDhv3
+tgo9iRnj9M6aEShCyJTIHJp1zsrIpDgo3lJ1ZldBJkUT7ZYGQF0pUxJeLzQ2AiBOnpwpcwQaGixt
+GM5gJXMgK1QypzNsbPhZiG87Yczg0k7o8iFFwHwPPDkMsRWl/4WopB3TFGUsCVeTsKVZystLcXi2
+ReJKVo7E4VrCTIM9VMqOea6lQyqXC2VnKZWVGWE8vlzMxo4pkYxNxfIsyVTKjukZ+aIDbqYKvxFS
+Bopb3bBtpPwzUik7huE3LdSDZRfpMlelknYMibxyGp5eDxO6v2bK9J8nQk2xG/0w8D302vcnj4EL
+y+juXksoZP5pIGfzL5/Px9DQEBtMxpmekYkoBILas/IBifHbmgwE4Z8QTETMNNhDKBRjXuf7BINB
+QxkOh2le9gHRqPW/SFMi+hW4A399Ko6i4ff/QAIscq1GvxZJhICjp9VKTurHzQb19fWMn3sewFL6
+fD5LPYZEzl4szZlSx+fQ3t7O4OBgSePhj/KMVVFFFVVUUYUF/gURB7EPhSihDgAAAABJRU5ErkJg
+gg==
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_subplan.png b/web/pgadmin/misc/static/explain/img/ex_subplan.png
new file mode 100644
index 0000000000000000000000000000000000000000..e13d7794e91fc0842702b0b34c1a9b61607dcf92
GIT binary patch
literal 1283
zcmV+e1^oJnP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002O
z!_bzQ(SL){ba>NdYtf>m<hHo#y}#_l#qYwz?#9UP$jI=?%J9p~@y*Wi&(QMG(el&N
z^Viq)tgh&<vFNF*=D4}*zQ66j!R@lN>5`V;n496cyz8c@(AnDc$Hlj+rEHy)MVE~(
zje!}GmC?n;b=BCB+u@bv?y2JJpyBGA(b0>*y>Y_8Zq(O`&Cz|**oW5HjmgV)&CP_q
zyl}CsSnBe)wXlcC#=6+uj;W$#*yDlM;eY7uxXQ|X(bI*zwrqx}WKxY8b*9HQh{34O
zXl={vL%r!uzvx%P>V?$sl-Tfk&g_TN?ycYR5Ub*n)$gj(YI2sLtk-VAk2du2$?)v9
z@9DJj?Z)lovFzcl@ae+p;+*H(mgLov>)x!v$huvZB(B<V=--*+(T?ies-Crnc&$CS
z>3-?jshY`Moz7yq?t<^;!0Fhh?&7_NyG?JLL&vIhi>qwE$G7O#rsmV5>)g5C#f9C%
zgW=4J+`xh5&Y;`Af8@=c4PV1400001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0=h{=K~zY`&6Md=+CUh_IT8h`QXz`eqXw2kLy;n)g4XJmdbG8avTB3Kp(v<$
z)p}e1_3p-?5Zu7nnNFV%o0)f?-@M1}Mx*)X((3dKs}!TxX;p}pR)f)GG8$jBwRd!w
z%`ZDUEmpIMqcggeuI?_I0%_~9**P5z(YJQL>Qz?8^x17DJq=;{x!wU~4cdV|%WIZ~
zu$^Xx5QDtK35c8yeo&)jELJMwVp*3&xINyX;bB4W`rcrMwbBt;yniI{)+-1?FfbY#
z<5dwki^CHb36sj4;gCDzP(xg7TRUOE_;`3?5(g$H!Vxb}L0qhf<BXVz0ua3qO*$hH
zHH5VoIL=<X3)B(ZiOt5=WWh)fsJ71<dhlKp3=LHn5NrmCg5jxYL1vmmz_>6q?R^LC
z+aGBz3y6${r7U>JZlAM>UNjEhXh=TF8Nd2bRuFzH;GTIO2?j%Mzk8N%fEdW$AV22w
zL@?s<g=QOEOV-}=<mI>0-wW-D+06_Mp*`e&BlRIa<9G3lpVBjaeSfJrI7i?75F?V;
zhba6A>5ka^!s61W*yk_H%U@$pB6W_~LOdQ{ip?)B&3~&x5><%OLCKO($?{#QQC=UB
z<Ren&Kr5@OYb)r7D5loYlSrKdt)|oIH6(6iGFem^Z!W)?&s9dPbVz|M5s8^hVH*(_
zo>gwAm@Mw(G_XV%g#!oHwzf7zl+6?hyND182=m3g{rzGd!crMCwGM*d8pU;Vu)TYD
zgz$MJ63OJ|-to!teyLO{m&;J$!1{@Z<P~R0Bq4++r@u~5@V88|)H+C(^;9Z*fR4`2
z&M%~1s%8btQZKa*97stu9$sEvoj**h=5i_>8k8**uFuf<<<-sY<GEC;&VjbCuSwwQ
z=Jy>1Q997kA*u1=_V(_Af+!tz^Qco}$N!-}nbr1E9_U2o0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1oA>oooOA

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_subplan.svg b/web/pgadmin/misc/static/explain/img/ex_subplan.svg
new file mode 100644
index 0000000..6ac82f9
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_subplan.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_tid_scan.png b/web/pgadmin/misc/static/explain/img/ex_tid_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a6063a1af031c626802911fdca598983aff7d52
GIT binary patch
literal 1074
zcmV-21kL-2P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002d
z;PI2T-@Mf8{{H{L*6o?Q;ibal>hbyT^!nN5^vBrl+3WSI#^#K%-OAhV<Lvi^t=n~>
z)~3be%jEIR<?+4P?Qx#e)#LN7$>)Ek+0f?l*6Q@d;qbxV@5$rvYnjrc!{ox+?t7-#
zwb1FX%;=%P<Hg|a#oq4t`u(HD<awmm>Ezqh$F9h;kiMjKx|(3PkxRstJB*5mfq{XC
zhlh@ij+T~|prD|vtgO7eyv@zc+}zyo@bKZ`;laPVtDlO=oI}p1PuR6w-oa?$%4zD;
zYVFo~;L^J5>E!0-=GocV($dn%$jHFJz_hfqs;a7)nVF-bqn?|W>)wU&>3;X;Y4PG!
z_2EYK-Z;soXy4!8*4Eb0(9p`t%EiUS?d|R1)xgxbiOHZ?)2mYZ=33Can9k15#>U3O
z!^66|x{;BQ>gwv<&a=<QxxT)>xVX5tx3}Kg*UYkn%e$bxy}jh*<eZ$G=;-L1o13t(
zu%DlwudlDArKN?1g_M+(;^{T(00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0qIFZK~zY`?UY$l+E5sVm1q!Z0s&d05EXH$OJzw&f>9JvV-~<xtzpr&mJ)3#
zt^4}dJ;CwH5a3Q{crP>OnfJ;0@};Mzn|G656V%rsV(N#@1DyaC>j%>yg4)_VZtpi^
z4$R~na=AT7>*IZ1AL@sFZhUwc9|;5piB`Y>L|~&3j^krablejP42`#Fv4k9O$mI$;
zF%N;*>=WT2Y&Irr9@rh6L@)yJTD0r+VpD$OYqp`G5qH=Twobo1f&7qh2|N*)`RMCt
z5>^5=+dBb3rrVuclg!S|FML{Dj6`FxSe&F1G{YvB49;`QE2-7B^m-<<5!u}0xots|
z<ZO=2OPoHy%R7<P^ye?Td;4GaGY3ktP$(AJk|>G{P073tp-|<+t)p+>BR|ra<Kx}6
zp9P^(`c;(}RaK?3e4@NYvh3lhmf7D8Y$NrD70Xgh{acr1xh^r;=Ey0}bN}Z4ADn5#
z`7_Z35rLQn@fV!3T@cu3sYXRZOw?(q(U+=l1rYG!LIqTS4wb1%)v`)c*EjlN#riFI
zORLpVMG%CdCdSn7?sHM{I%^=7WY6#Nxs&Jv80AAeN<QWi_cvX!_a&yZ5Yzei!HOA(
z>8=<EC*^g;nl5r9OG%cY6BHS5^LNqB+sG4UmhG=%BJ%?P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$g8msoqyPW_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_tid_scan.svg b/web/pgadmin/misc/static/explain/img/ex_tid_scan.svg
new file mode 100644
index 0000000..b11c048
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_tid_scan.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_unique.png b/web/pgadmin/misc/static/explain/img/ex_unique.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb70480be8a4f49369d9a974ce9461284d1a4654
GIT binary patch
literal 1238
zcmV;{1S$K8P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002Z
z#KgUKcgI3Ps~Q@~u&}o&Ddc5l(9X`ss&&DSHnvqK%tl7dN=mayB!;PEQjHmZrCYPX
zru6a2@a(tm>b0~uF5<tRp~YT>vPSFWp6cS9>EWB(wt%)SEA8a5a-%iq-I&LARP5lb
z-DPF(<-exPW%BF9vLz+pz?JIUs&=Y7>DZ_2+_~o1l-8qo)LdNR&yAk7hpyUjc&<L=
z)RExHh~LMC=Fy_RTtmV?KjF-Z=+~yT<a*t~g5%7duOT6OvqIz1kK4U}xG^!fML3PY
zQ|HvA<j$bIJ3G^6X1VKs(35S+RaLw1gT_uymdstaH8rt4A-G~H%wuG_M@zb8Ws;TA
zW^2)iL34~eXwA;?y}#|h!R^Ar?#0IN$jR`_%<--<Hq^DqyuItEspqP!=dQ2mv9s#7
zx9hx}zpN}W)z$R0W_!S=%C9#);NbVg#O%qp*3r@OwTrE~yX?Nd?W<8{vvZEQtk|zV
zN|H5LgGPX=ey69T;D1YtlFETR00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*y&TK~zY`&6N9F(oh)3hs+U!xs^aDm&vUZae{%Mz>t6d6NpNgVwgoYFIh(3
zy7;emwsT-WPoD8Z&*S?eXP>?A`{nFI5QC)~(-9-qn4TPw8K(^79c_;qX;>yRGhrkM
zmgDZ;!yxpe#VVK0K;5Sag0tJFa13pkb~v0)7iDnswA^I|Fc`!t6CSVG?Df&|5A2Mc
z!yy3hc-(%<1rabC4w>YwJny3XHeUcC4=@N!Y=Y67XxiA1bfc8ZIN0SO&+|UgKRXvh
zUD&C47Dj2YfB}5L&;mV(@PZ&Ly2G|eBm_^E<{w2_CCX`sM~Fq1<B1`}C&Xea%&Iq;
zOraAtn&uS5sSu)=8Af8U2<cyQC6^Jh6isJYB}$L*I2jkk+%OUd1{d?;LMoL)BePGk
zEL;+-QB0IFnW0^bN?v7?8Vr}lB^C%K$n~|kVljbU#y!RTm7(NDzETiDp<uWw4oSsV
zYGtpCl;9o9;t`QtCtYGLmn$hZIa_^(yU7b1^-3X8DwULAxGEqp-;0rcUaP<81*7Gw
zBy{A<kv%cHxiuOAn5}W8+b#20LxQz!W_B9d5|f&{uUdPr_um+(w~{uGN$q#<KYaXj
zKp75?ByAo;$g$acEF)SxhLA%)^%){~kHK9IDF4aP7lhzE1{2$1^xKlu=tsIy%GLP0
z%U9sB({2~!v2K*jZ{34|T{`aYNox=7f;@(n{C?Iu7(mXM)eH3mdJKaAk6o%=pvP`N
zn!Bw|K76H)j6C-5UF2YX)XG2AV35adO6;r+Ja($S4C^s4@~?d5s&8bh#{hC(D_^Jx
z-eXexH}KeH7t!J|X}^=NZ1#fD;;{zEtA5=J<`#PF|I{BM#JXLtE~^v(001R)MObuX
zVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=
zI&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f(A@n
A@Bjb+

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unique.svg b/web/pgadmin/misc/static/explain/img/ex_unique.svg
new file mode 100644
index 0000000..8e43bba
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_unique.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_unknown.png b/web/pgadmin/misc/static/explain/img/ex_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5d54f5c7b253978b96993ea260828a144395095
GIT binary patch
literal 1088
zcmV-G1i$-<P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-s00025
z!qAqu(2ljybgR*YvC*c#(8$fuxW>>^ozY{g+H$$zaJ}Pq#prd!=Xk~Eea7c~!sL0n
z;BT|sS)tRx%FtS*)o#A!TgB^2zUV@{=s>&YKf2~ayXQu|=uW=pS;FaL#_4Uz>V?ql
zgTmx#uG*ix&}^sCZn)r0!09iw<{+@-AFtyivE(VU<S(@3G`8hBxaB~(<wd*ZOTFe$
zzUOPl=~<%FSE18r!{{2W;}xvqRlw(3!RL9*>ypmuTB6fvyyGgg<`JvnU&H8?(CcKb
z+6}7WW5noy&+LiA<Yc7LR>0^9sp2xV<VL*aXU6Do$?BHZ?`E*vFt_Gl#_Mpx=3d3>
zQNZYfzTthY(P6#i1*qX$pwV}|;(pHSdCBU($<S58=^e1-60GB4s@Sl@&}p>ZUZ&P@
z%j|o{>5S9ub++Dj%<5;W*ml6=ipuD1!|15M&|t;sdd}^2y5K>(=xE66YQW}z$LM~@
z=wip~XUObp%Itj4?KV3{{{R300d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U
z00H_*L_t(Y$L-VoSJF@z2k>#Zm$&&66HySyHefJJgw$z(fRdI186mP%OejOc)GU0-
z>Thp&cTUGnWA_LBjqi^;_jC5#=Xowg_45ER0W(yCfW=C5-iGWBdSGy9_=%GlaUqi-
z7)9=}agTSxH_7@rUI+w3XtcKw!x1Su^>jKm6Hh#wotx+6KyX3qS=8e5Xfl=jOVUex
zCY$SJ(DF(?f1kYIGpjlM+Q3>O|MFk*N?zYUx};D{l{E6&w>hhkH|4iEy=IG*tr8}&
ziR$)Hxu#$u2i^f4va_4qyCc)=fD6L<KCfW%UbnKkRKg@awqsLAR5&PNlF7pVi-x=T
zA@#A_G0)+r?gu!`8IsoWZc$>r-g5o!YDdRuOx8EdI)ya=f(84cqtfc00Its*zP`{t
zvGm*-tI5)xw)yaY^JDU5HB1@DiXyUq6VF9xpNhu9hUnU)HhsCxm`TK0GodYv9AJ>K
zpwO8V$2+zpw@(TFp)QUR#cP=s&KF1o75*B>n>;TB;RT6Mw;+beHz%@@2+C4n@q3xe
zxX1(rB0fx-PLf_>Qk9e@SL44kfRHrYjx{x*Q0S;ZNGK6#A=nd5v?4T3cRC&Bgw()6
z&n4?oZ*z4HNy+sL?wE4ZCuHQu?RfR}=da`6SyFNV?OMnlOFxg0KS=w20#>Z+^Z)<=
zC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!q
zSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMn
GLSTY1nJ@bQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unknown.svg b/web/pgadmin/misc/static/explain/img/ex_unknown.svg
new file mode 100644
index 0000000..09268af
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_unknown.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_update.png b/web/pgadmin/misc/static/explain/img/ex_update.png
new file mode 100644
index 0000000000000000000000000000000000000000..a45c53f83a0593704855bb8488871e8e8c9be701
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003#P)t-s00016
zTU$1W!3nA2a+aZzl9IuXHZry4b*9Hbyy(lKSW3X@*Qid%s&!q&>iw+&W5()h$LMIt
z>~72Kb<OPByMEiff7`u@<Gpj-zJT1nfd9QPd(rRQ!GeU*?%l(Kgwyce#Dw0(h2F-7
zjMVPm$A{m@h>_Lq;K_;M%!-%U@#D*$;?9iY%%0@Up5oAr<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp{Fb>D#L7+PCQ5nd;rD?A*BN;F{~+tnA&o>));H-n#7It?J{P?cclX;jZoAyXxef
z?BcKO;k@kQu<hls@8rJi=CbbQv+w1=@aDkp=(F$XweaY|@ae+v?6&di#PaRM^X<m-
z@W|M;qjdlP00DGTPE!Ct=GbNc000SaNLh0L01FcU01FcV0GgZ_0007=Nkl<ZNXPAy
z-B#LA5QQ<eL#ah;5tS;UMbwA_5<v)|fIxsm0YgCuG)2mv@BaclIp+XDLzfq>)m}Ik
z`zl|u_nwKx@;3iqJ}~+$R5OHe*le~9W_M+Eb)VV);&7ZYr@MQ57tF=s@$q-Y6tOKY
zFWZ<Eq^rzltJUgYHW0qY9ImfBj+s~b$~)|Np_(D^I2a5bC)(@vMljMeZ3shERr^4m
z0k9j9!To)$N40l*d;0*Il+U+8O{EeF5Mun>Zl6PH7^x9N(m>1S^C~n`i3##!Jnm*;
zhV*LqVXP05gphXrI;BEA5sz0XlFizcG0d`H|I-vdaf)Ui`bxFjrCB!Z-`K&F`2_lG
zW8HeL^u3$4uQLXA^nuZXrj20OZmUD+*A=A;U0IeplNZl1u(P(dwqr=q)KgrQK@JCl
z;>^E+p@=3)q}Ws)l=#)94014dCK_tO3=TIzaIq>bwt5*3%TPQ!V{>4cXY2w@vpi4H
z5p%f~7?%#4`cmO#jZGLtp*Uy@&3d0|w^~NK=oCe<1FcLZ-GKRfgiQARl7;%8Pr;%T
znUf=*08efhU<o1hL;@WKQ8^qA7C~fLHUsG+pZ6(3LA}y4RZ?t@Jvm83((Co|<_PjH
z|Dvj=XrUD8WCI!k&sR*5kA}v!pSNfJ6<IEJ=yr%Ul7estl~^=#V{VN2CpSP8r8wXr
z>T$a{bA%j*5fQuxo)|>jZr1{YRBA-{1CJ*HoZICJnIoNCF4LOJQi(<*B`9&BQ0dhg
zYo(KD3q<dDK30Jnj^of*LV-ZR+!#3wqPz#EaOo)mPN(zS97+20!yp>N#m*4FIU=Af
z#HdEyIudfZoB=prjNI`t<e#W9MZxd)7yi=N)=U1%xA~vu4`}JQAOw$Lx&QzG07*qo
IM6N<$g4z^C1ONa4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_update.svg b/web/pgadmin/misc/static/explain/img/ex_update.svg
new file mode 100644
index 0000000..bbea29f
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_update.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+A0/ouJeMuOzfAAAAAElFTkSuQmCC
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_values_scan.png b/web/pgadmin/misc/static/explain/img/ex_values_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..15b5ab4079cb8e2c015b4f5f10a3dbf8aa6410d3
GIT binary patch
literal 913
zcmV;C18)3@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-siMiW0
zh{12o?m@ljMZW2E&hL59?@+?(R>SIp)9{Ja@MXyCYRT+x%IuWb@tNB3d(Q2G(e0$&
z@u=SNj@0gw*6*+3^985jNWbYDuH#$9>o~XNP`>9?z~@=P=Z4YkYscts$mx~W?|Po8
zrNQK8meIkFHu36*?c-qX<6!dYhx6)(@8e(Z<X`jahxF@*@Z?|d<X`pchw|iK_Unh?
z@Av20E9lxQ=-Mjk+bZnaD(>4V@7pT#<zLt8_37Fw>)R^r+bZweD)8JY^yOdH>GbpD
zU-ji*_v?rF>xbLDiPY)z_T^vG==1U1D)QVa^V}-;<zLa~^7Py)_~l>q+$#9yU-|2Y
z`s|0!=JL+v^7-ap`|O6y<?;31D)-$g_}wY`=3mR?@%G&+`{rND<M8?2DahmS`Rs@M
z?1snV@W$fr{N`W%?1shR@B7{<{N5@3=3n>SDf-<h{^no8;O_n2DgNw+z~An<+wA_{
zDgWkQ)amoS-tDc{=`>ap0ssI20d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_
z00FH@L_t(Y$L-b0a)Lk*24Ek?nFomRW_gGUK~X^z7hFIQqoPPyTuOQNU}g{;#Yj~u
zhm?Jq%hywM|1~5M&-$-bf~P9QA@C)YD!#&4B$dhJ^6-HRlK><UDZb@$)Hi|e6h+aI
z7lL#eAd=5jtC&#LT8)Xk5M;BM1g#-eV_6OzX@=ukmluLU0gw}e6wC6MaC~zhM3C?#
zXpU=qVA33nA0Xiih4FYO5~N%P_eS3qM6pMZN(IEs1gO_R%)p!pq$!4J!B~W0dA{*T
zU_hti6P|CPal)1$2<_;=bi0@Y8wgGI=I8hOm;|k%FdTl$=-8HJWkw8nG`bFqIFuv>
z5m{ALg&>p3bzpku)=*JRlO)sW-M}cOX=((S&+i6irfAxdAd5xpz^qoW1LG6e7Dc)D
zP+)8u6H)Rf`%_@fM3(#wc;Boj%jm#tw?0u-yn)kXbuBtDXA-pbh^`yxxHJr711}%G
z-3~o|;r(g)dX0&(b{s2Az~Sh+#{;pMQ)_F3i9?ViuwB>2PwdUWSk8WbK|JGSEO{p|
n8_Tjb@b)wQYk!?*{c(K(wSJimSdJ6(00000NkvXXu0mjfih%ky

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_values_scan.svg b/web/pgadmin/misc/static/explain/img/ex_values_scan.svg
new file mode 100644
index 0000000..5328679
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_values_scan.svg
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABHNCSVQICAgIfAhkiAAAA1tJREFU
+aIHtms9r02Acxj9vOuKv48BDmAgObHCQkziHwq4iCGtzUWgv/gH+A549eN0fsIsDT9llIHgcuqnb
+rTDWKRsMRw/C8OTaZW3jIWubzOTNmzXd6ugDLTR98/Z98uT7fZ68VLz9+IPLAO2iF5AVRkSGDSMi
+w4YRkWHDpSEy1u8ExbElr1Zx/jluWDZLzaLod35V9E2kVnG4XyrQOqoCIIDcFZONRQfuFfudXhl9
+EwFoHVVp1TcAEMJ/RWGQ6kmJTFy95q1sHgAwOzXOfqMe+WPi5E0EP0egVnF4UCrQdqvdYznd5FsG
+6kmJrGwe8PrFXQDevP/O5OT1yHHihITKJW27VZr1jUTSadVLvLUOj9uJi8vpZmhBOd0EtmLHB0nL
+1Js+Ua8zRtNNvsaol0jE8zzp94Zls754+sptYVg2NKPPSSLR/W23SrujnqT2IINiX2oWReT9HUMi
+p5td8xKAuJKsXrD+4pBJ11KFYdl+YYcQr16wAybVYCKR5ApRR1r1NN0ECNRInjj1+q6RQcGwbL68
+U1cvWZEEIoMyubTqZRJRpksFWoE2mZO0yUEhk2JvB9sk8ZH6wiIKQEuxRELeELOkWsXhYamAd9yL
+KJpu+rUwyIgCgAKRbmtU6Pfesa9e57y46TOPKG0FJppuhlxXU4goPjxpRJkpF8DtPR4IPc9qjHoK
+7Vf+vWHZfmGHIDE5fBXEyQWSKuhW8err/jqQP8727SPpTS4fqichMbke6WScq7Mbls1aCpODMIm+
+IkqWvp5WPV+tQEf8XyPKapYRJYnHpYooM+UC3qk2uZaByaWBgrMn31reqTaZixl3oRFFBaptslZx
+eFSew3O3u8c0Pc/n84go7bNkLQk8d7urnhDnGFFUoOn50IJkT3IdBB9ho1CrODwuz0FAPaHn+ZQm
+ogQ35o6aPUvc2Tn0Tm/UpW2TquESAHcbr7F+dmdf2TzglX2HX39cfjf81Tx/MsHNGzrzzm5oo+4s
+Jhe6DRPUU+0AkURmp8aZd3YpPb3VG6gJ5p3dztap4vRhGJbtF3YIKgEzGZFE9ht1MTs17i1++MnL
+Z7cBWFjek+7/qiCtegTU63xOHVE6ZBaW9wD5JvYgYFi2X9ghSNQb/fNhyDAiMmwYERk2jIgMG/4C
+m6uK9JrcxxgAAAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f858be1cee3f5d8f2b67f55d730e6d3f43e9b42
GIT binary patch
literal 2021
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_window_aggregate.svg b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.svg
new file mode 100644
index 0000000..096f296
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png b/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..b51e0b3f4c61ba166b69bd7f32cb23dc8428429e
GIT binary patch
literal 1965
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_worktable_scan.svg b/web/pgadmin/misc/static/explain/img/ex_worktable_scan.svg
new file mode 100644
index 0000000..38f1f9c
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/ex_worktable_scan.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="50"
+   height="50"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,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
+"
+     x="0"
+     y="0"
+     width="50"
+     height="50"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/exclude.png b/web/pgadmin/misc/static/explain/img/exclude.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd62eef6410d9315857d2e6d246770e8b65b8f47
GIT binary patch
literal 725
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47y|-)LR^8|wi}<ymTvVw_QrY1
zLC3?d^*UDhFWLA1|9`)wM`KREbvXQ5A-=(6%9fR9ZhU_6^4^pwS28lLWMw^=HS5>=
z_isLZP1<@%wQ`Qjk=K%*nNgG09JqAnY+~Zm($d$dsZT2^E~KQ~e)?Lsd6{J1L`ko#
z8QYH^XzhAZSorVPuP+-md|tBT-`~HFYif>6nkJPuNuqp?YIOD56Ib^8`rV6+d^K<0
zzkmPUtXQ!pGFqZ!hGg3swe*g4XK!wIaz5qe_HfFSrwbQu^AC`0S|K@Qk7Uk-*~hPM
zEhsq<82I=1?;BI6UhV1m_vg>S@bIm*jgk|0NG?2Of9R!T^@7Cpr%znEwc5~Vm%sly
zUEQ5NKC5l*c3rqFx8Q{2nrn7@9?A4<w%>5&&c`3`Z{FOOoV;9Hdxfs<!JM4Ox9?6m
zenWfBW$A4XY?hzYUUH^<`Bv54PXhWDq@H_cwegx-ao6F7A05CjWh@Eu3ubV5b|VeM
zN%D4g;b^-zwF=1LEbxdd2GSm2>~=ES4#-&V>Eak7aXC5R07H*Y2Gbdx45l?XZ+LiQ
zWMmY~)Ws(}c=qt=V{riyAu&Nw;pq&V9$ucOPn<e=Qd>hyv$&ZhB;@K9Q<JS*N=v?e
zImpD;=5|bN*|M}_&%~xBFluK@M_UI6S4XqEt8Zx7+`W4C?)K%=xA(7?_c&m$V4z{4
zVq&6Wqh!RTA|NX)Ek1w3j45*_&6>t1bmBw`i*--4qPDWM%7n?><t2V{=CjN{U1V)w
zV3-jtUHUai{xs0(swJ)wB`Jv|saDBFsX&Us$iUE0*8qr2Lkumf3@ojTjkFEStPBi}
hcbK)IXvob^$xN%ntzp~MJ}aOG22WQ%mvv4FO#q89Jv#sZ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exclude.svg b/web/pgadmin/misc/static/explain/img/exclude.svg
new file mode 100644
index 0000000..7dc1378
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/exclude.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="16"
+   height="16"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAghJREFU
+OI2tk89Lk3Ecx1/fbY+pe/bjwUfWUpxEDekgCB0iKDoEQUFdFBKDpEP+AaFlFy8Z1MGbCB7qolhG
+XUJvhTCLyogOw4mw2cgwmzN63B73PHPPt1Nzy9vqdf6+P98Xbz4f+B/cXzHk0PScrCXrAXi/bZOT
+ak2fuwCMkiSzvsmVqddlC1nB36HL02/ktaWsLBtIwEgs8009ydOPK/Jiq8ba+DhmPA4uF18mJ+WR
+nh4Mdx1DsSSxxQ9EItF9AwDrxyZRp0CozsPq8B0OpdOESiVCxSJKKsXy4CA/d0yWPsXJ5fPsyYoO
+ABCCs8db4OULQsEg4dFRzPl5ZC5HU28vzsgI6efPyGb2kA3e6hIBVE2jWfMh3y1iq162pqbQ+/sB
+2J6dpZhMUkiu4bScQQSCBwcI1YdQFAr5PFtWAbdplh9ZlsVWNovd0IjwB8G/P6DcwY5ST9Z2cDq7
+ML1eWgcGWJ2YIDE2Rrivj11NQ3Z2QSAIPn+1gZQOBIK8/Z5j4MZNvl6/yqvubnKpFADrCwsYvwwC
+w/dQPmcpetVqA2lbuLUmUqIBPdLG+SczeHQdy3Gwgfr2dk49eszKbgmf6sXV2Fht4GQ2CBwOc6Gj
+jWOBevFnkSqXRwghbs/MSQ0dW9GqDfSNBKdPHOVuh19UBioBeNB7SegbCZpNg38ieuuhPBfL1HR8
+B/gNAqjSuTgH4AQAAAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="16"
+     height="16"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/extension-sm.png b/web/pgadmin/misc/static/explain/img/extension-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..432d2f44231c1f88e787091060efc5125d80ef64
GIT binary patch
literal 423
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}QGic~E0BJDv+cp5$Qv6qzrVk7
zW3&9@vzBik`G5Ho|K-)uTicc2-1GkNGyU0x%oo?3fBedPak1gnHlr`^;(z|izO~Kb
z%lm|1zjI$+bN~H2|Mm{mcMpR9{3*J*S>f3^`;Si}e|^pQ`={jETDdE$#Qyy)dv><$
z|KAGswyocQwlbCk`2{mLJiCzw<Zu>vL>2>S4={E+nQaGT<aoL`hDcoQ?f2wsP!M2o
z7l{eDQ+~(c|9@BE84^1Kx361bY|Bu`qQb-J62DBxZ_<p5BE`pwna}6wxpa1%>e!Vz
zahdU=*X&ESFOpJE*&;e)`qd3*DpaT5u9JDLS%3P((k)@<ZbaLzV+h!Of4=?`KK92q
z=3fE@l4^--L`h0wNvc(HQ7VvPFfuSS&^0vDH82b@GO#i+wlXo*HZZj^FqrpFZxxD$
o-29Zxv`UBu152<5plTB<12c$*Q`1A&05vdpy85}Sb4q9e01}k2!2kdN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension-sm.svg b/web/pgadmin/misc/static/explain/img/extension-sm.svg
new file mode 100644
index 0000000..3871e60
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/extension-sm.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="16"
+   height="16"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAU5JREFU
+OI3FkU1LAlEUhp8r0aeIFg2WAwalRURFmxZRa1u3ate2PxAtW/U7WhW4rEWu27UYkHRhWmCgM+hQ
+SjR+ordNjFrDREH0wtkc7nnOe94L/y3h1DTvT6VVStGoJAEYDawzt3Mh6pW0HAusCFdA5iosJ2Zn
+GZ9WGPH7AWhWq9TMMpauo26e41W2hCMgez0vJxeX8IZUR7tvxQIv9xmiu49fAXryUHqGs/gXoq43
+Vx+y0N4muHYiADw2Xb/FG1QReFzLG1R5LSZs4FCPPQxCIIQHV4nB2OzXvlAMyzC+dWAZBr5QrMfr
+p+USETm1tIpPDTsufy088Zy5IxLL2XMDfmc2zjBTGuU7Datk0G226DZb9nYzpQ0Mf8qAvv/Nk7/Z
+l0Xzkm6nwfLeAQDtes09Hyfp2pFMxxUpO8cyHVfkzwkfSscVqWtHvwf8md4BuVJ12ly1hjUAAAAA
+SUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="16"
+     height="16"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/extension.png b/web/pgadmin/misc/static/explain/img/extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c533347883fc1dec73bcb21b32fc54e3c31732
GIT binary patch
literal 996
zcmV<A0~`E_P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%`cO<%MQ2zLf_6I6&9|zdd99>-@a*2m!Junh7N)|bRf|-bx|zVboSKha_w(lG
z;mc-L4XVVcQjb)R!IoN>UTm6cdTlP+)48aeY_F(+!qvh)fIq$I$ak@QM1Demb2p-y
zZNIpg%*U?Z+|0eUmR?Q)@#@yLtA3u9XFhv8mdv0tb~C!Ok)4xckc3Wsa4?*aUD?pH
z@afX-<;s|gP-<HecxWLITMn+{ye)Dq{QLL&`11Mo?Dg>A^X%E{;lk+My5`xk<JGC)
z(V?J^Pa0qr9%URUZYXJ}aH`|EXsd7=V;MkvKHbcisFznYY9*e@qsi^kq0p+c;k~!#
z!?5MPqv5li->#6?p?1NAale3n)0NuDkgb|nMQ$vEwur;)&X(P%klLe(*qwsbn0(Ze
z&%lqBg+*3S1ZY?dnutTImr{GNfpyZ2etkM9XCNqTCtr9wihVOAV;iN_u6D(TK#xcf
zTn`v!7(bOtw4`b=XCQ>Lht=`it<|(#mtSF?W<QQcm$;T>m1BZ;G0*SUx8%WGt7&4B
zV3D+tb8RIwgF3Luu#bN^7G4v==*yVRp-_@kjmeoSdoaG!zSzWv!K`UHUKC!MVuZSj
zaIAJVdpAyF9vxyClh&g+jY8GGe$u;klYuouT@*iVD?w)>Y;7EsekVC?DRR1gZ_tQZ
zyKv99ZOyY~z@}84hBK{_JHn(!l6fLVbTnhFZfMSfV$6Lul0qhGC2*Z?NP<TvhBL~q
zUB|0c#ivccpg)Ro98G#WT*`P>$a6}(W;2>aI;l}Wz+t|fIeljlScFSfreaXWZ%f2#
zMZ#t~zg?k^OOAUphI1*4cr3V}Qns8&igY1@Y#D@Y7H?b$T8UCtsbn_2S%i2!rk!)h
z!>EaMCcvamyR?(W!KK8$qF<3+9BCaAUJ)8<9X6LmEss1DV->ZTL3e2=8EP9)hfaum
zLy>wXERsEdsDEo(3zmE$ex`jsyImG&7=LOOfol~TcO*%INEmJ%7i}B|YRn1%0004W
zQchC<K<3zH00001VoOIv0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW
z0~-xG4!&HBF8}}lR!KxbR2b7^U?3j2xS;VvLXbtwTz~>0W=QhVA)yH_FkL{}1;huk
zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<<OZkC{A-h<6}A<CjbDaP8qDe
SR_4e60000<MNUMnLSTaX-^L37

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension.svg b/web/pgadmin/misc/static/explain/img/extension.svg
new file mode 100644
index 0000000..6ab57bf
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/extension.svg
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="16"
+   height="16"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAPJJREFU
+OI2lU6EOwjAUvDK2NCg+iB9ADI2ZBgUSPCwEgV0QOMxISEio5R/4DAyubCyjoRgGW7suhD35+u7e
+3bUlg34bdapRC/0rAaOOZNSRfxEw6sjADxH4IcpIjAQ766ltDfwQO+tZ6DVNBC2bYrs8gosYXETg
+IsJ8NkLLpmA2pJukRFOgelXBbpKSDGhUcFidcEmusgt8wPwRFWbyJFoGNxG/t3/B/N0rK02BN+nB
+TVLCqCPH08XHhmo1U1EgUP3lM+gMPbD1VrtGYnrK+TA7Qw+RuAMAzpt9dQZ5NdmgCVypoEyNCgYq
+HpKqxnRW+ze+AO9gktborNU6AAAAAElFTkSuQmCC
+"
+     x="0"
+     y="0"
+     width="16"
+     height="16"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/extensions.png b/web/pgadmin/misc/static/explain/img/extensions.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed7ca97a33ef595f448b8621168d531f86d51d5
GIT binary patch
literal 1017
zcmV<V0|xwwP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%_fSk!MS^xZ(#^N3p?R&Od+_Yu$ibj%T^6RorB#bmn!1_5x}2JiUH9|m=i$p{
zRt>7esZx(rkHMB&m|kp}YkF-i+S9qHoNTYCfx^|oK7c>H>Bx7neMEjjfO9vZnQgzg
znaszo-`vc-ww7K_0rBeAwyS=gm1jPCJeJI$Gj=n&vyq*XV~~VSeQ+?GkzLu)v+(KC
z?&Zpui%@D?5_o7K5L*te<Gd|$E&TiU`}p$t_U!fW;PdR+>*2!a-MZ%4vg6gM;L)L=
zk53w479M3BDQ+lfsBo&|xoE3!8)F$jd_LXGn5dUmHEJcE$)m~b(xK3*v*EqB=fkk&
zzN6u@o!_pI*P(X7g>k=tf76xP$dIj?SVe9ugSLpn>&}+lsF2#DiP)Wj)|h<Mlh449
zm4!uCPy}dL4Vs8UtCv!Hv4M5cjedPPC}$ujZ6{xNJBocXBx4(;)vk8Mhd_@=5nK-#
zWf(t|O0=YEF=rrzvxn93+^yBLT$f*Ao@PIeN0+#kWR+urcQMcJ*SF-sT&rnflVFjw
zkaKM%G=n;@$*_-qITl_M!syGG&7n||RE^1*D|;}$)4tfmhrz6AIbIZAnqq{yi*T%V
zHhVWtV;&u18I#tdIE_NpzkbrYc9VfMLtPX<ZYx1&B5Z9Om3}8VZ7FiPes9o-TDx%1
zw{6X{Wx%FXorW{5k~_krMv{3VMsze|t!`+}gJR5mHj+XnY9(-;Zb*VhCx$c1uU*Hh
zRmG=Gz@R^havV*1JY33nR>*Toyk;|+MLMZbK)_+XojHAH5m<yvR;FT5#&1i+YDL0k
zJHK6_k4uhwGlp|1i+C)!pi;J+M~ZYIf@~RtZ5D4_30jF#R;gq*y;+2KJ*J&=$it|K
zbtb^1PrI~}#=)h;zM@}|T^wm05?&D+Y8^J0MJ<m!6k`>&nL&4HC>d%SP=`*4d_$3X
zCoGaZfT(|KS__tZB7UZQKD%8OXc&KL7J+LO8+Rm0f=C!{9T#mJ7UDFE00001bW%=J
z06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru+XEXB
z6Cp(Rbcp}}0B%V{K~xyiU68R2z#t3+nS(tt!3~hY^vGg42FL=`EENNm0{M!6-y1;)
zQxFazvL_cK*b<_okFg0d8Hc#V=Eh`)h+yZmplehnlNZEdtgQ@hX0CNeIWEpxw!oyN
ndc~tsp9TlidOzOC&-)*|KH)3pD2ngr00000NkvXXu0mjf<KNUz

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extensions.svg b/web/pgadmin/misc/static/explain/img/extensions.svg
new file mode 100644
index 0000000..22a8206
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/extensions.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="16"
+   height="16"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVFJREFU
+OI2NkzFLw0AUx3+RFEo/QAZdO/oJpJ9BOhakUHAJBS04tMUqghqKgw4OIUO3LhkKgrPfwcWxqwjF
+7ZooeBCX3HlJrsU3vTvu/97v/+7OuR4eYItotcwAfK/pWA/ksbNJHAYxYRDrQv8q8PD+ltkE24q4
+5qJRqzO/e0bIFCETRlfHhEEMQH/SyWx23LJXIVN6wzaNWr1wMAxi+pNOZS4uwNP9Cx/fn1kICJlU
+SIRMuL05tdK4AGuZ0p90ALi4fESRCJlwcn5kpekN29nZ3r7j7PY8PaCcJC/w19lGoy0olGi1zNYy
+VYgADMbTAo0Sm5rCLXRHhzpXNGEQa7H4SQpWfK/puOZC5SaNKRb5nhluZcdCMxhPtQ0zKhbKNOqN
+mDNo+V2iaK4H72z6TGYXlbf8Lon8AuB1tsD3mo71M5VpFFFZDFtmYCsUzRaVp/wLVVvmqzUDDg4A
+AAAASUVORK5CYII=
+"
+     x="0"
+     y="0"
+     width="16"
+     height="16"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/exttable-sm.png b/web/pgadmin/misc/static/explain/img/exttable-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..acd43cb8abab6ef38daf34ff6d7544153a11dacf
GIT binary patch
literal 612
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47`X#{LR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)^XY$8$A0G@_?f)(TiB-0o~u9DE`4h_
z@3s2Om(8dDdI7mh-fGNz8JxJ$(RuCl>$mUSyLbQo{YQ@;J%0Rn>9T9)mKA9e*Bv-;
z!q9k*y7s}c^63vAJOIkOd8|>@*q@f(clYkyWy_XDM0Trbo^<nAscUdhS#3sp`^5F@
z*S~u8DlvJNy7pxi^{qR09)J1r<?Gk4&!4|-WV%sNW#!y?Ya^o8sA?W7EL!yO<Hxse
z-}X<~s-}6=(71d4{Dn`SK5cATs;+%VU9<bdiL;%Z6I)tVJ$drv&6_t59=vw<+ErTC
zQw{Rh4~>~Gc%JJ_00swRNswPKgTu2MX+REVfk$L9koEv$x0Bg+K*j`57sn8Z%gG4}
zOa@Af9Sp+8+}hIC?Ck2|<}MBG)BEEc0z4vIL{>Gq^Qb83DJpS_PM9=p;?&9E0U<$c
zTq{<rTA7`ZmHE2Bfwko87hay;9$%lc3z#+)6+IK17IrPoEP&B6+Pa$ET|Heqd_}|T
z-R;Y#Z{6O%UOwc2y~2g=!)h89Dk9JL?rkX8V67z4`B96(W2)e}i60vRfNoPQag8WR
zNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTd
cHGouG8JIydoSGiG2B?9-)78&qol`;+0LVQH?EnA(

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable-sm.svg b/web/pgadmin/misc/static/explain/img/exttable-sm.svg
new file mode 100644
index 0000000..e1763ad
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/exttable-sm.svg
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="16"
+   height="16"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAbpJREFU
+OI3Fkk1IFGEYx3/P7GwTnqttD0vZSSJWw5SdoK51DLRPg4wMsxACsUsQIh3qFJVG5c1jdPADgq1L
+KUTWQfPQwT1sGfkR0S7tsLuzM+9MB3V0/Dh48oGH9+V5+f/4vw9/2OmSlUt1/7y/HWH2VlwA9LXD
+DjNGlTjEd9kkjDJ7DMVuHfCX2Nl/MPxD4+3PVU0I4PmQU1HmShpjFQPLVhSW26ooLNuj6HhUXC/Q
+aBvNbfyJrBnLureQgy99o/yddXnWl8QwIogIkcjSOTZe4MnAIA0XGnmnm5s76LwSI5/PcaZ5jucv
+0ogISsG16xP09C5Sm9xHz9nU1g4Sib10d1UxMvqZoZGTTH2dYH5hP8XiH9qu/qKmphpd1wG1OUBE
+ME0T04Te+xk+fjqMcmfpf1rL0bpDiAgi4S1o6wH5vEvTuWnef8hxp2uGY/UlOm/7PHiYxjAMNE3b
+GjA0/I3zl37jezPcu1vm9KkUjx+d4PLFLG/SB2lMvSST+R4ChJJ4w3qF48RpbT0e2I1Go4gIluXT
+fnOS5JFpXh9oCZIYArSnYjgelJ3V4BRsFQTKqniUHIXt+gFg5+s/qYOxr5VB8/gAAAAASUVORK5C
+YII=
+"
+     x="0"
+     y="0"
+     width="16"
+     height="16"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/exttable.png b/web/pgadmin/misc/static/explain/img/exttable.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d84035feea62d53d457481178b1bcbe9622f856
GIT binary patch
literal 793
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47;6K3LR^7d#i`GGFZ`c${`aEG
zUw7Sjd-49u`;VVLefIp>vuC#+K3#L}dGf{=db3}2pa0)_=3m{3Kc$C%<?Q>BxZ_*c
zrqAAMJ~%9YXTIQ#?wnU@GhViy`J27(hvTw$W((iw&U_Y~)1HyF{qEhn_wL=hfB*i2
z2M-=TeE8_mqsNaQ8yXiV>iW%}zd>1TV^GL$BjYL^{S(WU9oVt+prXnmSNFZkm#=*C
z<jL~o%k>TF^o@>PyZ-vst5*{zPLflcU|_QU>a|B#uU=JF?bJ2cziipY_3PKae*OB&
zm8%-sEqaEB)ikG?m>;=w=lPpAZ{EIr`|{<>zP<^HDjPJkuS}Y>_5J(zA3l8e^y$;P
zckfoNT)BGn>N#`fDXPqIbUAEeo3EjBs<&_JrcIlkKYzY=?-6CSwcft_U%!6i?2@ga
zbAH+K$Dcoc{`&RnzI`WD)Hj5LAKkcd)7Gt9lT#YiwN7;R-um|K+wv9LmDM(Tcy0Rl
z@#D5_+u{=nRW-JDc3)AL{8UY|HzVudq)BroPg$ri@$mwIO;dmoz*rLG7tG-B>_!@p
z!&%@FSq!8-z}W3%wjGdh+0(@_MB;LCLV^n;4-Zd|&l#OHId5d<%!#>Uvqyo^u8z@B
zF;Otku#nSp1DBm9qhscRhMqMtE)H>yfu51Bp}w)6&cZqimabjQ@@VyoS1+4cMR<97
zd#?KUoIRtfbCAhvS=cqRZDrs1`uxr%FfOPQ4>vF8_xIM%-_X$2-@kPI{CbCkfC7#P
z2U8Or8zmzxD>XAS4xIy%;`1lWcrs_sq*>GW#5zu&ICF}R^VG?+r}Y`QMZ`qYkF!mE
zGG*G-@CFvG)vNY8H+eN(zQmoCbx`Y7z#e{vFBY0Te&TE7f!<Rsag8WRNi0dVN-jzT
zQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTdHGouG8JIyd
UoSGiG2B?9-)78&qol`;+0Q{<O$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable.svg b/web/pgadmin/misc/static/explain/img/exttable.svg
new file mode 100644
index 0000000..d2efe1d
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/exttable.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="16"
+   height="16"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAk9JREFU
+OI2dkl1Ik2EUx3/n3d7XGWbmyLa6UbRAKUgqSITFcnQRQZFKH1I3hVYWlCUSiBAifUBdJEqpCV1U
+5kVCN13UxYKRRBeJXphdOCpsUFqNzXR7v7pYjpYLoj8ceM4D539+5zyPAJT0RGz+Q+FmrziXkjPV
+HlTboMCZxKMusjbHIN9poTlASPnHdZvxOYWnH4XXn1N1aQPLhpjpYDaRw4SusqBbzCct5pNmKnSL
+hYTFD8MiYdjopgWAkgllZzmlJDbYAphGxn2aYOzuVVZaW2i9sBERyRq6rlNbf4t4chpO3skkCGwt
+59nzJK1t06mOfxSbpkld/QC6cYiuSy1pgrSBz7eZnu4C3oxFaDoVZnT0FSKCoiiEQiH27R/EtPcy
+PFRKIFCx3EBE+Pb1E+fOfmfq3QTXbhQRi0M0GqW94wMWftoufmHqbQgRWb4DEaGqqgoRoaYmTt3B
+EQ43OFlcHGd1YTVD90vJz3egKEqGQQZBbm4uLpeLoqI1NB7PIxabQ1F2cqwhitvtQtM0NE37O0Ew
+GERRFKLRGN29eRQXu9ET/fT2NaAbL9lQZv7al285gaqq+Hw+Cgu9XLm+ioryUoYfljPyuJ31nnv0
+D64jPl+J3+9HVdXsI0xOvqfp9CyVlR4aT8RwOBRUVaXl/B6qdzzhcucMt/si2UcIvhijvcNgQ5nC
+0SNziJCxsNoD2zGMR/T1B7CsJKxwpxoDFHfP2FrvELv82+jq3JR+/98/0lI+MPAAUVRuym7Czd6U
+fUlPxLYNA3Gmgf5J4Wav/ASfptBgzLJqagAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="16"
+     height="16"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/img/exttables.png b/web/pgadmin/misc/static/explain/img/exttables.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5dfbd454ed33727bd1deeb87c62f8671f6a5a03
GIT binary patch
literal 662
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47^MPyLR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)(1uT&ufDx|_ipp)e^tkR=O6f)yz^Vw
zrq7<MKiDpPYdG(<`plR2?%iuX{nra9vgEDdjF%cSU*5le-z{*Hjoqv(SFS&L^yu;9
z$BCh54YV)SY`ps5!GohmFPT|vP}AIAUOws6@q1>vXAHE?^>wU&`SRt%hYy!6TNV*D
zT}|t>v;9UL?W?Amt7goYwSN8jSFc`8IQ7}h=Aef5HBHT(=Pz7){rdI(-PetE&Kqf+
zTC?J?x9>_dt@Dk|J3fB=`1bAF+~nQb+SeRSH>_B(;_1_;Rn;?8HFg`BEWUQ_zJ=bg
zri$H9o;-Q;=FOu=PwX9+YwPW<2KnxXa>IqjgMEvDp~F}b<QL4~@a#q!ki%Kv5m^kR
zJ;2!QWVRiUvDwqbF+}2W?0HwQLk<G27xhdW8XmB4zY~jTVks}b`QPk?8Z+O@ht}mE
zxDUR+@ZDiy$V$_wu1ha@gBJAuXUmw$IQdLNWLm+2wv7?FW$l-rva#qG$_eeUt7l;6
zYfsQPQ#3I{XWjwDPG7CLC)J97I!!#j(8XtG(AulgXTzi?C<q+hI5%(o`R%TodS@Ig
zFq!9*{+X5YhEDkFv(xUs<#&JSY%y0q+H23fcl??hCZB62%C0l7Vc_x-dugW}eiG<>
z)e_f;l9a@fRIB8oR3OD*WMF8ZYiOivU>IU#U}a)#Wn!XjU}|MxU@=ow4n;$5eoAIq
iB}9XPC0GMUwUvPxM8m1+p=*E|7(8A5T-G@yGywoZHZsuw

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttables.svg b/web/pgadmin/misc/static/explain/img/exttables.svg
new file mode 100644
index 0000000..4bdfaed
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/img/exttables.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.1"
+   width="16"
+   height="16"
+   id="svg2">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAgdJREFU
+OI2dk0trE1EUgL8zmThp2mw0bVpFMFoXareWBMwfqOJCLYLQCiK04qNF3aqb4roVIm1xJYoLkaJ2
+5SKCbkoVo6AuRE1NjUkkfdg8nJk0My5iYh4byYHLuVwO3/kOnCv+cNLmPyN2vkca31SAc0EfbinS
+s8Vgp6bj1Uq4VMAus2Mb8PibwrM7Kfvt2W5pAlg2ROIG0US+gV9bW4b5w0m71kStXKKJfEsmKsAB
+1xqtmqgA+13rgLslE7W2T63JWsnJj98KL0yNnFEi+/fkzBI5w6JQtDA3LRQAkbJi2aRetWkQGzCt
+6lBqLaASkYl5Cisppm+HcLs1RASHw4GIcP3yT6IfnZy46eD+9631BpV84+ouTLOLY4NLzMzMIyKk
+EpscH0gT/SAMD33hYsDbbFDJ3d3tjF8q8GQ+ydzTIK8X3pHO9NJmfWV0bB2fr7Naq44d7GAuDZCr
+AwWDQQIBm9HhZeKZvYi1zOyDPrb3bENEqrXKeL9Hxvs90miy9Fln8OgvMqttTFxbYXfvOidPGUzP
+RtE0DUVR6jdkcjFrA0y9ynHkzXsiCzvocn9i5IpOKBRCVVWmbs3x8NEePB0p7t09xOHnLpp+lz+c
+tH0zMQYG8py5sK+q63Q6EREKBZ2h0y/JrHgpjPQ1AyYXs7buaKdogV78tzhZo1RdqJxpsRFPYHX6
++AMN5/e0m/GsSQAAAABJRU5ErkJggg==
+"
+     x="0"
+     y="0"
+     width="16"
+     height="16"
+     id="image10" />
+</svg>
diff --git a/web/pgadmin/misc/static/explain/js/snap.svg-min.js b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
new file mode 100644
index 0000000..6567d19
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
@@ -0,0 +1,21 @@
+// Snap.svg 0.4.1
+//
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// build: 2015-04-13
+
+!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g=/\s*,\s*/,h="*",i=function(a,b){return a-b},j={n:{}},k=function(){for(var a=0,b=this.length;b>a;a++)if("undefined"!=typeof this[a])return this[a]},l=function(){for(var a=this.length;--a;)if("undefined"!=typeof this[a])return this[a]},m=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=m.listeners(a),j=0,n=[],o={},p=[],q=b;p.firstDefined=k,p.lastDefined=l,b=a,c=0;for(var r=0,s=h.length;s>r;r++)"zIndex"in h[r]&&(n.push(h[r].zIndex),h[r].zIndex<0&&(o[h[r].zIndex]=h[r]));for(n.sort(i);n[j]<0;)if(e=o[n[j++]],p.push(e.apply(d,g)),c)return c=f,p;for(r=0;s>r;r++)if(e=h[r],"zIndex"in e)if(e.zIndex==n[j]){if(p.push(e.apply(d,g)),c)break;do if(j++,e=o[n[j]],e&&p.push(e.apply(d,g)),c)break;while(e)}else o[e.zIndex]=e;else if(p.push(e.apply(d,g)),c)break;return c=f,b=q,p};m._events=j,m.listeners=function(a){var b,c,d,e,g,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,g=m.length;g>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[h]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},m.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(g),d=0,e=c.length;e>d;d++)!function(a){for(var c,d=a.split(f),e=j,g=0,h=d.length;h>g;g++)e=e.n,e=e.hasOwnProperty(d[g])&&e[d[g]]||(e[d[g]]={n:{}});for(e.f=e.f||[],g=0,h=e.f.length;h>g;g++)if(e.f[g]==b){c=!0;break}!c&&e.f.push(b)}(c[d]);return function(a){+a==+a&&(b.zIndex=+a)}},m.f=function(a){var b=[].slice.call(arguments,1);return function(){m.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},m.stop=function(){c=1},m.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},m.nts=function(){return b.split(f)},m.off=m.unbind=function(a,b){if(!a)return void(m._events=j={n:{}});var c=a.split(g);if(c.length>1)for(var d=0,i=c.length;i>d;d++)m.off(c[d],b);else{c=a.split(f);var k,l,n,d,i,o,p,q=[j];for(d=0,i=c.length;i>d;d++)for(o=0;o<q.length;o+=n.length-2){if(n=[o,1],k=q[o].n,c[d]!=h)k[c[d]]&&n.push(k[c[d]]);else for(l in k)k[e](l)&&n.push(k[l]);q.splice.apply(q,n)}for(d=0,i=q.length;i>d;d++)for(k=q[d];k.n;){if(b){if(k.f){for(o=0,p=k.f.length;p>o;o++)if(k.f[o]==b){k.f.splice(o,1);break}!k.f.length&&delete k.f}for(l in k.n)if(k.n[e](l)&&k.n[l].f){var r=k.n[l].f;for(o=0,p=r.length;p>o;o++)if(r[o]==b){r.splice(o,1);break}!r.length&&delete k.n[l].f}}else{delete k.f;for(l in k.n)k.n[e](l)&&k.n[l].f&&delete k.n[l].f}k=k.n}}},m.once=function(a,b){var c=function(){return m.unbind(a,c),b.apply(this,arguments)};return m.on(a,c)},m.version=d,m.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=m:"function"==typeof define&&define.amd?define("eve",[],function(){return m}):a.eve=m}(this),function(a,b){if("function"==typeof define&&define.amd)define(["eve"],function(c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("eve");module.exports=b(a,c)}else b(a,a.eve)}(window||this,function(a,b){var c=function(b){var c={},d=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},e=Array.isArray||function(a){return a instanceof Array||"[object Array]"==Object.prototype.toString.call(a)},f=0,g="M"+(+new Date).toString(36),h=function(){return g+(f++).toString(36)},i=Date.now||function(){return+new Date},j=function(a){var b=this;if(null==a)return b.s;var c=b.s-a;b.b+=b.dur*c,b.B+=b.dur*c,b.s=a},k=function(a){var b=this;return null==a?b.spd:void(b.spd=a)},l=function(a){var b=this;return null==a?b.dur:(b.s=b.s*a/b.dur,void(b.dur=a))},m=function(){var a=this;delete c[a.id],a.update(),b("mina.stop."+a.id,a)},n=function(){var a=this;a.pdif||(delete c[a.id],a.update(),a.pdif=a.get()-a.b)},o=function(){var a=this;a.pdif&&(a.b=a.get()-a.pdif,delete a.pdif,c[a.id]=a)},p=function(){var a,b=this;if(e(b.start)){a=[];for(var c=0,d=b.start.length;d>c;c++)a[c]=+b.start[c]+(b.end[c]-b.start[c])*b.easing(b.s)}else a=+b.start+(b.end-b.start)*b.easing(b.s);b.set(a)},q=function(){var a=0;for(var e in c)if(c.hasOwnProperty(e)){var f=c[e],g=f.get();a++,f.s=(g-f.b)/(f.dur/f.spd),f.s>=1&&(delete c[e],f.s=1,a--,function(a){setTimeout(function(){b("mina.finish."+a.id,a)})}(f)),f.update()}a&&d(q)},r=function(a,b,e,f,g,i,s){var t={id:h(),start:a,end:b,b:e,s:0,dur:f-e,spd:1,get:g,set:i,easing:s||r.linear,status:j,speed:k,duration:l,stop:m,pause:n,resume:o,update:p};c[t.id]=t;var u,v=0;for(u in c)if(c.hasOwnProperty(u)&&(v++,2==v))break;return 1==v&&d(q),t};return r.time=i,r.getById=function(a){return c[a]||null},r.linear=function(a){return a},r.easeout=function(a){return Math.pow(a,1.7)},r.easein=function(a){return Math.pow(a,.48)},r.easeinout=function(a){if(1==a)return 1;if(0==a)return 0;var b=.48-a/1.04,c=Math.sqrt(.1734+b*b),d=c-b,e=Math.pow(Math.abs(d),1/3)*(0>d?-1:1),f=-c-b,g=Math.pow(Math.abs(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},r.backin=function(a){if(1==a)return 1;var b=1.70158;return a*a*((b+1)*a-b)},r.backout=function(a){if(0==a)return 0;a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},r.elastic=function(a){return a==!!a?a:Math.pow(2,-10*a)*Math.sin(2*(a-.075)*Math.PI/.3)+1},r.bounce=function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b},a.mina=r,r}("undefined"==typeof b?function(){}:b),d=function(a){function c(a,b){if(a){if(a.nodeType)return w(a);if(e(a,"array")&&c.set)return c.set.apply(c,a);if(a instanceof s)return a;if(null==b)return a=y.doc.querySelector(String(a)),w(a)}return a=null==a?"100%":a,b=null==b?"100%":b,new v(a,b)}function d(a,b){if(b){if("#text"==a&&(a=y.doc.createTextNode(b.text||b["#text"]||"")),"#comment"==a&&(a=y.doc.createComment(b.text||b["#text"]||"")),"string"==typeof a&&(a=d(a)),"string"==typeof b)return 1==a.nodeType?"xlink:"==b.substring(0,6)?a.getAttributeNS(T,b.substring(6)):"xml:"==b.substring(0,4)?a.getAttributeNS(U,b.substring(4)):a.getAttribute(b):"text"==b?a.nodeValue:null;if(1==a.nodeType){for(var c in b)if(b[z](c)){var e=A(b[c]);e?"xlink:"==c.substring(0,6)?a.setAttributeNS(T,c.substring(6),e):"xml:"==c.substring(0,4)?a.setAttributeNS(U,c.substring(4),e):a.setAttribute(c,e):a.removeAttribute(c)}}else"text"in b&&(a.nodeValue=b.text)}else a=y.doc.createElementNS(U,a);return a}function e(a,b){return b=A.prototype.toLowerCase.call(b),"finite"==b?isFinite(a):"array"==b&&(a instanceof Array||Array.isArray&&Array.isArray(a))?!0:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||J.call(a).slice(8,-1).toLowerCase()==b}function f(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=f(a[c]));return b}function h(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function i(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),g=d.cache=d.cache||{},i=d.count=d.count||[];return g[z](f)?(h(i,f),c?c(g[f]):g[f]):(i.length>=1e3&&delete g[i.shift()],i.push(f),g[f]=a.apply(b,e),c?c(g[f]):g[f])}return d}function j(a,b,c,d,e,f){if(null==e){var g=a-c,h=b-d;return g||h?(180+180*D.atan2(-h,-g)/H+360)%360:0}return j(a,b,e,f)-j(c,d,e,f)}function k(a){return a%360*H/180}function l(a){return 180*a/H%360}function m(a){var b=[];return a=a.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g,function(a,c,d){return d=d.split(/\s*,\s*|\s+/),"rotate"==c&&1==d.length&&d.push(0,0),"scale"==c&&(d.length>2?d=d.slice(0,2):2==d.length&&d.push(0,0),1==d.length&&d.push(d[0],0,0)),b.push("skewX"==c?["m",1,0,D.tan(k(d[0])),1,0,0]:"skewY"==c?["m",1,D.tan(k(d[0])),0,1,0,0]:[c.charAt(0)].concat(d)),a}),b}function n(a,b){var d=ab(a),e=new c.Matrix;if(d)for(var f=0,g=d.length;g>f;f++){var h,i,j,k,l,m=d[f],n=m.length,o=A(m[0]).toLowerCase(),p=m[0]!=o,q=p?e.invert():0;"t"==o&&2==n?e.translate(m[1],0):"t"==o&&3==n?p?(h=q.x(0,0),i=q.y(0,0),j=q.x(m[1],m[2]),k=q.y(m[1],m[2]),e.translate(j-h,k-i)):e.translate(m[1],m[2]):"r"==o?2==n?(l=l||b,e.rotate(m[1],l.x+l.width/2,l.y+l.height/2)):4==n&&(p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.rotate(m[1],j,k)):e.rotate(m[1],m[2],m[3])):"s"==o?2==n||3==n?(l=l||b,e.scale(m[1],m[n-1],l.x+l.width/2,l.y+l.height/2)):4==n?p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.scale(m[1],m[1],j,k)):e.scale(m[1],m[1],m[2],m[3]):5==n&&(p?(j=q.x(m[3],m[4]),k=q.y(m[3],m[4]),e.scale(m[1],m[2],j,k)):e.scale(m[1],m[2],m[3],m[4])):"m"==o&&7==n&&e.add(m[1],m[2],m[3],m[4],m[5],m[6])}return e}function o(a){var b=a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||a.node.parentNode&&w(a.node.parentNode)||c.select("svg")||c(0,0),d=b.select("defs"),e=null==d?!1:d.node;return e||(e=u("defs",b.node).node),e}function p(a){return a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||c.select("svg")}function q(a,b,c){function e(a){if(null==a)return I;if(a==+a)return a;d(j,{width:a});try{return j.getBBox().width}catch(b){return 0}}function f(a){if(null==a)return I;if(a==+a)return a;d(j,{height:a});try{return j.getBBox().height}catch(b){return 0}}function g(d,e){null==b?i[d]=e(a.attr(d)||0):d==b&&(i=e(null==c?a.attr(d)||0:c))}var h=p(a).node,i={},j=h.querySelector(".svg---mgr");switch(j||(j=d("rect"),d(j,{x:-9e9,y:-9e9,width:10,height:10,"class":"svg---mgr",fill:"none"}),h.appendChild(j)),a.type){case"rect":g("rx",e),g("ry",f);case"image":g("width",e),g("height",f);case"text":g("x",e),g("y",f);break;case"circle":g("cx",e),g("cy",f),g("r",e);break;case"ellipse":g("cx",e),g("cy",f),g("rx",e),g("ry",f);break;case"line":g("x1",e),g("x2",e),g("y1",f),g("y2",f);break;case"marker":g("refX",e),g("markerWidth",e),g("refY",f),g("markerHeight",f);break;case"radialGradient":g("fx",e),g("fy",f);break;case"tspan":g("dx",e),g("dy",f);break;default:g(b,e)}return h.removeChild(j),i}function r(a){e(a,"array")||(a=Array.prototype.slice.call(arguments,0));for(var b=0,c=0,d=this.node;this[b];)delete this[b++];for(b=0;b<a.length;b++)"set"==a[b].type?a[b].forEach(function(a){d.appendChild(a.node)}):d.appendChild(a[b].node);var f=d.childNodes;for(b=0;b<f.length;b++)this[c++]=w(f[b]);return this}function s(a){if(a.snap in V)return V[a.snap];var b;try{b=a.ownerSVGElement}catch(c){}this.node=a,b&&(this.paper=new v(b)),this.type=a.tagName||a.nodeName;var d=this.id=S(this);if(this.anims={},this._={transform:[]},a.snap=d,V[d]=this,"g"==this.type&&(this.add=r),this.type in{g:1,mask:1,pattern:1,symbol:1})for(var e in v.prototype)v.prototype[z](e)&&(this[e]=v.prototype[e])}function t(a){this.node=a}function u(a,b){var c=d(a);b.appendChild(c);var e=w(c);return e}function v(a,b){var c,e,f,g=v.prototype;if(a&&"svg"==a.tagName){if(a.snap in V)return V[a.snap];var h=a.ownerDocument;c=new s(a),e=a.getElementsByTagName("desc")[0],f=a.getElementsByTagName("defs")[0],e||(e=d("desc"),e.appendChild(h.createTextNode("Created with Snap")),c.node.appendChild(e)),f||(f=d("defs"),c.node.appendChild(f)),c.defs=f;for(var i in g)g[z](i)&&(c[i]=g[i]);c.paper=c.root=c}else c=u("svg",y.doc.body),d(c.node,{height:b,version:1.1,width:a,xmlns:U});return c}function w(a){return a?a instanceof s||a instanceof t?a:a.tagName&&"svg"==a.tagName.toLowerCase()?new v(a):a.tagName&&"object"==a.tagName.toLowerCase()&&"image/svg+xml"==a.type?new v(a.contentDocument.getElementsByTagName("svg")[0]):new s(a):a}function x(a,b){for(var c=0,d=a.length;d>c;c++){var e={type:a[c].type,attr:a[c].attr()},f=a[c].children();b.push(e),f.length&&x(f,e.childNodes=[])}}c.version="0.4.0",c.toString=function(){return"Snap v"+this.version},c._={};var y={win:a.window,doc:a.window.document};c._.glob=y;{var z="hasOwnProperty",A=String,B=parseFloat,C=parseInt,D=Math,E=D.max,F=D.min,G=D.abs,H=(D.pow,D.PI),I=(D.round,""),J=Object.prototype.toString,K=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,L=(c._.separator=/[,\s]+/,/[\s]*,[\s]*/),M={hs:1,rg:1},N=/([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,O=/([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,P=/(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/gi,Q=0,R="S"+(+new Date).toString(36),S=function(a){return(a&&a.type?a.type:I)+R+(Q++).toString(36)},T="http://www.w3.org/1999/xlink",U="http://www.w3.org/2000/svg",V={};c.url=function(a){return"url('#"+a+"')"}}c._.$=d,c._.id=S,c.format=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return A(b).replace(a,function(a,b){return c(a,b,d)})}}(),c._.clone=f,c._.cacher=i,c.rad=k,c.deg=l,c.sin=function(a){return D.sin(c.rad(a))},c.tan=function(a){return D.tan(c.rad(a))},c.cos=function(a){return D.cos(c.rad(a))},c.asin=function(a){return c.deg(D.asin(a))},c.acos=function(a){return c.deg(D.acos(a))},c.atan=function(a){return c.deg(D.atan(a))},c.atan2=function(a){return c.deg(D.atan2(a))},c.angle=j,c.len=function(a,b,d,e){return Math.sqrt(c.len2(a,b,d,e))},c.len2=function(a,b,c,d){return(a-c)*(a-c)+(b-d)*(b-d)},c.closestPoint=function(a,b,c){function d(a){var d=a.x-b,e=a.y-c;return d*d+e*e}for(var e,f,g,h,i=a.node,j=i.getTotalLength(),k=j/i.pathSegList.numberOfItems*.125,l=1/0,m=0;j>=m;m+=k)(h=d(g=i.getPointAtLength(m)))<l&&(e=g,f=m,l=h);for(k*=.5;k>.5;){var n,o,p,q,r,s;(p=f-k)>=0&&(r=d(n=i.getPointAtLength(p)))<l?(e=n,f=p,l=r):(q=f+k)<=j&&(s=d(o=i.getPointAtLength(q)))<l?(e=o,f=q,l=s):k*=.5}return e={x:e.x,y:e.y,length:f,distance:Math.sqrt(l)}},c.is=e,c.snapTo=function(a,b,c){if(c=e(c,"finite")?c:10,e(a,"array")){for(var d=a.length;d--;)if(G(a[d]-b)<=c)return a[d]}else{a=+a;var f=b%a;if(c>f)return b-f;if(f>a-c)return b-f+a}return b},c.getRGB=i(function(a){if(!a||(a=A(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:Z};if(!(M[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=W(a)),!a)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};var b,d,f,g,h,i,j=a.match(K);return j?(j[2]&&(f=C(j[2].substring(5),16),d=C(j[2].substring(3,5),16),b=C(j[2].substring(1,3),16)),j[3]&&(f=C((h=j[3].charAt(3))+h,16),d=C((h=j[3].charAt(2))+h,16),b=C((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=B(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),f=B(i[2]),"%"==i[2].slice(-1)&&(f*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsb2rgb(b,d,f,g)):j[6]?(i=j[6].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsl2rgb(b,d,f,g)):(b=F(D.round(b),255),d=F(D.round(d),255),f=F(D.round(f),255),g=F(E(g,0),1),j={r:b,g:d,b:f,toString:Z},j.hex="#"+(16777216|f|d<<8|b<<16).toString(16).slice(1),j.opacity=e(g,"finite")?g:1,j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z}},c),c.hsb=i(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=i(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=i(function(a,b,c,d){if(e(d,"finite")){var f=D.round;return"rgba("+[f(a),f(b),f(c),+d.toFixed(2)]+")"}return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)});var W=function(a){var b=y.doc.getElementsByTagName("head")[0]||y.doc.getElementsByTagName("svg")[0],c="rgb(255, 0, 0)";return(W=i(function(a){if("red"==a.toLowerCase())return c;b.style.color=c,b.style.color=a;var d=y.doc.defaultView.getComputedStyle(b,I).getPropertyValue("color");return d==c?null:d}))(a)},X=function(){return"hsb("+[this.h,this.s,this.b]+")"},Y=function(){return"hsl("+[this.h,this.s,this.l]+")"},Z=function(){return 1==this.opacity||null==this.opacity?this.hex:"rgba("+[this.r,this.g,this.b,this.opacity]+")"},$=function(a,b,d){if(null==b&&e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&e(a,string)){var f=c.getRGB(a);a=f.r,b=f.g,d=f.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},_=function(a,b,d,f){a=D.round(255*a),b=D.round(255*b),d=D.round(255*d);var g={r:a,g:b,b:d,opacity:e(f,"finite")?f:1,hex:c.rgb(a,b,d),toString:Z};return e(f,"finite")&&(g.opacity=f),g};c.color=function(a){var b;return e(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):e(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):(e(a,"string")&&(a=c.getRGB(a)),e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&!("error"in a)?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1,a.error=1)),a.toString=Z,a},c.hsb2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var f,g,h,i,j;return a=a%360/60,j=c*b,i=j*(1-G(a%2-1)),f=g=h=c-j,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.hsl2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var f,g,h,i,j;return a=a%360/60,j=2*b*(.5>c?c:1-c),i=j*(1-G(a%2-1)),f=g=h=c-j/2,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.rgb2hsb=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=E(a,b,c),g=f-F(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:X}},c.rgb2hsl=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=E(a,b,c),h=F(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:Y}},c.parsePathString=function(a){if(!a)return null;var b=c.path(a);if(b.arr)return c.path.clone(b.arr);var d={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},f=[];return e(a,"array")&&e(a[0],"array")&&(f=c.path.clone(a)),f.length||A(a).replace(N,function(a,b,c){var e=[],g=b.toLowerCase();if(c.replace(P,function(a,b){b&&e.push(+b)}),"m"==g&&e.length>2&&(f.push([b].concat(e.splice(0,2))),g="l",b="m"==b?"l":"L"),"o"==g&&1==e.length&&f.push([b,e[0]]),"r"==g)f.push([b].concat(e));else for(;e.length>=d[g]&&(f.push([b].concat(e.splice(0,d[g]))),d[g]););}),f.toString=c.path.toString,b.arr=c.path.clone(f),f};var ab=c.parseTransformString=function(a){if(!a)return null;var b=[];return e(a,"array")&&e(a[0],"array")&&(b=c.path.clone(a)),b.length||A(a).replace(O,function(a,c,d){{var e=[];c.toLowerCase()}d.replace(P,function(a,b){b&&e.push(+b)}),b.push([c].concat(e))}),b.toString=c.path.toString,b};c._.svgTransform2string=m,c._.rgTransform=/^[a-z][\s]*-?\.?\d/i,c._.transform2matrix=n,c._unit2px=q;y.doc.contains||y.doc.compareDocumentPosition?function(a,b){var c=9==a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a==d||!(!d||1!=d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)for(;b;)if(b=b.parentNode,b==a)return!0;return!1};c._.getSomeDefs=o,c._.getSomeSVG=p,c.select=function(a){return a=A(a).replace(/([^\\]):/g,"$1\\:"),w(y.doc.querySelector(a))},c.selectAll=function(a){for(var b=y.doc.querySelectorAll(a),d=(c.set||Array)(),e=0;e<b.length;e++)d.push(w(b[e]));return d},setInterval(function(){for(var a in V)if(V[z](a)){var b=V[a],c=b.node;("svg"!=b.type&&!c.ownerSVGElement||"svg"==b.type&&(!c.parentNode||"ownerSVGElement"in c.parentNode&&!c.ownerSVGElement))&&delete V[a]}},1e4),s.prototype.attr=function(a,c){var d=this,f=d.node;if(!a){if(1!=f.nodeType)return{text:f.nodeValue};for(var g=f.attributes,h={},i=0,j=g.length;j>i;i++)h[g[i].nodeName]=g[i].nodeValue;return h}if(e(a,"string")){if(!(arguments.length>1))return b("snap.util.getattr."+a,d).firstDefined();var k={};k[a]=c,a=k}for(var l in a)a[z](l)&&b("snap.util.attr."+l,d,a[l]);return d},c.parse=function(a){var b=y.doc.createDocumentFragment(),c=!0,d=y.doc.createElement("div");if(a=A(a),a.match(/^\s*<\s*svg(?:\s|>)/)||(a="<svg>"+a+"</svg>",c=!1),d.innerHTML=a,a=d.getElementsByTagName("svg")[0])if(c)b=a;else for(;a.firstChild;)b.appendChild(a.firstChild);return new t(b)},c.fragment=function(){for(var a=Array.prototype.slice.call(arguments,0),b=y.doc.createDocumentFragment(),d=0,e=a.length;e>d;d++){var f=a[d];f.node&&f.node.nodeType&&b.appendChild(f.node),f.nodeType&&b.appendChild(f),"string"==typeof f&&b.appendChild(c.parse(f).node)}return new t(b)},c._.make=u,c._.wrap=w,v.prototype.el=function(a,b){var c=u(a,this.node);return b&&c.attr(b),c},s.prototype.children=function(){for(var a=[],b=this.node.childNodes,d=0,e=b.length;e>d;d++)a[d]=c(b[d]);return a},s.prototype.toJSON=function(){var a=[];return x([this],a),a[0]},b.on("snap.util.getattr",function(){var a=b.nt();a=a.substring(a.lastIndexOf(".")+1);var c=a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});return bb[z](c)?this.node.ownerDocument.defaultView.getComputedStyle(this.node,null).getPropertyValue(c):d(this.node,a)});var bb={"alignment-baseline":0,"baseline-shift":0,clip:0,"clip-path":0,"clip-rule":0,color:0,"color-interpolation":0,"color-interpolation-filters":0,"color-profile":0,"color-rendering":0,cursor:0,direction:0,display:0,"dominant-baseline":0,"enable-background":0,fill:0,"fill-opacity":0,"fill-rule":0,filter:0,"flood-color":0,"flood-opacity":0,font:0,"font-family":0,"font-size":0,"font-size-adjust":0,"font-stretch":0,"font-style":0,"font-variant":0,"font-weight":0,"glyph-orientation-horizontal":0,"glyph-orientation-vertical":0,"image-rendering":0,kerning:0,"letter-spacing":0,"lighting-color":0,marker:0,"marker-end":0,"marker-mid":0,"marker-start":0,mask:0,opacity:0,overflow:0,"pointer-events":0,"shape-rendering":0,"stop-color":0,"stop-opacity":0,stroke:0,"stroke-dasharray":0,"stroke-dashoffset":0,"stroke-linecap":0,"stroke-linejoin":0,"stroke-miterlimit":0,"stroke-opacity":0,"stroke-width":0,"text-anchor":0,"text-decoration":0,"text-rendering":0,"unicode-bidi":0,visibility:0,"word-spacing":0,"writing-mode":0};b.on("snap.util.attr",function(a){var c=b.nt(),e={};c=c.substring(c.lastIndexOf(".")+1),e[c]=a;var f=c.replace(/-(\w)/gi,function(a,b){return b.toUpperCase()}),g=c.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});bb[z](g)?this.node.style[f]=null==a?I:a:d(this.node,e)}),function(){}(v.prototype),c.ajax=function(a,c,d,f){var g=new XMLHttpRequest,h=S();if(g){if(e(c,"function"))f=d,d=c,c=null;else if(e(c,"object")){var i=[];for(var j in c)c.hasOwnProperty(j)&&i.push(encodeURIComponent(j)+"="+encodeURIComponent(c[j]));c=i.join("&")}return g.open(c?"POST":"GET",a,!0),c&&(g.setRequestHeader("X-Requested-With","XMLHttpRequest"),g.setRequestHeader("Content-type","application/x-www-form-urlencoded")),d&&(b.once("snap.ajax."+h+".0",d),b.once("snap.ajax."+h+".200",d),b.once("snap.ajax."+h+".304",d)),g.onreadystatechange=function(){4==g.readyState&&b("snap.ajax."+h+"."+g.status,f,g)},4==g.readyState?g:(g.send(c),g)}},c.load=function(a,b,d){c.ajax(a,function(a){var e=c.parse(a.responseText);d?b.call(d,e):b(e)})};var cb=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,h=e.clientLeft||d.clientLeft||0,i=b.top+(g.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(g.win.pageXOffset||e.scrollLeft||d.scrollLeft)-h;return{y:i,x:j}};return c.getElementByPoint=function(a,b){var c=this,d=(c.canvas,y.doc.elementFromPoint(a,b));if(y.win.opera&&"svg"==d.tagName){var e=cb(d),f=d.createSVGRect();f.x=a-e.x,f.y=b-e.y,f.width=f.height=1;var g=d.getIntersectionList(f,null);g.length&&(d=g[g.length-1])}return d?w(d):null},c.plugin=function(a){a(c,s,v,y,t)},y.win.Snap=c,c}(a||this);return d.plugin(function(d,e,f,g,h){function i(a,b){if(null==b){var c=!0;if(b=a.node.getAttribute("linearGradient"==a.type||"radialGradient"==a.type?"gradientTransform":"pattern"==a.type?"patternTransform":"transform"),!b)return new d.Matrix;b=d._.svgTransform2string(b)}else b=d._.rgTransform.test(b)?o(b).replace(/\.{3}|\u2026/g,a._.transform||""):d._.svgTransform2string(b),n(b,"array")&&(b=d.path?d.path.toString.call(b):o(b)),a._.transform=b;var e=d._.transform2matrix(b,a.getBBox(1));return c?e:void(a.matrix=e)}function j(a){function b(a,b){var c=q(a.node,b);c=c&&c.match(f),c=c&&c[2],c&&"#"==c.charAt()&&(c=c.substring(1),c&&(h[c]=(h[c]||[]).concat(function(c){var d={};d[b]=URL(c),q(a.node,d)})))}function c(a){var b=q(a.node,"xlink:href");b&&"#"==b.charAt()&&(b=b.substring(1),b&&(h[b]=(h[b]||[]).concat(function(b){a.attr("xlink:href","#"+b)})))}for(var d,e=a.selectAll("*"),f=/^\s*url\(("|'|)(.*)\1\)\s*$/,g=[],h={},i=0,j=e.length;j>i;i++){d=e[i],b(d,"fill"),b(d,"stroke"),b(d,"filter"),b(d,"mask"),b(d,"clip-path"),c(d);var k=q(d.node,"id");k&&(q(d.node,{id:d.id}),g.push({old:k,id:d.id}))}for(i=0,j=g.length;j>i;i++){var l=h[g[i].old];if(l)for(var m=0,n=l.length;n>m;m++)l[m](g[i].id)}}function k(a,b,c){return function(d){var e=d.slice(a,b);return 1==e.length&&(e=e[0]),c?c(e):e}}function l(a){return function(){var b=a?"<"+this.type:"",c=this.node.attributes,d=this.node.childNodes;if(a)for(var e=0,f=c.length;f>e;e++)b+=" "+c[e].name+'="'+c[e].value.replace(/"/g,'\\"')+'"';if(d.length){for(a&&(b+=">"),e=0,f=d.length;f>e;e++)3==d[e].nodeType?b+=d[e].nodeValue:1==d[e].nodeType&&(b+=u(d[e]).toString());a&&(b+="</"+this.type+">")}else a&&(b+="/>");return b}}var m=e.prototype,n=d.is,o=String,p=d._unit2px,q=d._.$,r=d._.make,s=d._.getSomeDefs,t="hasOwnProperty",u=d._.wrap;m.getBBox=function(a){if(!d.Matrix||!d.path)return this.node.getBBox();var b=this,c=new d.Matrix;if(b.removed)return d._.box();for(;"use"==b.type;)if(a||(c=c.add(b.transform().localMatrix.translate(b.attr("x")||0,b.attr("y")||0))),b.original)b=b.original;else{var e=b.attr("xlink:href");b=b.original=b.node.ownerDocument.getElementById(e.substring(e.indexOf("#")+1))}var f=b._,g=d.path.get[b.type]||d.path.get.deflt;try{return a?(f.bboxwt=g?d.path.getBBox(b.realPath=g(b)):d._.box(b.node.getBBox()),d._.box(f.bboxwt)):(b.realPath=g(b),b.matrix=b.transform().localMatrix,f.bbox=d.path.getBBox(d.path.map(b.realPath,c.add(b.matrix))),d._.box(f.bbox))}catch(h){return d._.box()}};var v=function(){return this.string};m.transform=function(a){var b=this._;if(null==a){for(var c,e=this,f=new d.Matrix(this.node.getCTM()),g=i(this),h=[g],j=new d.Matrix,k=g.toTransformString(),l=o(g)==o(this.matrix)?o(b.transform):k;"svg"!=e.type&&(e=e.parent());)h.push(i(e));for(c=h.length;c--;)j.add(h[c]);return{string:l,globalMatrix:f,totalMatrix:j,localMatrix:g,diffMatrix:f.clone().add(g.invert()),global:f.toTransformString(),total:j.toTransformString(),local:k,toString:v}}return a instanceof d.Matrix?(this.matrix=a,this._.transform=a.toTransformString()):i(this,a),this.node&&("linearGradient"==this.type||"radialGradient"==this.type?q(this.node,{gradientTransform:this.matrix}):"pattern"==this.type?q(this.node,{patternTransform:this.matrix}):q(this.node,{transform:this.matrix})),this},m.parent=function(){return u(this.node.parentNode)},m.append=m.add=function(a){if(a){if("set"==a.type){var b=this;return a.forEach(function(a){b.add(a)}),this}a=u(a),this.node.appendChild(a.node),a.paper=this.paper}return this},m.appendTo=function(a){return a&&(a=u(a),a.append(this)),this},m.prepend=function(a){if(a){if("set"==a.type){var b,c=this;return a.forEach(function(a){b?b.after(a):c.prepend(a),b=a}),this}a=u(a);var d=a.parent();this.node.insertBefore(a.node,this.node.firstChild),this.add&&this.add(),a.paper=this.paper,this.parent()&&this.parent().add(),d&&d.add()}return this},m.prependTo=function(a){return a=u(a),a.prepend(this),this},m.before=function(a){if("set"==a.type){var b=this;return a.forEach(function(a){var c=a.parent();b.node.parentNode.insertBefore(a.node,b.node),c&&c.add()}),this.parent().add(),this}a=u(a);var c=a.parent();return this.node.parentNode.insertBefore(a.node,this.node),this.parent()&&this.parent().add(),c&&c.add(),a.paper=this.paper,this},m.after=function(a){a=u(a);var b=a.parent();return this.node.nextSibling?this.node.parentNode.insertBefore(a.node,this.node.nextSibling):this.node.parentNode.appendChild(a.node),this.parent()&&this.parent().add(),b&&b.add(),a.paper=this.paper,this},m.insertBefore=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.insertAfter=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node.nextSibling),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.remove=function(){var a=this.parent();return this.node.parentNode&&this.node.parentNode.removeChild(this.node),delete this.paper,this.removed=!0,a&&a.add(),this},m.select=function(a){return u(this.node.querySelector(a))},m.selectAll=function(a){for(var b=this.node.querySelectorAll(a),c=(d.set||Array)(),e=0;e<b.length;e++)c.push(u(b[e]));return c},m.asPX=function(a,b){return null==b&&(b=this.attr(a)),+p(this,a,b)},m.use=function(){var a,b=this.node.id;return b||(b=this.id,q(this.node,{id:b})),a="linearGradient"==this.type||"radialGradient"==this.type||"pattern"==this.type?r(this.type,this.node.parentNode):r("use",this.node.parentNode),q(a.node,{"xlink:href":"#"+b}),a.original=this,a},m.clone=function(){var a=u(this.node.cloneNode(!0));return q(a.node,"id")&&q(a.node,{id:a.id}),j(a),a.insertAfter(this),a},m.toDefs=function(){var a=s(this);return a.appendChild(this.node),this},m.pattern=m.toPattern=function(a,b,c,d){var e=r("pattern",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,a=a.x),q(e.node,{x:a,y:b,width:c,height:d,patternUnits:"userSpaceOnUse",id:e.id,viewBox:[a,b,c,d].join(" ")}),e.node.appendChild(this.node),e},m.marker=function(a,b,c,d,e,f){var g=r("marker",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,e=a.refX||a.cx,f=a.refY||a.cy,a=a.x),q(g.node,{viewBox:[a,b,c,d].join(" "),markerWidth:c,markerHeight:d,orient:"auto",refX:e||0,refY:f||0,id:g.id}),g.node.appendChild(this.node),g};var w=function(a,b,d,e){"function"!=typeof d||d.length||(e=d,d=c.linear),this.attr=a,this.dur=b,d&&(this.easing=d),e&&(this.callback=e)};d._.Animation=w,d.animation=function(a,b,c,d){return new w(a,b,c,d)},m.inAnim=function(){var a=this,b=[];for(var c in a.anims)a.anims[t](c)&&!function(a){b.push({anim:new w(a._attrs,a.dur,a.easing,a._callback),mina:a,curStatus:a.status(),status:function(b){return a.status(b)},stop:function(){a.stop()}})}(a.anims[c]);return b},d.animate=function(a,d,e,f,g,h){"function"!=typeof g||g.length||(h=g,g=c.linear);var i=c.time(),j=c(a,d,i,i+f,c.time,e,g);return h&&b.once("mina.finish."+j.id,h),j},m.stop=function(){for(var a=this.inAnim(),b=0,c=a.length;c>b;b++)a[b].stop();return this},m.animate=function(a,d,e,f){"function"!=typeof e||e.length||(f=e,e=c.linear),a instanceof w&&(f=a.callback,e=a.easing,d=a.dur,a=a.attr);var g,h,i,j,l=[],m=[],p={},q=this;for(var r in a)if(a[t](r)){q.equal?(j=q.equal(r,o(a[r])),g=j.from,h=j.to,i=j.f):(g=+q.attr(r),h=+a[r]);var s=n(g,"array")?g.length:1;p[r]=k(l.length,l.length+s,i),l=l.concat(g),m=m.concat(h)}var u=c.time(),v=c(l,m,u,u+d,c.time,function(a){var b={};for(var c in p)p[t](c)&&(b[c]=p[c](a));q.attr(b)},e);return q.anims[v.id]=v,v._attrs=a,v._callback=f,b("snap.animcreated."+q.id,v),b.once("mina.finish."+v.id,function(){delete q.anims[v.id],f&&f.call(q)}),b.once("mina.stop."+v.id,function(){delete q.anims[v.id]}),q};var x={};m.data=function(a,c){var e=x[this.id]=x[this.id]||{};if(0==arguments.length)return b("snap.data.get."+this.id,this,e,null),e;
+if(1==arguments.length){if(d.is(a,"object")){for(var f in a)a[t](f)&&this.data(f,a[f]);return this}return b("snap.data.get."+this.id,this,e[a],a),e[a]}return e[a]=c,b("snap.data.set."+this.id,this,c,a),this},m.removeData=function(a){return null==a?x[this.id]={}:x[this.id]&&delete x[this.id][a],this},m.outerSVG=m.toString=l(1),m.innerSVG=l(),m.toDataURL=function(){if(a&&a.btoa){var b=this.getBBox(),c=d.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>',{x:+b.x.toFixed(3),y:+b.y.toFixed(3),width:+b.width.toFixed(3),height:+b.height.toFixed(3),contents:this.outerSVG()});return"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(c)))}},h.prototype.select=m.select,h.prototype.selectAll=m.selectAll}),d.plugin(function(a){function b(a,b,d,e,f,g){return null==b&&"[object SVGMatrix]"==c.call(a)?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,void(this.f=a.f)):void(null!=a?(this.a=+a,this.b=+b,this.c=+d,this.d=+e,this.e=+f,this.f=+g):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0))}var c=Object.prototype.toString,d=String,e=Math,f="";!function(c){function g(a){return a[0]*a[0]+a[1]*a[1]}function h(a){var b=e.sqrt(g(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}c.add=function(a,c,d,e,f,g){var h,i,j,k,l=[[],[],[]],m=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],n=[[a,d,f],[c,e,g],[0,0,1]];for(a&&a instanceof b&&(n=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),h=0;3>h;h++)for(i=0;3>i;i++){for(k=0,j=0;3>j;j++)k+=m[h][j]*n[j][i];l[h][i]=k}return this.a=l[0][0],this.b=l[1][0],this.c=l[0][1],this.d=l[1][1],this.e=l[0][2],this.f=l[1][2],this},c.invert=function(){var a=this,c=a.a*a.d-a.b*a.c;return new b(a.d/c,-a.b/c,-a.c/c,a.a/c,(a.c*a.f-a.d*a.e)/c,(a.b*a.e-a.a*a.f)/c)},c.clone=function(){return new b(this.a,this.b,this.c,this.d,this.e,this.f)},c.translate=function(a,b){return this.add(1,0,0,1,a,b)},c.scale=function(a,b,c,d){return null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d),this},c.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var f=+e.cos(b).toFixed(9),g=+e.sin(b).toFixed(9);return this.add(f,g,-g,f,c,d),this.add(1,0,0,1,-c,-d)},c.x=function(a,b){return a*this.a+b*this.c+this.e},c.y=function(a,b){return a*this.b+b*this.d+this.f},c.get=function(a){return+this[d.fromCharCode(97+a)].toFixed(4)},c.toString=function(){return"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")"},c.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},c.determinant=function(){return this.a*this.d-this.b*this.c},c.split=function(){var b={};b.dx=this.e,b.dy=this.f;var c=[[this.a,this.c],[this.b,this.d]];b.scalex=e.sqrt(g(c[0])),h(c[0]),b.shear=c[0][0]*c[1][0]+c[0][1]*c[1][1],c[1]=[c[1][0]-c[0][0]*b.shear,c[1][1]-c[0][1]*b.shear],b.scaley=e.sqrt(g(c[1])),h(c[1]),b.shear/=b.scaley,this.determinant()<0&&(b.scalex=-b.scalex);var d=-c[0][1],f=c[1][1];return 0>f?(b.rotate=a.deg(e.acos(f)),0>d&&(b.rotate=360-b.rotate)):b.rotate=a.deg(e.asin(d)),b.isSimple=!(+b.shear.toFixed(9)||b.scalex.toFixed(9)!=b.scaley.toFixed(9)&&b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate,b},c.toTransformString=function(a){var b=a||this.split();return+b.shear.toFixed(9)?"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]:(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[+b.dx.toFixed(4),+b.dy.toFixed(4)]:f)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:f)+(b.rotate?"r"+[+b.rotate.toFixed(4),0,0]:f))}}(b.prototype),a.Matrix=b,a.matrix=function(a,c,d,e,f,g){return new b(a,c,d,e,f,g)}}),d.plugin(function(a,c,d,e,f){function g(d){return function(e){if(b.stop(),e instanceof f&&1==e.node.childNodes.length&&("radialGradient"==e.node.firstChild.tagName||"linearGradient"==e.node.firstChild.tagName||"pattern"==e.node.firstChild.tagName)&&(e=e.node.firstChild,n(this).appendChild(e),e=l(e)),e instanceof c)if("radialGradient"==e.type||"linearGradient"==e.type||"pattern"==e.type){e.node.id||p(e.node,{id:e.id});var g=q(e.node.id)}else g=e.attr(d);else if(g=a.color(e),g.error){var h=a(n(this).ownerSVGElement).gradient(e);h?(h.node.id||p(h.node,{id:h.id}),g=q(h.node.id)):g=e}else g=r(g);var i={};i[d]=g,p(this.node,i),this.node.style[d]=t}}function h(a){b.stop(),a==+a&&(a+="px"),this.node.style.fontSize=a}function i(a){for(var b=[],c=a.childNodes,d=0,e=c.length;e>d;d++){var f=c[d];3==f.nodeType&&b.push(f.nodeValue),"tspan"==f.tagName&&b.push(1==f.childNodes.length&&3==f.firstChild.nodeType?f.firstChild.nodeValue:i(f))}return b}function j(){return b.stop(),this.node.style.fontSize}var k=a._.make,l=a._.wrap,m=a.is,n=a._.getSomeDefs,o=/^url\(#?([^)]+)\)$/,p=a._.$,q=a.url,r=String,s=a._.separator,t="";b.on("snap.util.attr.mask",function(a){if(a instanceof c||a instanceof f){if(b.stop(),a instanceof f&&1==a.node.childNodes.length&&(a=a.node.firstChild,n(this).appendChild(a),a=l(a)),"mask"==a.type)var d=a;else d=k("mask",n(this)),d.node.appendChild(a.node);!d.node.id&&p(d.node,{id:d.id}),p(this.node,{mask:q(d.id)})}}),function(a){b.on("snap.util.attr.clip",a),b.on("snap.util.attr.clip-path",a),b.on("snap.util.attr.clipPath",a)}(function(a){if(a instanceof c||a instanceof f){if(b.stop(),"clipPath"==a.type)var d=a;else d=k("clipPath",n(this)),d.node.appendChild(a.node),!d.node.id&&p(d.node,{id:d.id});p(this.node,{"clip-path":q(d.node.id||d.id)})}}),b.on("snap.util.attr.fill",g("fill")),b.on("snap.util.attr.stroke",g("stroke"));var u=/^([lr])(?:\(([^)]*)\))?(.*)$/i;b.on("snap.util.grad.parse",function(a){a=r(a);var b=a.match(u);if(!b)return null;var c=b[1],d=b[2],e=b[3];return d=d.split(/\s*,\s*/).map(function(a){return+a==a?+a:a}),1==d.length&&0==d[0]&&(d=[]),e=e.split("-"),e=e.map(function(a){a=a.split(":");var b={color:a[0]};return a[1]&&(b.offset=parseFloat(a[1])),b}),{type:c,params:d,stops:e}}),b.on("snap.util.attr.d",function(c){b.stop(),m(c,"array")&&m(c[0],"array")&&(c=a.path.toString.call(c)),c=r(c),c.match(/[ruo]/i)&&(c=a.path.toAbsolute(c)),p(this.node,{d:c})})(-1),b.on("snap.util.attr.#text",function(a){b.stop(),a=r(a);for(var c=e.doc.createTextNode(a);this.node.firstChild;)this.node.removeChild(this.node.firstChild);this.node.appendChild(c)})(-1),b.on("snap.util.attr.path",function(a){b.stop(),this.attr({d:a})})(-1),b.on("snap.util.attr.class",function(a){b.stop(),this.node.className.baseVal=a})(-1),b.on("snap.util.attr.viewBox",function(a){var c;c=m(a,"object")&&"x"in a?[a.x,a.y,a.width,a.height].join(" "):m(a,"array")?a.join(" "):a,p(this.node,{viewBox:c}),b.stop()})(-1),b.on("snap.util.attr.transform",function(a){this.transform(a),b.stop()})(-1),b.on("snap.util.attr.r",function(a){"rect"==this.type&&(b.stop(),p(this.node,{rx:a,ry:a}))})(-1),b.on("snap.util.attr.textpath",function(a){if(b.stop(),"text"==this.type){var d,e,f;if(!a&&this.textPath){for(e=this.textPath;e.node.firstChild;)this.node.appendChild(e.node.firstChild);return e.remove(),void delete this.textPath}if(m(a,"string")){var g=n(this),h=l(g.parentNode).path(a);g.appendChild(h.node),d=h.id,h.attr({id:d})}else a=l(a),a instanceof c&&(d=a.attr("id"),d||(d=a.id,a.attr({id:d})));if(d)if(e=this.textPath,f=this.node,e)e.attr({"xlink:href":"#"+d});else{for(e=p("textPath",{"xlink:href":"#"+d});f.firstChild;)e.appendChild(f.firstChild);f.appendChild(e),this.textPath=l(e)}}})(-1),b.on("snap.util.attr.text",function(a){if("text"==this.type){for(var c=this.node,d=function(a){var b=p("tspan");if(m(a,"array"))for(var c=0;c<a.length;c++)b.appendChild(d(a[c]));else b.appendChild(e.doc.createTextNode(a));return b.normalize&&b.normalize(),b};c.firstChild;)c.removeChild(c.firstChild);for(var f=d(a);f.firstChild;)c.appendChild(f.firstChild)}b.stop()})(-1),b.on("snap.util.attr.fontSize",h)(-1),b.on("snap.util.attr.font-size",h)(-1),b.on("snap.util.getattr.transform",function(){return b.stop(),this.transform()})(-1),b.on("snap.util.getattr.textpath",function(){return b.stop(),this.textPath})(-1),function(){function c(c){return function(){b.stop();var d=e.doc.defaultView.getComputedStyle(this.node,null).getPropertyValue("marker-"+c);return"none"==d?d:a(e.doc.getElementById(d.match(o)[1]))}}function d(a){return function(c){b.stop();var d="marker"+a.charAt(0).toUpperCase()+a.substring(1);if(""==c||!c)return void(this.node.style[d]="none");if("marker"==c.type){var e=c.node.id;return e||p(c.node,{id:c.id}),void(this.node.style[d]=q(e))}}}b.on("snap.util.getattr.marker-end",c("end"))(-1),b.on("snap.util.getattr.markerEnd",c("end"))(-1),b.on("snap.util.getattr.marker-start",c("start"))(-1),b.on("snap.util.getattr.markerStart",c("start"))(-1),b.on("snap.util.getattr.marker-mid",c("mid"))(-1),b.on("snap.util.getattr.markerMid",c("mid"))(-1),b.on("snap.util.attr.marker-end",d("end"))(-1),b.on("snap.util.attr.markerEnd",d("end"))(-1),b.on("snap.util.attr.marker-start",d("start"))(-1),b.on("snap.util.attr.markerStart",d("start"))(-1),b.on("snap.util.attr.marker-mid",d("mid"))(-1),b.on("snap.util.attr.markerMid",d("mid"))(-1)}(),b.on("snap.util.getattr.r",function(){return"rect"==this.type&&p(this.node,"rx")==p(this.node,"ry")?(b.stop(),p(this.node,"rx")):void 0})(-1),b.on("snap.util.getattr.text",function(){if("text"==this.type||"tspan"==this.type){b.stop();var a=i(this.node);return 1==a.length?a[0]:a}})(-1),b.on("snap.util.getattr.#text",function(){return this.node.textContent})(-1),b.on("snap.util.getattr.viewBox",function(){b.stop();var c=p(this.node,"viewBox");return c?(c=c.split(s),a._.box(+c[0],+c[1],+c[2],+c[3])):void 0})(-1),b.on("snap.util.getattr.points",function(){var a=p(this.node,"points");return b.stop(),a?a.split(s):void 0})(-1),b.on("snap.util.getattr.path",function(){var a=p(this.node,"d");return b.stop(),a})(-1),b.on("snap.util.getattr.class",function(){return this.node.className.baseVal})(-1),b.on("snap.util.getattr.fontSize",j)(-1),b.on("snap.util.getattr.font-size",j)(-1)}),d.plugin(function(a,b){var c=/\S+/g,d=String,e=b.prototype;e.addClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(h.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e||k.push(f);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.removeClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(k.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e&&k.splice(e,1);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.hasClass=function(a){var b=this.node,d=b.className.baseVal,e=d.match(c)||[];return!!~e.indexOf(a)},e.toggleClass=function(a,b){if(null!=b)return b?this.addClass(a):this.removeClass(a);var d,e,f,g,h=(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];for(d=0;f=h[d++];)e=k.indexOf(f),~e?k.splice(e,1):k.push(f);return g=k.join(" "),j!=g&&(i.className.baseVal=g),this}}),d.plugin(function(){function a(a){return a}function c(a){return function(b){return+b.toFixed(3)+a}}var d={"+":function(a,b){return a+b},"-":function(a,b){return a-b},"/":function(a,b){return a/b},"*":function(a,b){return a*b}},e=String,f=/[a-z]+$/i,g=/^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;b.on("snap.util.attr",function(a){var c=e(a).match(g);if(c){var h=b.nt(),i=h.substring(h.lastIndexOf(".")+1),j=this.attr(i),k={};b.stop();var l=c[3]||"",m=j.match(f),n=d[c[1]];if(m&&m==l?a=n(parseFloat(j),+c[2]):(j=this.asPX(i),a=n(this.asPX(i),this.asPX(i,c[2]+l))),isNaN(j)||isNaN(a))return;k[i]=a,this.attr(k)}})(-10),b.on("snap.util.equal",function(h,i){var j=e(this.attr(h)||""),k=e(i).match(g);if(k){b.stop();var l=k[3]||"",m=j.match(f),n=d[k[1]];return m&&m==l?{from:parseFloat(j),to:n(parseFloat(j),+k[2]),f:c(m)}:(j=this.asPX(h),{from:j,to:n(j,this.asPX(h,k[2]+l)),f:a})}})(-10)}),d.plugin(function(c,d,e,f){var g=e.prototype,h=c.is;g.rect=function(a,b,c,d,e,f){var g;return null==f&&(f=e),h(a,"object")&&"[object Object]"==a?g=a:null!=a&&(g={x:a,y:b,width:c,height:d},null!=e&&(g.rx=e,g.ry=f)),this.el("rect",g)},g.circle=function(a,b,c){var d;return h(a,"object")&&"[object Object]"==a?d=a:null!=a&&(d={cx:a,cy:b,r:c}),this.el("circle",d)};var i=function(){function a(){this.parentNode.removeChild(this)}return function(b,c){var d=f.doc.createElement("img"),e=f.doc.body;d.style.cssText="position:absolute;left:-9999em;top:-9999em",d.onload=function(){c.call(d),d.onload=d.onerror=null,e.removeChild(d)},d.onerror=a,e.appendChild(d),d.src=b}}();g.image=function(a,b,d,e,f){var g=this.el("image");if(h(a,"object")&&"src"in a)g.attr(a);else if(null!=a){var j={"xlink:href":a,preserveAspectRatio:"none"};null!=b&&null!=d&&(j.x=b,j.y=d),null!=e&&null!=f?(j.width=e,j.height=f):i(a,function(){c._.$(g.node,{width:this.offsetWidth,height:this.offsetHeight})}),c._.$(g.node,j)}return g},g.ellipse=function(a,b,c,d){var e;return h(a,"object")&&"[object Object]"==a?e=a:null!=a&&(e={cx:a,cy:b,rx:c,ry:d}),this.el("ellipse",e)},g.path=function(a){var b;return h(a,"object")&&!h(a,"array")?b=a:a&&(b={d:a}),this.el("path",b)},g.group=g.g=function(a){var b=this.el("g");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.svg=function(a,b,c,d,e,f,g,i){var j={};return h(a,"object")&&null==b?j=a:(null!=a&&(j.x=a),null!=b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),null!=e&&null!=f&&null!=g&&null!=i&&(j.viewBox=[e,f,g,i])),this.el("svg",j)},g.mask=function(a){var b=this.el("mask");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.ptrn=function(a,b,c,d,e,f,g,i){if(h(a,"object"))var j=a;else j={patternUnits:"userSpaceOnUse"},a&&(j.x=a),b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),j.viewBox=null!=e&&null!=f&&null!=g&&null!=i?[e,f,g,i]:[a||0,b||0,c||0,d||0];return this.el("pattern",j)},g.use=function(a){return null!=a?(a instanceof d&&(a.attr("id")||a.attr({id:c._.id(a)}),a=a.attr("id")),"#"==String(a).charAt()&&(a=a.substring(1)),this.el("use",{"xlink:href":"#"+a})):d.prototype.use.call(this)},g.symbol=function(a,b,c,d){var e={};return null!=a&&null!=b&&null!=c&&null!=d&&(e.viewBox=[a,b,c,d]),this.el("symbol",e)},g.text=function(a,b,c){var d={};return h(a,"object")?d=a:null!=a&&(d={x:a,y:b,text:c||""}),this.el("text",d)},g.line=function(a,b,c,d){var e={};return h(a,"object")?e=a:null!=a&&(e={x1:a,x2:c,y1:b,y2:d}),this.el("line",e)},g.polyline=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polyline",b)},g.polygon=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polygon",b)},function(){function d(){return this.selectAll("stop")}function e(a,b){var d=k("stop"),e={offset:+b+"%"};return a=c.color(a),e["stop-color"]=a.hex,a.opacity<1&&(e["stop-opacity"]=a.opacity),k(d,e),this.node.appendChild(d),this}function f(){if("linearGradient"==this.type){var a=k(this.node,"x1")||0,b=k(this.node,"x2")||1,d=k(this.node,"y1")||0,e=k(this.node,"y2")||0;return c._.box(a,d,math.abs(b-a),math.abs(e-d))}var f=this.node.cx||.5,g=this.node.cy||.5,h=this.node.r||0;return c._.box(f-h,g-h,2*h,2*h)}function h(a,c){function d(a,b){for(var c=(b-l)/(a-m),d=m;a>d;d++)g[d].offset=+(+l+c*(d-m)).toFixed(2);m=a,l=b}var e,f=b("snap.util.grad.parse",null,c).firstDefined();if(!f)return null;f.params.unshift(a),e="l"==f.type.toLowerCase()?i.apply(0,f.params):j.apply(0,f.params),f.type!=f.type.toLowerCase()&&k(e.node,{gradientUnits:"userSpaceOnUse"});var g=f.stops,h=g.length,l=0,m=0;h--;for(var n=0;h>n;n++)"offset"in g[n]&&d(n,g[n].offset);for(g[h].offset=g[h].offset||100,d(h,g[h].offset),n=0;h>=n;n++){var o=g[n];e.addStop(o.color,o.offset)}return e}function i(a,b,g,h,i){var j=c._.make("linearGradient",a);return j.stops=d,j.addStop=e,j.getBBox=f,null!=b&&k(j.node,{x1:b,y1:g,x2:h,y2:i}),j}function j(a,b,g,h,i,j){var l=c._.make("radialGradient",a);return l.stops=d,l.addStop=e,l.getBBox=f,null!=b&&k(l.node,{cx:b,cy:g,r:h}),null!=i&&null!=j&&k(l.node,{fx:i,fy:j}),l}var k=c._.$;g.gradient=function(a){return h(this.defs,a)},g.gradientLinear=function(a,b,c,d){return i(this.defs,a,b,c,d)},g.gradientRadial=function(a,b,c,d,e){return j(this.defs,a,b,c,d,e)},g.toString=function(){var a,b=this.node.ownerDocument,d=b.createDocumentFragment(),e=b.createElement("div"),f=this.node.cloneNode(!0);return d.appendChild(e),e.appendChild(f),c._.$(f,{xmlns:"http://www.w3.org/2000/svg"}),a=e.innerHTML,d.removeChild(d.firstChild),a},g.toDataURL=function(){return a&&a.btoa?"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(this))):void 0},g.clear=function(){for(var a,b=this.node.firstChild;b;)a=b.nextSibling,"defs"!=b.tagName?b.parentNode.removeChild(b):g.clear.call({node:b}),b=a}}()}),d.plugin(function(a,b){function c(a){var b=c.ps=c.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[K](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]}function d(a,b,c,d){return null==a&&(a=b=c=d=0),null==b&&(b=a.y,c=a.width,d=a.height,a=a.x),{x:a,y:b,width:c,w:c,height:d,h:d,x2:a+c,y2:b+d,cx:a+c/2,cy:b+d/2,r1:N.min(c,d)/2,r2:N.max(c,d)/2,r0:N.sqrt(c*c+d*d)/2,path:w(a,b,c,d),vb:[a,b,c,d].join(" ")}}function e(){return this.join(",").replace(L,"$1")}function f(a){var b=J(a);return b.toString=e,b}function g(a,b,c,d,e,f,g,h,j){return null==j?n(a,b,c,d,e,f,g,h):i(a,b,c,d,e,f,g,h,o(a,b,c,d,e,f,g,h,j))}function h(c,d){function e(a){return+(+a).toFixed(3)}return a._.cacher(function(a,f,h){a instanceof b&&(a=a.attr("d")),a=E(a);for(var j,k,l,m,n,o="",p={},q=0,r=0,s=a.length;s>r;r++){if(l=a[r],"M"==l[0])j=+l[1],k=+l[2];else{if(m=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6]),q+m>f){if(d&&!p.start){if(n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q),o+=["C"+e(n.start.x),e(n.start.y),e(n.m.x),e(n.m.y),e(n.x),e(n.y)],h)return o;p.start=o,o=["M"+e(n.x),e(n.y)+"C"+e(n.n.x),e(n.n.y),e(n.end.x),e(n.end.y),e(l[5]),e(l[6])].join(),q+=m,j=+l[5],k=+l[6];continue}if(!c&&!d)return n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q)}q+=m,j=+l[5],k=+l[6]}o+=l.shift()+l}return p.end=o,n=c?q:d?p:i(j,k,l[0],l[1],l[2],l[3],l[4],l[5],1)},null,a._.clone)}function i(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/O;return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}}function j(b,c,e,f,g,h,i,j){a.is(b,"array")||(b=[b,c,e,f,g,h,i,j]);var k=D.apply(null,b);return d(k.min.x,k.min.y,k.max.x-k.min.x,k.max.y-k.min.y)}function k(a,b,c){return b>=a.x&&b<=a.x+a.width&&c>=a.y&&c<=a.y+a.height}function l(a,b){return a=d(a),b=d(b),k(b,a.x,a.y)||k(b,a.x2,a.y)||k(b,a.x,a.y2)||k(b,a.x2,a.y2)||k(a,b.x,b.y)||k(a,b.x2,b.y)||k(a,b.x,b.y2)||k(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)}function m(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function n(a,b,c,d,e,f,g,h,i){null==i&&(i=1),i=i>1?1:0>i?0:i;for(var j=i/2,k=12,l=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;k>p;p++){var q=j*l[p]+j,r=m(q,a,c,e,g),s=m(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return j*o}function o(a,b,c,d,e,f,g,h,i){if(!(0>i||n(a,b,c,d,e,f,g,h)<i)){var j,k=1,l=k/2,m=k-l,o=.01;for(j=n(a,b,c,d,e,f,g,h,m);S(j-i)>o;)l/=2,m+=(i>j?1:-1)*l,j=n(a,b,c,d,e,f,g,h,m);return m}}function p(a,b,c,d,e,f,g,h){if(!(Q(a,c)<P(e,g)||P(a,c)>Q(e,g)||Q(b,d)<P(f,h)||P(b,d)>Q(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+Q(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+Q(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+Q(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+Q(f,h).toFixed(2)))return{x:l,y:m}}}}function q(a,b,c){var d=j(a),e=j(b);if(!l(d,e))return c?0:[];for(var f=n.apply(0,a),g=n.apply(0,b),h=~~(f/8),k=~~(g/8),m=[],o=[],q={},r=c?0:[],s=0;h+1>s;s++){var t=i.apply(0,a.concat(s/h));m.push({x:t.x,y:t.y,t:s/h})}for(s=0;k+1>s;s++)t=i.apply(0,b.concat(s/k)),o.push({x:t.x,y:t.y,t:s/k});for(s=0;h>s;s++)for(var u=0;k>u;u++){var v=m[s],w=m[s+1],x=o[u],y=o[u+1],z=S(w.x-v.x)<.001?"y":"x",A=S(y.x-x.x)<.001?"y":"x",B=p(v.x,v.y,w.x,w.y,x.x,x.y,y.x,y.y);if(B){if(q[B.x.toFixed(4)]==B.y.toFixed(4))continue;q[B.x.toFixed(4)]=B.y.toFixed(4);var C=v.t+S((B[z]-v[z])/(w[z]-v[z]))*(w.t-v.t),D=x.t+S((B[A]-x[A])/(y[A]-x[A]))*(y.t-x.t);C>=0&&1>=C&&D>=0&&1>=D&&(c?r++:r.push({x:B.x,y:B.y,t1:C,t2:D}))}}return r}function r(a,b){return t(a,b)}function s(a,b){return t(a,b,1)}function t(a,b,c){a=E(a),b=E(b);for(var d,e,f,g,h,i,j,k,l,m,n=c?0:[],o=0,p=a.length;p>o;o++){var r=a[o];if("M"==r[0])d=h=r[1],e=i=r[2];else{"C"==r[0]?(l=[d,e].concat(r.slice(1)),d=l[6],e=l[7]):(l=[d,e,d,e,h,i,h,i],d=h,e=i);for(var s=0,t=b.length;t>s;s++){var u=b[s];if("M"==u[0])f=j=u[1],g=k=u[2];else{"C"==u[0]?(m=[f,g].concat(u.slice(1)),f=m[6],g=m[7]):(m=[f,g,f,g,j,k,j,k],f=j,g=k);var v=q(l,m,c);if(c)n+=v;else{for(var w=0,x=v.length;x>w;w++)v[w].segment1=o,v[w].segment2=s,v[w].bez1=l,v[w].bez2=m;n=n.concat(v)}}}}}return n}function u(a,b,c){var d=v(a);return k(d,b,c)&&t(a,[["M",b,c],["H",d.x2+10]],1)%2==1}function v(a){var b=c(a);if(b.bbox)return J(b.bbox);if(!a)return d();a=E(a);for(var e,f=0,g=0,h=[],i=[],j=0,k=a.length;k>j;j++)if(e=a[j],"M"==e[0])f=e[1],g=e[2],h.push(f),i.push(g);else{var l=D(f,g,e[1],e[2],e[3],e[4],e[5],e[6]);h=h.concat(l.min.x,l.max.x),i=i.concat(l.min.y,l.max.y),f=e[5],g=e[6]}var m=P.apply(0,h),n=P.apply(0,i),o=Q.apply(0,h),p=Q.apply(0,i),q=d(m,n,o-m,p-n);return b.bbox=J(q),q}function w(a,b,c,d,f){if(f)return[["M",+a+ +f,b],["l",c-2*f,0],["a",f,f,0,0,1,f,f],["l",0,d-2*f],["a",f,f,0,0,1,-f,f],["l",2*f-c,0],["a",f,f,0,0,1,-f,-f],["l",0,2*f-d],["a",f,f,0,0,1,f,-f],["z"]];var g=[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]];return g.toString=e,g}function x(a,b,c,d,f){if(null==f&&null==d&&(d=c),a=+a,b=+b,c=+c,d=+d,null!=f)var g=Math.PI/180,h=a+c*Math.cos(-d*g),i=a+c*Math.cos(-f*g),j=b+c*Math.sin(-d*g),k=b+c*Math.sin(-f*g),l=[["M",h,j],["A",c,c,0,+(f-d>180),0,i,k]];else l=[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]];return l.toString=e,l}function y(b){var d=c(b),g=String.prototype.toLowerCase;if(d.rel)return f(d.rel);a.is(b,"array")&&a.is(b&&b[0],"array")||(b=a.parsePathString(b));var h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=b[0][1],j=b[0][2],k=i,l=j,m++,h.push(["M",i,j]));for(var n=m,o=b.length;o>n;n++){var p=h[n]=[],q=b[n];if(q[0]!=g.call(q[0]))switch(p[0]=g.call(q[0]),p[0]){case"a":p[1]=q[1],p[2]=q[2],p[3]=q[3],p[4]=q[4],p[5]=q[5],p[6]=+(q[6]-i).toFixed(3),p[7]=+(q[7]-j).toFixed(3);break;case"v":p[1]=+(q[1]-j).toFixed(3);break;case"m":k=q[1],l=q[2];default:for(var r=1,s=q.length;s>r;r++)p[r]=+(q[r]-(r%2?i:j)).toFixed(3)}else{p=h[n]=[],"m"==q[0]&&(k=q[1]+i,l=q[2]+j);for(var t=0,u=q.length;u>t;t++)h[n][t]=q[t]}var v=h[n].length;switch(h[n][0]){case"z":i=k,j=l;break;case"h":i+=+h[n][v-1];break;case"v":j+=+h[n][v-1];break;default:i+=+h[n][v-2],j+=+h[n][v-1]}}return h.toString=e,d.rel=f(h),h}function z(b){var d=c(b);if(d.abs)return f(d.abs);if(I(b,"array")&&I(b&&b[0],"array")||(b=a.parsePathString(b)),!b||!b.length)return[["M",0,0]];var g,h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=+b[0][1],j=+b[0][2],k=i,l=j,m++,h[0]=["M",i,j]);for(var n,o,p=3==b.length&&"M"==b[0][0]&&"R"==b[1][0].toUpperCase()&&"Z"==b[2][0].toUpperCase(),q=m,r=b.length;r>q;q++){if(h.push(n=[]),o=b[q],g=o[0],g!=g.toUpperCase())switch(n[0]=g.toUpperCase(),n[0]){case"A":n[1]=o[1],n[2]=o[2],n[3]=o[3],n[4]=o[4],n[5]=o[5],n[6]=+o[6]+i,n[7]=+o[7]+j;break;case"V":n[1]=+o[1]+j;break;case"H":n[1]=+o[1]+i;break;case"R":for(var s=[i,j].concat(o.slice(1)),t=2,u=s.length;u>t;t++)s[t]=+s[t]+i,s[++t]=+s[t]+j;h.pop(),h=h.concat(G(s,p));break;case"O":h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);break;case"U":h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));break;case"M":k=+o[1]+i,l=+o[2]+j;default:for(t=1,u=o.length;u>t;t++)n[t]=+o[t]+(t%2?i:j)}else if("R"==g)s=[i,j].concat(o.slice(1)),h.pop(),h=h.concat(G(s,p)),n=["R"].concat(o.slice(-2));else if("O"==g)h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);else if("U"==g)h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));else for(var v=0,w=o.length;w>v;v++)n[v]=o[v];if(g=g.toUpperCase(),"O"!=g)switch(n[0]){case"Z":i=+k,j=+l;break;case"H":i=n[1];break;case"V":j=n[1];break;case"M":k=n[n.length-2],l=n[n.length-1];default:i=n[n.length-2],j=n[n.length-1]}}return h.toString=e,d.abs=f(h),h}function A(a,b,c,d){return[a,b,c,d,c,d]}function B(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]}function C(b,c,d,e,f,g,h,i,j,k){var l,m=120*O/180,n=O/180*(+f||0),o=[],p=a._.cacher(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(b,c,-n),b=l.x,c=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(O/180*f),N.sin(O/180*f),(b-i)/2),r=(c-j)/2,s=q*q/(d*d)+r*r/(e*e);s>1&&(s=N.sqrt(s),d=s*d,e=s*e);var t=d*d,u=e*e,v=(g==h?-1:1)*N.sqrt(S((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*d*r/e+(b+i)/2,x=v*-e*q/d+(c+j)/2,y=N.asin(((c-x)/e).toFixed(9)),z=N.asin(((j-x)/e).toFixed(9));y=w>b?O-y:y,z=w>i?O-z:z,0>y&&(y=2*O+y),0>z&&(z=2*O+z),h&&y>z&&(y-=2*O),!h&&z>y&&(z-=2*O)}var A=z-y;if(S(A)>m){var B=z,D=i,E=j;z=y+m*(h&&z>y?1:-1),i=w+d*N.cos(z),j=x+e*N.sin(z),o=C(i,j,d,e,f,0,h,D,E,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),J=N.tan(A/4),K=4/3*d*J,L=4/3*e*J,M=[b,c],P=[b+K*G,c-L*F],Q=[i+K*I,j-L*H],R=[i,j];if(P[0]=2*M[0]-P[0],P[1]=2*M[1]-P[1],k)return[P,Q,R].concat(o);o=[P,Q,R].concat(o).join().split(",");for(var T=[],U=0,V=o.length;V>U;U++)T[U]=U%2?p(o[U-1],o[U],n).y:p(o[U],o[U+1],n).x;return T}function D(a,b,c,d,e,f,g,h){for(var i,j,k,l,m,n,o,p,q=[],r=[[],[]],s=0;2>s;++s)if(0==s?(j=6*a-12*c+6*e,i=-3*a+9*c-9*e+3*g,k=3*c-3*a):(j=6*b-12*d+6*f,i=-3*b+9*d-9*f+3*h,k=3*d-3*b),S(i)<1e-12){if(S(j)<1e-12)continue;l=-k/j,l>0&&1>l&&q.push(l)}else o=j*j-4*k*i,p=N.sqrt(o),0>o||(m=(-j+p)/(2*i),m>0&&1>m&&q.push(m),n=(-j-p)/(2*i),n>0&&1>n&&q.push(n));for(var t,u=q.length,v=u;u--;)l=q[u],t=1-l,r[0][u]=t*t*t*a+3*t*t*l*c+3*t*l*l*e+l*l*l*g,r[1][u]=t*t*t*b+3*t*t*l*d+3*t*l*l*f+l*l*l*h;return r[0][v]=a,r[1][v]=b,r[0][v+1]=g,r[1][v+1]=h,r[0].length=r[1].length=v+2,{min:{x:P.apply(0,r[0]),y:P.apply(0,r[1])},max:{x:Q.apply(0,r[0]),y:Q.apply(0,r[1])}}}function E(a,b){var d=!b&&c(a);if(!b&&d.curve)return f(d.curve);for(var e=z(a),g=b&&z(b),h={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},i={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},j=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"].concat(C.apply(0,[b.x,b.y].concat(a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e].concat(a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"].concat(B(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"].concat(B(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"].concat(A(b.x,b.y,a[1],a[2]));break;case"H":a=["C"].concat(A(b.x,b.y,a[1],b.y));break;case"V":a=["C"].concat(A(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"].concat(A(b.x,b.y,b.X,b.Y))}return a}),k=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)m[b]="A",g&&(n[b]="A"),a.splice(b++,0,["C"].concat(c.splice(0,6)));a.splice(b,1),r=Q(e.length,g&&g.length||0)}},l=function(a,b,c,d,f){a&&b&&"M"==a[f][0]&&"M"!=b[f][0]&&(b.splice(f,0,["M",d.x,d.y]),c.bx=0,c.by=0,c.x=a[f][1],c.y=a[f][2],r=Q(e.length,g&&g.length||0))},m=[],n=[],o="",p="",q=0,r=Q(e.length,g&&g.length||0);r>q;q++){e[q]&&(o=e[q][0]),"C"!=o&&(m[q]=o,q&&(p=m[q-1])),e[q]=j(e[q],h,p),"A"!=m[q]&&"C"==o&&(m[q]="C"),k(e,q),g&&(g[q]&&(o=g[q][0]),"C"!=o&&(n[q]=o,q&&(p=n[q-1])),g[q]=j(g[q],i,p),"A"!=n[q]&&"C"==o&&(n[q]="C"),k(g,q)),l(e,g,h,i,q),l(g,e,i,h,q);var s=e[q],t=g&&g[q],u=s.length,v=g&&t.length;h.x=s[u-2],h.y=s[u-1],h.bx=M(s[u-4])||h.x,h.by=M(s[u-3])||h.y,i.bx=g&&(M(t[v-4])||i.x),i.by=g&&(M(t[v-3])||i.y),i.x=g&&t[v-2],i.y=g&&t[v-1]}return g||(d.curve=f(e)),g?[e,g]:e}function F(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=E(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a}function G(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}var H=b.prototype,I=a.is,J=a._.clone,K="hasOwnProperty",L=/,?([a-z]),?/gi,M=parseFloat,N=Math,O=N.PI,P=N.min,Q=N.max,R=N.pow,S=N.abs,T=h(1),U=h(),V=h(0,1),W=a._unit2px,X={path:function(a){return a.attr("path")},circle:function(a){var b=W(a);return x(b.cx,b.cy,b.r)},ellipse:function(a){var b=W(a);return x(b.cx||0,b.cy||0,b.rx,b.ry)},rect:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height,b.rx,b.ry)},image:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height)},line:function(a){return"M"+[a.attr("x1")||0,a.attr("y1")||0,a.attr("x2"),a.attr("y2")]},polyline:function(a){return"M"+a.attr("points")},polygon:function(a){return"M"+a.attr("points")+"z"},deflt:function(a){var b=a.node.getBBox();return w(b.x,b.y,b.width,b.height)}};a.path=c,a.path.getTotalLength=T,a.path.getPointAtLength=U,a.path.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return V(a,b).end;var d=V(a,c,1);return b?V(d,b).end:d},H.getTotalLength=function(){return this.node.getTotalLength?this.node.getTotalLength():void 0},H.getPointAtLength=function(a){return U(this.attr("d"),a)},H.getSubpath=function(b,c){return a.path.getSubpath(this.attr("d"),b,c)},a._.box=d,a.path.findDotsAtSegment=i,a.path.bezierBBox=j,a.path.isPointInsideBBox=k,a.closest=function(b,c,e,f){for(var g=100,h=d(b-g/2,c-g/2,g,g),i=[],j=e[0].hasOwnProperty("x")?function(a){return{x:e[a].x,y:e[a].y}}:function(a){return{x:e[a],y:f[a]}},l=0;1e6>=g&&!l;){for(var m=0,n=e.length;n>m;m++){var o=j(m);if(k(h,o.x,o.y)){l++,i.push(o);break}}l||(g*=2,h=d(b-g/2,c-g/2,g,g))}if(1e6!=g){var p,q=1/0;for(m=0,n=i.length;n>m;m++){var r=a.len(b,c,i[m].x,i[m].y);q>r&&(q=r,i[m].len=r,p=i[m])}return p}},a.path.isBBoxIntersect=l,a.path.intersection=r,a.path.intersectionNumber=s,a.path.isPointInside=u,a.path.getBBox=v,a.path.get=X,a.path.toRelative=y,a.path.toAbsolute=z,a.path.toCubic=E,a.path.map=F,a.path.toString=e,a.path.clone=f}),d.plugin(function(a){var d=Math.max,e=Math.min,f=function(a){if(this.items=[],this.bindings={},this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)a[b]&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},g=f.prototype;g.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],a&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},g.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},g.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this},g.animate=function(d,e,f,g){"function"!=typeof f||f.length||(g=f,f=c.linear),d instanceof a._.Animation&&(g=d.callback,f=d.easing,e=f.dur,d=d.attr);var h=arguments;if(a.is(d,"array")&&a.is(h[h.length-1],"array"))var i=!0;var j,k=function(){j?this.b=j:j=this.b},l=0,m=this,n=g&&function(){++l==m.length&&g.call(this)
+};return this.forEach(function(a,c){b.once("snap.animcreated."+a.id,k),i?h[c]&&a.animate.apply(a,h[c]):a.animate(d,e,f,n)})},g.remove=function(){for(;this.length;)this.pop().remove();return this},g.bind=function(a,b,c){var d={};if("function"==typeof b)this.bindings[a]=b;else{var e=c||a;this.bindings[a]=function(a){d[e]=a,b.attr(d)}}return this},g.attr=function(a){var b={};for(var c in a)this.bindings[c]?this.bindings[c](a[c]):b[c]=a[c];for(var d=0,e=this.items.length;e>d;d++)this.items[d].attr(b);return this},g.clear=function(){for(;this.length;)this.pop()},g.splice=function(a,b){a=0>a?d(this.length+a,0):a,b=d(0,e(this.length-a,b));var c,g=[],h=[],i=[];for(c=2;c<arguments.length;c++)i.push(arguments[c]);for(c=0;b>c;c++)h.push(this[a+c]);for(;c<this.length-a;c++)g.push(this[a+c]);var j=i.length;for(c=0;c<j+g.length;c++)this.items[a+c]=this[a+c]=j>c?i[c]:g[c-j];for(c=this.items.length=this.length-=b-j;this[c];)delete this[c++];return new f(h)},g.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0;return!1},g.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},g.getBBox=function(){for(var a=[],b=[],c=[],f=[],g=this.items.length;g--;)if(!this.items[g].removed){var h=this.items[g].getBBox();a.push(h.x),b.push(h.y),c.push(h.x+h.width),f.push(h.y+h.height)}return a=e.apply(0,a),b=e.apply(0,b),c=d.apply(0,c),f=d.apply(0,f),{x:a,y:b,x2:c,y2:f,width:c-a,height:f-b,cx:a+(c-a)/2,cy:b+(f-b)/2}},g.clone=function(a){a=new f;for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},g.toString=function(){return"Snap‘s set"},g.type="set",a.Set=f,a.set=function(){var a=new f;return arguments.length&&a.push.apply(a,Array.prototype.slice.call(arguments,0)),a}}),d.plugin(function(a,c){function d(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}}function e(b,c,e){c=p(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];for(var f,g,h,i,l=Math.max(b.length,c.length),m=[],n=[],o=0;l>o;o++){if(h=b[o]||d(c[o]),i=c[o]||d(h),h[0]!=i[0]||"r"==h[0].toLowerCase()&&(h[2]!=i[2]||h[3]!=i[3])||"s"==h[0].toLowerCase()&&(h[3]!=i[3]||h[4]!=i[4])){b=a._.transform2matrix(b,e()),c=a._.transform2matrix(c,e()),m=[["m",b.a,b.b,b.c,b.d,b.e,b.f]],n=[["m",c.a,c.b,c.c,c.d,c.e,c.f]];break}for(m[o]=[],n[o]=[],f=0,g=Math.max(h.length,i.length);g>f;f++)f in h&&(m[o][f]=h[f]),f in i&&(n[o][f]=i[f])}return{from:k(m),to:k(n),f:j(m)}}function f(a){return a}function g(a){return function(b){return+b.toFixed(3)+a}}function h(a){return a.join(" ")}function i(b){return a.rgb(b[0],b[1],b[2])}function j(a){var b,c,d,e,f,g,h=0,i=[];for(b=0,c=a.length;c>b;b++){for(f="[",g=['"'+a[b][0]+'"'],d=1,e=a[b].length;e>d;d++)g[d]="val["+h++ +"]";f+=g+"]",i[b]=f}return Function("val","return Snap.path.toString.call(["+i+"])")}function k(a){for(var b=[],c=0,d=a.length;d>c;c++)for(var e=1,f=a[c].length;f>e;e++)b.push(a[c][e]);return b}function l(a){return isFinite(parseFloat(a))}function m(b,c){return a.is(b,"array")&&a.is(c,"array")?b.toString()==c.toString():!1}var n={},o=/[a-z]+$/i,p=String;n.stroke=n.fill="colour",c.prototype.equal=function(a,c){return b("snap.util.equal",this,a,c).firstDefined()},b.on("snap.util.equal",function(b,c){var d,q,r=p(this.attr(b)||""),s=this;if(l(r)&&l(c))return{from:parseFloat(r),to:parseFloat(c),f:f};if("colour"==n[b])return d=a.color(r),q=a.color(c),{from:[d.r,d.g,d.b,d.opacity],to:[q.r,q.g,q.b,q.opacity],f:i};if("viewBox"==b)return d=this.attr(b).vb.split(" ").map(Number),q=c.split(" ").map(Number),{from:d,to:q,f:h};if("transform"==b||"gradientTransform"==b||"patternTransform"==b)return c instanceof a.Matrix&&(c=c.toTransformString()),a._.rgTransform.test(c)||(c=a._.svgTransform2string(c)),e(r,c,function(){return s.getBBox(1)});if("d"==b||"path"==b)return d=a.path.toCubic(r,c),{from:k(d[0]),to:k(d[1]),f:j(d[0])};if("points"==b)return d=p(r).split(a._.separator),q=p(c).split(a._.separator),{from:d,to:q,f:function(a){return a}};var t=r.match(o),u=p(c).match(o);return t&&m(t,u)?{from:parseFloat(r),to:parseFloat(c),f:g(t)}:{from:this.asPX(b),to:this.asPX(b,c),f:f}})}),d.plugin(function(a,c,d,e){for(var f=c.prototype,g="hasOwnProperty",h=("createTouch"in e.doc),i=["click","dblclick","mousedown","mousemove","mouseout","mouseover","mouseup","touchstart","touchmove","touchend","touchcancel"],j={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},k=(function(a,b){var c="y"==a?"scrollTop":"scrollLeft",d=b&&b.node?b.node.ownerDocument:e.doc;return d[c in d.documentElement?"documentElement":"body"][c]}),l=function(){return this.originalEvent.preventDefault()},m=function(){return this.originalEvent.stopPropagation()},n=function(a,b,c,d){var e=h&&j[b]?j[b]:b,f=function(e){var f=k("y",d),i=k("x",d);if(h&&j[g](b))for(var n=0,o=e.targetTouches&&e.targetTouches.length;o>n;n++)if(e.targetTouches[n].target==a||a.contains(e.targetTouches[n].target)){var p=e;e=e.targetTouches[n],e.originalEvent=p,e.preventDefault=l,e.stopPropagation=m;break}var q=e.clientX+i,r=e.clientY+f;return c.call(d,e,q,r)};return b!==e&&a.addEventListener(b,f,!1),a.addEventListener(e,f,!1),function(){return b!==e&&a.removeEventListener(b,f,!1),a.removeEventListener(e,f,!1),!0}},o=[],p=function(a){for(var c,d=a.clientX,e=a.clientY,f=k("y"),g=k("x"),i=o.length;i--;){if(c=o[i],h){for(var j,l=a.touches&&a.touches.length;l--;)if(j=a.touches[l],j.identifier==c.el._drag.id||c.el.node.contains(j.target)){d=j.clientX,e=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();{var m=c.el.node;m.nextSibling,m.parentNode,m.style.display}d+=g,e+=f,b("snap.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},q=function(c){a.unmousemove(p).unmouseup(q);for(var d,e=o.length;e--;)d=o[e],d.el._drag={},b("snap.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c),b.off("snap.drag.*."+d.el.id);o=[]},r=i.length;r--;)!function(b){a[b]=f[b]=function(c,d){if(a.is(c,"function"))this.events=this.events||[],this.events.push({name:b,f:c,unbind:n(this.node||document,b,c,d||this)});else for(var e=0,f=this.events.length;f>e;e++)if(this.events[e].name==b)try{this.events[e].f.call(this)}catch(g){}return this},a["un"+b]=f["un"+b]=function(a){for(var c=this.events||[],d=c.length;d--;)if(c[d].name==b&&(c[d].f==a||!a))return c[d].unbind(),c.splice(d,1),!c.length&&delete this.events,this;return this}}(i[r]);f.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},f.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var s=[];f.drag=function(c,d,e,f,g,h){function i(i,j,l){(i.originalEvent||i).preventDefault(),k._drag.x=j,k._drag.y=l,k._drag.id=i.identifier,!o.length&&a.mousemove(p).mouseup(q),o.push({el:k,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("snap.drag.start."+k.id,d),c&&b.on("snap.drag.move."+k.id,c),e&&b.on("snap.drag.end."+k.id,e),b("snap.drag.start."+k.id,g||f||k,j,l,i)}function j(a,c,d){b("snap.draginit."+k.id,k,a,c,d)}var k=this;if(!arguments.length){var l;return k.drag(function(a,b){this.attr({transform:l+(l?"T":"t")+[a,b]})},function(){l=this.transform().local})}return b.on("snap.draginit."+k.id,i),k._drag={},s.push({el:k,start:i,init:j}),k.mousedown(j),k},f.undrag=function(){for(var c=s.length;c--;)s[c].el==this&&(this.unmousedown(s[c].init),s.splice(c,1),b.unbind("snap.drag.*."+this.id),b.unbind("snap.draginit."+this.id));return!s.length&&a.unmousemove(p).unmouseup(q),this}}),d.plugin(function(a,c,d){var e=(c.prototype,d.prototype),f=/^\s*url\((.+)\)/,g=String,h=a._.$;a.filter={},e.filter=function(b){var d=this;"svg"!=d.type&&(d=d.paper);var e=a.parse(g(b)),f=a._.id(),i=(d.node.offsetWidth,d.node.offsetHeight,h("filter"));return h(i,{id:f,filterUnits:"userSpaceOnUse"}),i.appendChild(e.node),d.defs.appendChild(i),new c(i)},b.on("snap.util.getattr.filter",function(){b.stop();var c=h(this.node,"filter");if(c){var d=g(c).match(f);return d&&a.select(d[1])}}),b.on("snap.util.attr.filter",function(d){if(d instanceof c&&"filter"==d.type){b.stop();var e=d.node.id;e||(h(d.node,{id:d.id}),e=d.id),h(this.node,{filter:a.url(e)})}d&&"none"!=d||(b.stop(),this.node.removeAttribute("filter"))}),a.filter.blur=function(b,c){null==b&&(b=2);var d=null==c?b:[b,c];return a.format('<feGaussianBlur stdDeviation="{def}"/>',{def:d})},a.filter.blur.toString=function(){return this()},a.filter.shadow=function(b,c,d,e,f){return"string"==typeof d&&(e=d,f=e,d=4),"string"!=typeof e&&(f=e,e="#000"),e=e||"#000",null==d&&(d=4),null==f&&(f=1),null==b&&(b=0,c=2),null==c&&(c=b),e=a.color(e),a.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>',{color:e,dx:b,dy:c,blur:d,opacity:f})},a.filter.shadow.toString=function(){return this()},a.filter.grayscale=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>',{a:.2126+.7874*(1-b),b:.7152-.7152*(1-b),c:.0722-.0722*(1-b),d:.2126-.2126*(1-b),e:.7152+.2848*(1-b),f:.0722-.0722*(1-b),g:.2126-.2126*(1-b),h:.0722+.9278*(1-b)})},a.filter.grayscale.toString=function(){return this()},a.filter.sepia=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>',{a:.393+.607*(1-b),b:.769-.769*(1-b),c:.189-.189*(1-b),d:.349-.349*(1-b),e:.686+.314*(1-b),f:.168-.168*(1-b),g:.272-.272*(1-b),h:.534-.534*(1-b),i:.131+.869*(1-b)})},a.filter.sepia.toString=function(){return this()},a.filter.saturate=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="saturate" values="{amount}"/>',{amount:1-b})},a.filter.saturate.toString=function(){return this()},a.filter.hueRotate=function(b){return b=b||0,a.format('<feColorMatrix type="hueRotate" values="{angle}"/>',{angle:b})},a.filter.hueRotate.toString=function(){return this()},a.filter.invert=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>',{amount:b,amount2:1-b})},a.filter.invert.toString=function(){return this()},a.filter.brightness=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>',{amount:b})},a.filter.brightness.toString=function(){return this()},a.filter.contrast=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>',{amount:b,amount2:.5-b/2})},a.filter.contrast.toString=function(){return this()}}),d.plugin(function(a,b){var c=a._.box,d=a.is,e=/^[^a-z]*([tbmlrc])/i,f=function(){return"T"+this.dx+","+this.dy};b.prototype.getAlign=function(a,b){null==b&&d(a,"string")&&(b=a,a=null),a=a||this.paper;var g=a.getBBox?a.getBBox():c(a),h=this.getBBox(),i={};switch(b=b&&b.match(e),b=b?b[1].toLowerCase():"c"){case"t":i.dx=0,i.dy=g.y-h.y;break;case"b":i.dx=0,i.dy=g.y2-h.y2;break;case"m":i.dx=0,i.dy=g.cy-h.cy;break;case"l":i.dx=g.x-h.x,i.dy=0;break;case"r":i.dx=g.x2-h.x2,i.dy=0;break;default:i.dx=g.cx-h.cx,i.dy=0}return i.toString=f,i},b.prototype.align=function(a,b){return this.transform("..."+this.getAlign(a,b))}}),d});
diff --git a/web/pgadmin/misc/static/explain/js/snap.svg.js b/web/pgadmin/misc/static/explain/js/snap.svg.js
new file mode 100644
index 0000000..ef0fb6d
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg.js
@@ -0,0 +1,8149 @@
+// Snap.svg 0.4.1
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// build: 2015-04-13
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ┌────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.4.2 - JavaScript Events Library                      │ \\
+// ├────────────────────────────────────────────────────────────┤ \\
+// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+// └────────────────────────────────────────────────────────────┘ \\
+(function (glob) {
+    var version = "0.4.2",
+        has = "hasOwnProperty",
+        separator = /[\.\/]/,
+        comaseparator = /\s*,\s*/,
+        wildcard = "*",
+        fun = function () {},
+        numsort = function (a, b) {
+            return a - b;
+        },
+        current_event,
+        stop,
+        events = {n: {}},
+        firstDefined = function () {
+            for (var i = 0, ii = this.length; i < ii; i++) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+        lastDefined = function () {
+            var i = this.length;
+            while (--i) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+    /*\
+     * eve
+     [ method ]
+     * Fires event with given `name`, given scope and other parameters.
+     > Arguments
+     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
+     - scope (object) context for the event handlers
+     - varargs (...) the rest of arguments will be sent to event handlers
+     = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value.
+    \*/
+        eve = function (name, scope) {
+            name = String(name);
+            var e = events,
+                oldstop = stop,
+                args = Array.prototype.slice.call(arguments, 2),
+                listeners = eve.listeners(name),
+                z = 0,
+                f = false,
+                l,
+                indexed = [],
+                queue = {},
+                out = [],
+                ce = current_event,
+                errors = [];
+            out.firstDefined = firstDefined;
+            out.lastDefined = lastDefined;
+            current_event = name;
+            stop = 0;
+            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                indexed.push(listeners[i].zIndex);
+                if (listeners[i].zIndex < 0) {
+                    queue[listeners[i].zIndex] = listeners[i];
+                }
+            }
+            indexed.sort(numsort);
+            while (indexed[z] < 0) {
+                l = queue[indexed[z++]];
+                out.push(l.apply(scope, args));
+                if (stop) {
+                    stop = oldstop;
+                    return out;
+                }
+            }
+            for (i = 0; i < ii; i++) {
+                l = listeners[i];
+                if ("zIndex" in l) {
+                    if (l.zIndex == indexed[z]) {
+                        out.push(l.apply(scope, args));
+                        if (stop) {
+                            break;
+                        }
+                        do {
+                            z++;
+                            l = queue[indexed[z]];
+                            l && out.push(l.apply(scope, args));
+                            if (stop) {
+                                break;
+                            }
+                        } while (l)
+                    } else {
+                        queue[l.zIndex] = l;
+                    }
+                } else {
+                    out.push(l.apply(scope, args));
+                    if (stop) {
+                        break;
+                    }
+                }
+            }
+            stop = oldstop;
+            current_event = ce;
+            return out;
+        };
+        // Undocumented. Debug only.
+        eve._events = events;
+    /*\
+     * eve.listeners
+     [ method ]
+     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
+     > Arguments
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated
+     = (array) array of event handlers
+    \*/
+    eve.listeners = function (name) {
+        var names = name.split(separator),
+            e = events,
+            item,
+            items,
+            k,
+            i,
+            ii,
+            j,
+            jj,
+            nes,
+            es = [e],
+            out = [];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            nes = [];
+            for (j = 0, jj = es.length; j < jj; j++) {
+                e = es[j].n;
+                items = [e[names[i]], e[wildcard]];
+                k = 2;
+                while (k--) {
+                    item = items[k];
+                    if (item) {
+                        nes.push(item);
+                        out = out.concat(item.f || []);
+                    }
+                }
+            }
+            es = nes;
+        }
+        return out;
+    };
+    /*\
+     * eve.on
+     [ method ]
+     **
+     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
+     | eve.on("*.under.*", f);
+     | eve("mouse.under.floor"); // triggers f
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
+     > Example:
+     | eve.on("mouse", eatIt)(2);
+     | eve.on("mouse", scream);
+     | eve.on("mouse", catchIt)(1);
+     * This will ensure that `catchIt` function will be called before `eatIt`.
+     *
+     * If you want to put your handler before non-indexed handlers, specify a negative value.
+     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
+    \*/
+    eve.on = function (name, f) {
+        name = String(name);
+        if (typeof f != "function") {
+            return function () {};
+        }
+        var names = name.split(comaseparator);
+        for (var i = 0, ii = names.length; i < ii; i++) {
+            (function (name) {
+                var names = name.split(separator),
+                    e = events,
+                    exist;
+                for (var i = 0, ii = names.length; i < ii; i++) {
+                    e = e.n;
+                    e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
+                }
+                e.f = e.f || [];
+                for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+                    exist = true;
+                    break;
+                }
+                !exist && e.f.push(f);
+            }(names[i]));
+        }
+        return function (zIndex) {
+            if (+zIndex == +zIndex) {
+                f.zIndex = +zIndex;
+            }
+        };
+    };
+    /*\
+     * eve.f
+     [ method ]
+     **
+     * Returns function that will fire given event with optional arguments.
+     * Arguments that will be passed to the result function will be also
+     * concated to the list of final arguments.
+     | el.onclick = eve.f("click", 1, 2);
+     | eve.on("click", function (a, b, c) {
+     |     console.log(a, b, c); // 1, 2, [event object]
+     | });
+     > Arguments
+     - event (string) event name
+     - varargs (…) and any other arguments
+     = (function) possible event handler function
+    \*/
+    eve.f = function (event) {
+        var attrs = [].slice.call(arguments, 1);
+        return function () {
+            eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
+        };
+    };
+    /*\
+     * eve.stop
+     [ method ]
+     **
+     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
+    \*/
+    eve.stop = function () {
+        stop = 1;
+    };
+    /*\
+     * eve.nt
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     > Arguments
+     **
+     - subname (string) #optional subname of the event
+     **
+     = (string) name of the event, if `subname` is not specified
+     * or
+     = (boolean) `true`, if current event’s name contains `subname`
+    \*/
+    eve.nt = function (subname) {
+        if (subname) {
+            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+        }
+        return current_event;
+    };
+    /*\
+     * eve.nts
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     **
+     = (array) names of the event
+    \*/
+    eve.nts = function () {
+        return current_event.split(separator);
+    };
+    /*\
+     * eve.off
+     [ method ]
+     **
+     * Removes given function from the list of event listeners assigned to given name.
+     * If no arguments specified all the events will be cleared.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+    \*/
+    /*\
+     * eve.unbind
+     [ method ]
+     **
+     * See @eve.off
+    \*/
+    eve.off = eve.unbind = function (name, f) {
+        if (!name) {
+            eve._events = events = {n: {}};
+            return;
+        }
+        var names = name.split(comaseparator);
+        if (names.length > 1) {
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                eve.off(names[i], f);
+            }
+            return;
+        }
+        names = name.split(separator);
+        var e,
+            key,
+            splice,
+            i, ii, j, jj,
+            cur = [events];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
+                splice = [j, 1];
+                e = cur[j].n;
+                if (names[i] != wildcard) {
+                    if (e[names[i]]) {
+                        splice.push(e[names[i]]);
+                    }
+                } else {
+                    for (key in e) if (e[has](key)) {
+                        splice.push(e[key]);
+                    }
+                }
+                cur.splice.apply(cur, splice);
+            }
+        }
+        for (i = 0, ii = cur.length; i < ii; i++) {
+            e = cur[i];
+            while (e.n) {
+                if (f) {
+                    if (e.f) {
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
+                            break;
+                        }
+                        !e.f.length && delete e.f;
+                    }
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        var funcs = e.n[key].f;
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
+                            break;
+                        }
+                        !funcs.length && delete e.n[key].f;
+                    }
+                } else {
+                    delete e.f;
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        delete e.n[key].f;
+                    }
+                }
+                e = e.n;
+            }
+        }
+    };
+    /*\
+     * eve.once
+     [ method ]
+     **
+     * Binds given event handler with a given name to only run once then unbind itself.
+     | eve.once("login", f);
+     | eve("login"); // triggers f
+     | eve("login"); // no listeners
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) same return function as @eve.on
+    \*/
+    eve.once = function (name, f) {
+        var f2 = function () {
+            eve.unbind(name, f2);
+            return f.apply(this, arguments);
+        };
+        return eve.on(name, f2);
+    };
+    /*\
+     * eve.version
+     [ property (string) ]
+     **
+     * Current version of the library.
+    \*/
+    eve.version = version;
+    eve.toString = function () {
+        return "You are running Eve " + version;
+    };
+    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
+})(this);
+
+(function (glob, factory) {
+    // AMD support
+    if (typeof define == "function" && define.amd) {
+        // Define as an anonymous module
+        define(["eve"], function (eve) {
+            return factory(glob, eve);
+        });
+    } else if (typeof exports != 'undefined') {
+        // Next for Node.js or CommonJS
+        var eve = require('eve');
+        module.exports = factory(glob, eve);
+    } else {
+        // Browser globals (glob is window)
+        // Snap adds itself to window
+        factory(glob, glob.eve);
+    }
+}(window || this, function (window, eve) {
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+var mina = (function (eve) {
+    var animations = {},
+    requestAnimFrame = window.requestAnimationFrame       ||
+                       window.webkitRequestAnimationFrame ||
+                       window.mozRequestAnimationFrame    ||
+                       window.oRequestAnimationFrame      ||
+                       window.msRequestAnimationFrame     ||
+                       function (callback) {
+                           setTimeout(callback, 16);
+                       },
+    isArray = Array.isArray || function (a) {
+        return a instanceof Array ||
+            Object.prototype.toString.call(a) == "[object Array]";
+    },
+    idgen = 0,
+    idprefix = "M" + (+new Date).toString(36),
+    ID = function () {
+        return idprefix + (idgen++).toString(36);
+    },
+    diff = function (a, b, A, B) {
+        if (isArray(a)) {
+            res = [];
+            for (var i = 0, ii = a.length; i < ii; i++) {
+                res[i] = diff(a[i], b, A[i], B);
+            }
+            return res;
+        }
+        var dif = (A - a) / (B - b);
+        return function (bb) {
+            return a + dif * (bb - b);
+        };
+    },
+    timer = Date.now || function () {
+        return +new Date;
+    },
+    sta = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.s;
+        }
+        var ds = a.s - val;
+        a.b += a.dur * ds;
+        a.B += a.dur * ds;
+        a.s = val;
+    },
+    speed = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.spd;
+        }
+        a.spd = val;
+    },
+    duration = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.dur;
+        }
+        a.s = a.s * val / a.dur;
+        a.dur = val;
+    },
+    stopit = function () {
+        var a = this;
+        delete animations[a.id];
+        a.update();
+        eve("mina.stop." + a.id, a);
+    },
+    pause = function () {
+        var a = this;
+        if (a.pdif) {
+            return;
+        }
+        delete animations[a.id];
+        a.update();
+        a.pdif = a.get() - a.b;
+    },
+    resume = function () {
+        var a = this;
+        if (!a.pdif) {
+            return;
+        }
+        a.b = a.get() - a.pdif;
+        delete a.pdif;
+        animations[a.id] = a;
+    },
+    update = function () {
+        var a = this,
+            res;
+        if (isArray(a.start)) {
+            res = [];
+            for (var j = 0, jj = a.start.length; j < jj; j++) {
+                res[j] = +a.start[j] +
+                    (a.end[j] - a.start[j]) * a.easing(a.s);
+            }
+        } else {
+            res = +a.start + (a.end - a.start) * a.easing(a.s);
+        }
+        a.set(res);
+    },
+    frame = function () {
+        var len = 0;
+        for (var i in animations) if (animations.hasOwnProperty(i)) {
+            var a = animations[i],
+                b = a.get(),
+                res;
+            len++;
+            a.s = (b - a.b) / (a.dur / a.spd);
+            if (a.s >= 1) {
+                delete animations[i];
+                a.s = 1;
+                len--;
+                (function (a) {
+                    setTimeout(function () {
+                        eve("mina.finish." + a.id, a);
+                    });
+                }(a));
+            }
+            a.update();
+        }
+        len && requestAnimFrame(frame);
+    },
+    /*\
+     * mina
+     [ method ]
+     **
+     * Generic animation of numbers
+     **
+     - a (number) start _slave_ number
+     - A (number) end _slave_ number
+     - b (number) start _master_ number (start time in general case)
+     - B (number) end _master_ number (end time in gereal case)
+     - get (function) getter of _master_ number (see @mina.time)
+     - set (function) setter of _slave_ number
+     - easing (function) #optional easing function, default is @mina.linear
+     = (object) animation descriptor
+     o {
+     o         id (string) animation id,
+     o         start (number) start _slave_ number,
+     o         end (number) end _slave_ number,
+     o         b (number) start _master_ number,
+     o         s (number) animation status (0..1),
+     o         dur (number) animation duration,
+     o         spd (number) animation speed,
+     o         get (function) getter of _master_ number (see @mina.time),
+     o         set (function) setter of _slave_ number,
+     o         easing (function) easing function, default is @mina.linear,
+     o         status (function) status getter/setter,
+     o         speed (function) speed getter/setter,
+     o         duration (function) duration getter/setter,
+     o         stop (function) animation stopper
+     o         pause (function) pauses the animation
+     o         resume (function) resumes the animation
+     o         update (function) calles setter with the right value of the animation
+     o }
+    \*/
+    mina = function (a, A, b, B, get, set, easing) {
+        var anim = {
+            id: ID(),
+            start: a,
+            end: A,
+            b: b,
+            s: 0,
+            dur: B - b,
+            spd: 1,
+            get: get,
+            set: set,
+            easing: easing || mina.linear,
+            status: sta,
+            speed: speed,
+            duration: duration,
+            stop: stopit,
+            pause: pause,
+            resume: resume,
+            update: update
+        };
+        animations[anim.id] = anim;
+        var len = 0, i;
+        for (i in animations) if (animations.hasOwnProperty(i)) {
+            len++;
+            if (len == 2) {
+                break;
+            }
+        }
+        len == 1 && requestAnimFrame(frame);
+        return anim;
+    };
+    /*\
+     * mina.time
+     [ method ]
+     **
+     * Returns the current time. Equivalent to:
+     | function () {
+     |     return (new Date).getTime();
+     | }
+    \*/
+    mina.time = timer;
+    /*\
+     * mina.getById
+     [ method ]
+     **
+     * Returns an animation by its id
+     - id (string) animation's id
+     = (object) See @mina
+    \*/
+    mina.getById = function (id) {
+        return animations[id] || null;
+    };
+
+    /*\
+     * mina.linear
+     [ method ]
+     **
+     * Default linear easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.linear = function (n) {
+        return n;
+    };
+    /*\
+     * mina.easeout
+     [ method ]
+     **
+     * Easeout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeout = function (n) {
+        return Math.pow(n, 1.7);
+    };
+    /*\
+     * mina.easein
+     [ method ]
+     **
+     * Easein easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easein = function (n) {
+        return Math.pow(n, .48);
+    };
+    /*\
+     * mina.easeinout
+     [ method ]
+     **
+     * Easeinout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeinout = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        if (n == 0) {
+            return 0;
+        }
+        var q = .48 - n / 1.04,
+            Q = Math.sqrt(.1734 + q * q),
+            x = Q - q,
+            X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+            y = -Q - q,
+            Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+            t = X + Y + .5;
+        return (1 - t) * 3 * t * t + t * t * t;
+    };
+    /*\
+     * mina.backin
+     [ method ]
+     **
+     * Backin easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backin = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        var s = 1.70158;
+        return n * n * ((s + 1) * n - s);
+    };
+    /*\
+     * mina.backout
+     [ method ]
+     **
+     * Backout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backout = function (n) {
+        if (n == 0) {
+            return 0;
+        }
+        n = n - 1;
+        var s = 1.70158;
+        return n * n * ((s + 1) * n + s) + 1;
+    };
+    /*\
+     * mina.elastic
+     [ method ]
+     **
+     * Elastic easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.elastic = function (n) {
+        if (n == !!n) {
+            return n;
+        }
+        return Math.pow(2, -10 * n) * Math.sin((n - .075) *
+            (2 * Math.PI) / .3) + 1;
+    };
+    /*\
+     * mina.bounce
+     [ method ]
+     **
+     * Bounce easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.bounce = function (n) {
+        var s = 7.5625,
+            p = 2.75,
+            l;
+        if (n < (1 / p)) {
+            l = s * n * n;
+        } else {
+            if (n < (2 / p)) {
+                n -= (1.5 / p);
+                l = s * n * n + .75;
+            } else {
+                if (n < (2.5 / p)) {
+                    n -= (2.25 / p);
+                    l = s * n * n + .9375;
+                } else {
+                    n -= (2.625 / p);
+                    l = s * n * n + .984375;
+                }
+            }
+        }
+        return l;
+    };
+    window.mina = mina;
+    return mina;
+})(typeof eve == "undefined" ? function () {} : eve);
+// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var Snap = (function(root) {
+Snap.version = "0.4.0";
+/*\
+ * Snap
+ [ method ]
+ **
+ * Creates a drawing surface or wraps existing SVG element.
+ **
+ - width (number|string) width of surface
+ - height (number|string) height of surface
+ * or
+ - DOM (SVGElement) element to be wrapped into Snap structure
+ * or
+ - array (array) array of elements (will return set of elements)
+ * or
+ - query (string) CSS query selector
+ = (object) @Element
+\*/
+function Snap(w, h) {
+    if (w) {
+        if (w.nodeType) {
+            return wrap(w);
+        }
+        if (is(w, "array") && Snap.set) {
+            return Snap.set.apply(Snap, w);
+        }
+        if (w instanceof Element) {
+            return w;
+        }
+        if (h == null) {
+            w = glob.doc.querySelector(String(w));
+            return wrap(w);
+        }
+    }
+    w = w == null ? "100%" : w;
+    h = h == null ? "100%" : h;
+    return new Paper(w, h);
+}
+Snap.toString = function () {
+    return "Snap v" + this.version;
+};
+Snap._ = {};
+var glob = {
+    win: root.window,
+    doc: root.window.document
+};
+Snap._.glob = glob;
+var has = "hasOwnProperty",
+    Str = String,
+    toFloat = parseFloat,
+    toInt = parseInt,
+    math = Math,
+    mmax = math.max,
+    mmin = math.min,
+    abs = math.abs,
+    pow = math.pow,
+    PI = math.PI,
+    round = math.round,
+    E = "",
+    S = " ",
+    objectToString = Object.prototype.toString,
+    ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+    colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,
+    bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+    reURLValue = /^url\(#?([^)]+)\)$/,
+    separator = Snap._.separator = /[,\s]+/,
+    whitespace = /[\s]/g,
+    commaSpaces = /[\s]*,[\s]*/,
+    hsrg = {hs: 1, rg: 1},
+    pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig,
+    idgen = 0,
+    idprefix = "S" + (+new Date).toString(36),
+    ID = function (el) {
+        return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36);
+    },
+    xlink = "http://www.w3.org/1999/xlink",
+    xmlns = "http://www.w3.org/2000/svg",
+    hub = {},
+    URL = Snap.url = function (url) {
+        return "url('#" + url + "')";
+    };
+
+function $(el, attr) {
+    if (attr) {
+        if (el == "#text") {
+            el = glob.doc.createTextNode(attr.text || attr["#text"] || "");
+        }
+        if (el == "#comment") {
+            el = glob.doc.createComment(attr.text || attr["#text"] || "");
+        }
+        if (typeof el == "string") {
+            el = $(el);
+        }
+        if (typeof attr == "string") {
+            if (el.nodeType == 1) {
+                if (attr.substring(0, 6) == "xlink:") {
+                    return el.getAttributeNS(xlink, attr.substring(6));
+                }
+                if (attr.substring(0, 4) == "xml:") {
+                    return el.getAttributeNS(xmlns, attr.substring(4));
+                }
+                return el.getAttribute(attr);
+            } else if (attr == "text") {
+                return el.nodeValue;
+            } else {
+                return null;
+            }
+        }
+        if (el.nodeType == 1) {
+            for (var key in attr) if (attr[has](key)) {
+                var val = Str(attr[key]);
+                if (val) {
+                    if (key.substring(0, 6) == "xlink:") {
+                        el.setAttributeNS(xlink, key.substring(6), val);
+                    } else if (key.substring(0, 4) == "xml:") {
+                        el.setAttributeNS(xmlns, key.substring(4), val);
+                    } else {
+                        el.setAttribute(key, val);
+                    }
+                } else {
+                    el.removeAttribute(key);
+                }
+            }
+        } else if ("text" in attr) {
+            el.nodeValue = attr.text;
+        }
+    } else {
+        el = glob.doc.createElementNS(xmlns, el);
+    }
+    return el;
+}
+Snap._.$ = $;
+Snap._.id = ID;
+function getAttrs(el) {
+    var attrs = el.attributes,
+        name,
+        out = {};
+    for (var i = 0; i < attrs.length; i++) {
+        if (attrs[i].namespaceURI == xlink) {
+            name = "xlink:";
+        } else {
+            name = "";
+        }
+        name += attrs[i].name;
+        out[name] = attrs[i].textContent;
+    }
+    return out;
+}
+function is(o, type) {
+    type = Str.prototype.toLowerCase.call(type);
+    if (type == "finite") {
+        return isFinite(o);
+    }
+    if (type == "array" &&
+        (o instanceof Array || Array.isArray && Array.isArray(o))) {
+        return true;
+    }
+    return  (type == "null" && o === null) ||
+            (type == typeof o && o !== null) ||
+            (type == "object" && o === Object(o)) ||
+            objectToString.call(o).slice(8, -1).toLowerCase() == type;
+}
+/*\
+ * Snap.format
+ [ method ]
+ **
+ * Replaces construction of type `{<name>}` to the corresponding argument
+ **
+ - token (string) string to format
+ - json (object) object which properties are used as a replacement
+ = (string) formatted string
+ > Usage
+ | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z"
+ | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
+ |     x: 10,
+ |     y: 20,
+ |     dim: {
+ |         width: 40,
+ |         height: 50,
+ |         "negative width": -40
+ |     }
+ | }));
+\*/
+Snap.format = (function () {
+    var tokenRegex = /\{([^\}]+)\}/g,
+        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+        replacer = function (all, key, obj) {
+            var res = obj;
+            key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+                name = name || quotedName;
+                if (res) {
+                    if (name in res) {
+                        res = res[name];
+                    }
+                    typeof res == "function" && isFunc && (res = res());
+                }
+            });
+            res = (res == null || res == obj ? all : res) + "";
+            return res;
+        };
+    return function (str, obj) {
+        return Str(str).replace(tokenRegex, function (all, key) {
+            return replacer(all, key, obj);
+        });
+    };
+})();
+function clone(obj) {
+    if (typeof obj == "function" || Object(obj) !== obj) {
+        return obj;
+    }
+    var res = new obj.constructor;
+    for (var key in obj) if (obj[has](key)) {
+        res[key] = clone(obj[key]);
+    }
+    return res;
+}
+Snap._.clone = clone;
+function repush(array, item) {
+    for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+        return array.push(array.splice(i, 1)[0]);
+    }
+}
+function cacher(f, scope, postprocessor) {
+    function newf() {
+        var arg = Array.prototype.slice.call(arguments, 0),
+            args = arg.join("\u2400"),
+            cache = newf.cache = newf.cache || {},
+            count = newf.count = newf.count || [];
+        if (cache[has](args)) {
+            repush(count, args);
+            return postprocessor ? postprocessor(cache[args]) : cache[args];
+        }
+        count.length >= 1e3 && delete cache[count.shift()];
+        count.push(args);
+        cache[args] = f.apply(scope, arg);
+        return postprocessor ? postprocessor(cache[args]) : cache[args];
+    }
+    return newf;
+}
+Snap._.cacher = cacher;
+function angle(x1, y1, x2, y2, x3, y3) {
+    if (x3 == null) {
+        var x = x1 - x2,
+            y = y1 - y2;
+        if (!x && !y) {
+            return 0;
+        }
+        return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+    } else {
+        return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3);
+    }
+}
+function rad(deg) {
+    return deg % 360 * PI / 180;
+}
+function deg(rad) {
+    return rad * 180 / PI % 360;
+}
+function x_y() {
+    return this.x + S + this.y;
+}
+function x_y_w_h() {
+    return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+}
+
+/*\
+ * Snap.rad
+ [ method ]
+ **
+ * Transform angle to radians
+ - deg (number) angle in degrees
+ = (number) angle in radians
+\*/
+Snap.rad = rad;
+/*\
+ * Snap.deg
+ [ method ]
+ **
+ * Transform angle to degrees
+ - rad (number) angle in radians
+ = (number) angle in degrees
+\*/
+Snap.deg = deg;
+/*\
+ * Snap.sin
+ [ method ]
+ **
+ * Equivalent to `Math.sin()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) sin
+\*/
+Snap.sin = function (angle) {
+    return math.sin(Snap.rad(angle));
+};
+/*\
+ * Snap.tan
+ [ method ]
+ **
+ * Equivalent to `Math.tan()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) tan
+\*/
+Snap.tan = function (angle) {
+    return math.tan(Snap.rad(angle));
+};
+/*\
+ * Snap.cos
+ [ method ]
+ **
+ * Equivalent to `Math.cos()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) cos
+\*/
+Snap.cos = function (angle) {
+    return math.cos(Snap.rad(angle));
+};
+/*\
+ * Snap.asin
+ [ method ]
+ **
+ * Equivalent to `Math.asin()` only works with degrees, not radians.
+ - num (number) value
+ = (number) asin in degrees
+\*/
+Snap.asin = function (num) {
+    return Snap.deg(math.asin(num));
+};
+/*\
+ * Snap.acos
+ [ method ]
+ **
+ * Equivalent to `Math.acos()` only works with degrees, not radians.
+ - num (number) value
+ = (number) acos in degrees
+\*/
+Snap.acos = function (num) {
+    return Snap.deg(math.acos(num));
+};
+/*\
+ * Snap.atan
+ [ method ]
+ **
+ * Equivalent to `Math.atan()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan in degrees
+\*/
+Snap.atan = function (num) {
+    return Snap.deg(math.atan(num));
+};
+/*\
+ * Snap.atan2
+ [ method ]
+ **
+ * Equivalent to `Math.atan2()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan2 in degrees
+\*/
+Snap.atan2 = function (num) {
+    return Snap.deg(math.atan2(num));
+};
+/*\
+ * Snap.angle
+ [ method ]
+ **
+ * Returns an angle between two or three points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ - x3 (number) #optional x coord of third point
+ - y3 (number) #optional y coord of third point
+ = (number) angle in degrees
+\*/
+Snap.angle = angle;
+/*\
+ * Snap.len
+ [ method ]
+ **
+ * Returns distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len = function (x1, y1, x2, y2) {
+    return Math.sqrt(Snap.len2(x1, y1, x2, y2));
+};
+/*\
+ * Snap.len2
+ [ method ]
+ **
+ * Returns squared distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len2 = function (x1, y1, x2, y2) {
+    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
+};
+/*\
+ * Snap.closestPoint
+ [ method ]
+ **
+ * Returns closest point to a given one on a given path.
+ > Parameters
+ - path (Element) path element
+ - x (number) x coord of a point
+ - y (number) y coord of a point
+ = (object) in format
+ {
+    x (number) x coord of the point on the path
+    y (number) y coord of the point on the path
+    length (number) length of the path to the point
+    distance (number) distance from the given point to the path
+ }
+\*/
+// Copied from http://bl.ocks.org/mbostock/8027637
+Snap.closestPoint = function (path, x, y) {
+    function distance2(p) {
+        var dx = p.x - x,
+            dy = p.y - y;
+        return dx * dx + dy * dy;
+    }
+    var pathNode = path.node,
+        pathLength = pathNode.getTotalLength(),
+        precision = pathLength / pathNode.pathSegList.numberOfItems * .125,
+        best,
+        bestLength,
+        bestDistance = Infinity;
+
+    // linear scan for coarse approximation
+    for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
+        if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
+            best = scan, bestLength = scanLength, bestDistance = scanDistance;
+        }
+    }
+
+    // binary search for precise estimate
+    precision *= .5;
+    while (precision > .5) {
+        var before,
+            after,
+            beforeLength,
+            afterLength,
+            beforeDistance,
+            afterDistance;
+        if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
+            best = before, bestLength = beforeLength, bestDistance = beforeDistance;
+        } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
+            best = after, bestLength = afterLength, bestDistance = afterDistance;
+        } else {
+            precision *= .5;
+        }
+    }
+
+    best = {
+        x: best.x,
+        y: best.y,
+        length: bestLength,
+        distance: Math.sqrt(bestDistance)
+    };
+    return best;
+}
+/*\
+ * Snap.is
+ [ method ]
+ **
+ * Handy replacement for the `typeof` operator
+ - o (…) any object or primitive
+ - type (string) name of the type, e.g., `string`, `function`, `number`, etc.
+ = (boolean) `true` if given value is of given type
+\*/
+Snap.is = is;
+/*\
+ * Snap.snapTo
+ [ method ]
+ **
+ * Snaps given value to given grid
+ - values (array|number) given array of values or step of the grid
+ - value (number) value to adjust
+ - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`.
+ = (number) adjusted value
+\*/
+Snap.snapTo = function (values, value, tolerance) {
+    tolerance = is(tolerance, "finite") ? tolerance : 10;
+    if (is(values, "array")) {
+        var i = values.length;
+        while (i--) if (abs(values[i] - value) <= tolerance) {
+            return values[i];
+        }
+    } else {
+        values = +values;
+        var rem = value % values;
+        if (rem < tolerance) {
+            return value - rem;
+        }
+        if (rem > values - tolerance) {
+            return value - rem + values;
+        }
+    }
+    return value;
+};
+// Colour
+/*\
+ * Snap.getRGB
+ [ method ]
+ **
+ * Parses color string as RGB object
+ - color (string) color string in one of the following formats:
+ # <ul>
+ #     <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li>
+ #     <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li>
+ #     <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li>
+ #     <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200,&nbsp;100,&nbsp;0)</code>)</li>
+ #     <li>rgba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li>
+ #     <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>)</li>
+ #     <li>hsba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;0.5)</code>)</li>
+ #     <li>hsla(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li>
+ # </ul>
+ * Note that `%` can be used any time: `rgb(20%, 255, 50%)`.
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) true if string can't be parsed
+ o }
+\*/
+Snap.getRGB = cacher(function (colour) {
+    if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    if (colour == "none") {
+        return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString};
+    }
+    !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+    if (!colour) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    var res,
+        red,
+        green,
+        blue,
+        opacity,
+        t,
+        values,
+        rgb = colour.match(colourRegExp);
+    if (rgb) {
+        if (rgb[2]) {
+            blue = toInt(rgb[2].substring(5), 16);
+            green = toInt(rgb[2].substring(3, 5), 16);
+            red = toInt(rgb[2].substring(1, 3), 16);
+        }
+        if (rgb[3]) {
+            blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+            green = toInt((t = rgb[3].charAt(2)) + t, 16);
+            red = toInt((t = rgb[3].charAt(1)) + t, 16);
+        }
+        if (rgb[4]) {
+            values = rgb[4].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red *= 2.55);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green *= 2.55);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue *= 2.55);
+            rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+        }
+        if (rgb[5]) {
+            values = rgb[5].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsb2rgb(red, green, blue, opacity);
+        }
+        if (rgb[6]) {
+            values = rgb[6].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsl2rgb(red, green, blue, opacity);
+        }
+        red = mmin(math.round(red), 255);
+        green = mmin(math.round(green), 255);
+        blue = mmin(math.round(blue), 255);
+        opacity = mmin(mmax(opacity, 0), 1);
+        rgb = {r: red, g: green, b: blue, toString: rgbtoString};
+        rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+        rgb.opacity = is(opacity, "finite") ? opacity : 1;
+        return rgb;
+    }
+    return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+}, Snap);
+/*\
+ * Snap.hsb
+ [ method ]
+ **
+ * Converts HSB values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - b (number) value or brightness
+ = (string) hex representation of the color
+\*/
+Snap.hsb = cacher(function (h, s, b) {
+    return Snap.hsb2rgb(h, s, b).hex;
+});
+/*\
+ * Snap.hsl
+ [ method ]
+ **
+ * Converts HSL values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (string) hex representation of the color
+\*/
+Snap.hsl = cacher(function (h, s, l) {
+    return Snap.hsl2rgb(h, s, l).hex;
+});
+/*\
+ * Snap.rgb
+ [ method ]
+ **
+ * Converts RGB values to a hex representation of the color
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (string) hex representation of the color
+\*/
+Snap.rgb = cacher(function (r, g, b, o) {
+    if (is(o, "finite")) {
+        var round = math.round;
+        return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")";
+    }
+    return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+});
+var toHex = function (color) {
+    var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0],
+        red = "rgb(255, 0, 0)";
+    toHex = cacher(function (color) {
+        if (color.toLowerCase() == "red") {
+            return red;
+        }
+        i.style.color = red;
+        i.style.color = color;
+        var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+        return out == red ? null : out;
+    });
+    return toHex(color);
+},
+hsbtoString = function () {
+    return "hsb(" + [this.h, this.s, this.b] + ")";
+},
+hsltoString = function () {
+    return "hsl(" + [this.h, this.s, this.l] + ")";
+},
+rgbtoString = function () {
+    return this.opacity == 1 || this.opacity == null ?
+            this.hex :
+            "rgba(" + [this.r, this.g, this.b, this.opacity] + ")";
+},
+prepareRGB = function (r, g, b) {
+    if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) {
+        b = r.b;
+        g = r.g;
+        r = r.r;
+    }
+    if (g == null && is(r, string)) {
+        var clr = Snap.getRGB(r);
+        r = clr.r;
+        g = clr.g;
+        b = clr.b;
+    }
+    if (r > 1 || g > 1 || b > 1) {
+        r /= 255;
+        g /= 255;
+        b /= 255;
+    }
+
+    return [r, g, b];
+},
+packageRGB = function (r, g, b, o) {
+    r = math.round(r * 255);
+    g = math.round(g * 255);
+    b = math.round(b * 255);
+    var rgb = {
+        r: r,
+        g: g,
+        b: b,
+        opacity: is(o, "finite") ? o : 1,
+        hex: Snap.rgb(r, g, b),
+        toString: rgbtoString
+    };
+    is(o, "finite") && (rgb.opacity = o);
+    return rgb;
+};
+/*\
+ * Snap.color
+ [ method ]
+ **
+ * Parses the color string and returns an object featuring the color's component values
+ - clr (string) color string in one of the supported formats (see @Snap.getRGB)
+ = (object) Combined RGB/HSB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) `true` if string can't be parsed,
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     v (number) value (brightness),
+ o     l (number) lightness
+ o }
+\*/
+Snap.color = function (clr) {
+    var rgb;
+    if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+        rgb = Snap.hsb2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+        rgb = Snap.hsl2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else {
+        if (is(clr, "string")) {
+            clr = Snap.getRGB(clr);
+        }
+        if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) {
+            rgb = Snap.rgb2hsl(clr);
+            clr.h = rgb.h;
+            clr.s = rgb.s;
+            clr.l = rgb.l;
+            rgb = Snap.rgb2hsb(clr);
+            clr.v = rgb.b;
+        } else {
+            clr = {hex: "none"};
+            clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+            clr.error = 1;
+        }
+    }
+    clr.toString = rgbtoString;
+    return clr;
+};
+/*\
+ * Snap.hsb2rgb
+ [ method ]
+ **
+ * Converts HSB values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - v (number) value or brightness
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsb2rgb = function (h, s, v, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "b" in h) {
+        v = h.b;
+        s = h.s;
+        o = h.o;
+        h = h.h;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = v * s;
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = v - C;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.hsl2rgb
+ [ method ]
+ **
+ * Converts HSL values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsl2rgb = function (h, s, l, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "l" in h) {
+        l = h.l;
+        s = h.s;
+        h = h.h;
+    }
+    if (h > 1 || s > 1 || l > 1) {
+        h /= 360;
+        s /= 100;
+        l /= 100;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = 2 * s * (l < .5 ? l : 1 - l);
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = l - C / 2;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.rgb2hsb
+ [ method ]
+ **
+ * Converts RGB values to an HSB object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSB object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     b (number) brightness
+ o }
+\*/
+Snap.rgb2hsb = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, V, C;
+    V = mmax(r, g, b);
+    C = V - mmin(r, g, b);
+    H = (C == 0 ? null :
+         V == r ? (g - b) / C :
+         V == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4
+        );
+    H = ((H + 360) % 6) * 60 / 360;
+    S = C == 0 ? 0 : C / V;
+    return {h: H, s: S, b: V, toString: hsbtoString};
+};
+/*\
+ * Snap.rgb2hsl
+ [ method ]
+ **
+ * Converts RGB values to an HSL object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSL object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     l (number) luminosity
+ o }
+\*/
+Snap.rgb2hsl = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, L, M, m, C;
+    M = mmax(r, g, b);
+    m = mmin(r, g, b);
+    C = M - m;
+    H = (C == 0 ? null :
+         M == r ? (g - b) / C :
+         M == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4);
+    H = ((H + 360) % 6) * 60 / 360;
+    L = (M + m) / 2;
+    S = (C == 0 ? 0 :
+         L < .5 ? C / (2 * L) :
+                  C / (2 - 2 * L));
+    return {h: H, s: S, l: L, toString: hsltoString};
+};
+
+// Transformations
+/*\
+ * Snap.parsePathString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given path string into an array of arrays of path segments
+ - pathString (string|array) path string or array of segments (in the last case it is returned straight away)
+ = (array) array of segments
+\*/
+Snap.parsePathString = function (pathString) {
+    if (!pathString) {
+        return null;
+    }
+    var pth = Snap.path(pathString);
+    if (pth.arr) {
+        return Snap.path.clone(pth.arr);
+    }
+
+    var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
+        data = [];
+    if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
+        data = Snap.path.clone(pathString);
+    }
+    if (!data.length) {
+        Str(pathString).replace(pathCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            if (name == "m" && params.length > 2) {
+                data.push([b].concat(params.splice(0, 2)));
+                name = "l";
+                b = b == "m" ? "l" : "L";
+            }
+            if (name == "o" && params.length == 1) {
+                data.push([b, params[0]]);
+            }
+            if (name == "r") {
+                data.push([b].concat(params));
+            } else while (params.length >= paramCounts[name]) {
+                data.push([b].concat(params.splice(0, paramCounts[name])));
+                if (!paramCounts[name]) {
+                    break;
+                }
+            }
+        });
+    }
+    data.toString = Snap.path.toString;
+    pth.arr = Snap.path.clone(data);
+    return data;
+};
+/*\
+ * Snap.parseTransformString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given transform string into an array of transformations
+ - TString (string|array) transform string or array of transformations (in the last case it is returned straight away)
+ = (array) array of transformations
+\*/
+var parseTransformString = Snap.parseTransformString = function (TString) {
+    if (!TString) {
+        return null;
+    }
+    var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+        data = [];
+    if (is(TString, "array") && is(TString[0], "array")) { // rough assumption
+        data = Snap.path.clone(TString);
+    }
+    if (!data.length) {
+        Str(TString).replace(tCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            data.push([b].concat(params));
+        });
+    }
+    data.toString = Snap.path.toString;
+    return data;
+};
+function svgTransform2string(tstr) {
+    var res = [];
+    tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
+        params = params.split(/\s*,\s*|\s+/);
+        if (name == "rotate" && params.length == 1) {
+            params.push(0, 0);
+        }
+        if (name == "scale") {
+            if (params.length > 2) {
+                params = params.slice(0, 2);
+            } else if (params.length == 2) {
+                params.push(0, 0);
+            }
+            if (params.length == 1) {
+                params.push(params[0], 0, 0);
+            }
+        }
+        if (name == "skewX") {
+            res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]);
+        } else if (name == "skewY") {
+            res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]);
+        } else {
+            res.push([name.charAt(0)].concat(params));
+        }
+        return all;
+    });
+    return res;
+}
+Snap._.svgTransform2string = svgTransform2string;
+Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i;
+function transform2matrix(tstr, bbox) {
+    var tdata = parseTransformString(tstr),
+        m = new Snap.Matrix;
+    if (tdata) {
+        for (var i = 0, ii = tdata.length; i < ii; i++) {
+            var t = tdata[i],
+                tlen = t.length,
+                command = Str(t[0]).toLowerCase(),
+                absolute = t[0] != command,
+                inver = absolute ? m.invert() : 0,
+                x1,
+                y1,
+                x2,
+                y2,
+                bb;
+            if (command == "t" && tlen == 2){
+                m.translate(t[1], 0);
+            } else if (command == "t" && tlen == 3) {
+                if (absolute) {
+                    x1 = inver.x(0, 0);
+                    y1 = inver.y(0, 0);
+                    x2 = inver.x(t[1], t[2]);
+                    y2 = inver.y(t[1], t[2]);
+                    m.translate(x2 - x1, y2 - y1);
+                } else {
+                    m.translate(t[1], t[2]);
+                }
+            } else if (command == "r") {
+                if (tlen == 2) {
+                    bb = bb || bbox;
+                    m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.rotate(t[1], x2, y2);
+                    } else {
+                        m.rotate(t[1], t[2], t[3]);
+                    }
+                }
+            } else if (command == "s") {
+                if (tlen == 2 || tlen == 3) {
+                    bb = bb || bbox;
+                    m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.scale(t[1], t[1], x2, y2);
+                    } else {
+                        m.scale(t[1], t[1], t[2], t[3]);
+                    }
+                } else if (tlen == 5) {
+                    if (absolute) {
+                        x2 = inver.x(t[3], t[4]);
+                        y2 = inver.y(t[3], t[4]);
+                        m.scale(t[1], t[2], x2, y2);
+                    } else {
+                        m.scale(t[1], t[2], t[3], t[4]);
+                    }
+                }
+            } else if (command == "m" && tlen == 7) {
+                m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+            }
+        }
+    }
+    return m;
+}
+Snap._.transform2matrix = transform2matrix;
+Snap._unit2px = unit2px;
+var contains = glob.doc.contains || glob.doc.compareDocumentPosition ?
+    function (a, b) {
+        var adown = a.nodeType == 9 ? a.documentElement : a,
+            bup = b && b.parentNode;
+            return a == bup || !!(bup && bup.nodeType == 1 && (
+                adown.contains ?
+                    adown.contains(bup) :
+                    a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
+            ));
+    } :
+    function (a, b) {
+        if (b) {
+            while (b) {
+                b = b.parentNode;
+                if (b == a) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
+function getSomeDefs(el) {
+    var p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) ||
+            (el.node.parentNode && wrap(el.node.parentNode)) ||
+            Snap.select("svg") ||
+            Snap(0, 0),
+        pdefs = p.select("defs"),
+        defs  = pdefs == null ? false : pdefs.node;
+    if (!defs) {
+        defs = make("defs", p.node).node;
+    }
+    return defs;
+}
+function getSomeSVG(el) {
+    return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg");
+}
+Snap._.getSomeDefs = getSomeDefs;
+Snap._.getSomeSVG = getSomeSVG;
+function unit2px(el, name, value) {
+    var svg = getSomeSVG(el).node,
+        out = {},
+        mgr = svg.querySelector(".svg---mgr");
+    if (!mgr) {
+        mgr = $("rect");
+        $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"});
+        svg.appendChild(mgr);
+    }
+    function getW(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {width: val});
+        try {
+            return mgr.getBBox().width;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function getH(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {height: val});
+        try {
+            return mgr.getBBox().height;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function set(nam, f) {
+        if (name == null) {
+            out[nam] = f(el.attr(nam) || 0);
+        } else if (nam == name) {
+            out = f(value == null ? el.attr(nam) || 0 : value);
+        }
+    }
+    switch (el.type) {
+        case "rect":
+            set("rx", getW);
+            set("ry", getH);
+        case "image":
+            set("width", getW);
+            set("height", getH);
+        case "text":
+            set("x", getW);
+            set("y", getH);
+        break;
+        case "circle":
+            set("cx", getW);
+            set("cy", getH);
+            set("r", getW);
+        break;
+        case "ellipse":
+            set("cx", getW);
+            set("cy", getH);
+            set("rx", getW);
+            set("ry", getH);
+        break;
+        case "line":
+            set("x1", getW);
+            set("x2", getW);
+            set("y1", getH);
+            set("y2", getH);
+        break;
+        case "marker":
+            set("refX", getW);
+            set("markerWidth", getW);
+            set("refY", getH);
+            set("markerHeight", getH);
+        break;
+        case "radialGradient":
+            set("fx", getW);
+            set("fy", getH);
+        break;
+        case "tspan":
+            set("dx", getW);
+            set("dy", getH);
+        break;
+        default:
+            set(name, getW);
+    }
+    svg.removeChild(mgr);
+    return out;
+}
+/*\
+ * Snap.select
+ [ method ]
+ **
+ * Wraps a DOM element specified by CSS selector as @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.select = function (query) {
+    query = Str(query).replace(/([^\\]):/g, "$1\\:");
+    return wrap(glob.doc.querySelector(query));
+};
+/*\
+ * Snap.selectAll
+ [ method ]
+ **
+ * Wraps DOM elements specified by CSS selector as set or array of @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.selectAll = function (query) {
+    var nodelist = glob.doc.querySelectorAll(query),
+        set = (Snap.set || Array)();
+    for (var i = 0; i < nodelist.length; i++) {
+        set.push(wrap(nodelist[i]));
+    }
+    return set;
+};
+
+function add2group(list) {
+    if (!is(list, "array")) {
+        list = Array.prototype.slice.call(arguments, 0);
+    }
+    var i = 0,
+        j = 0,
+        node = this.node;
+    while (this[i]) delete this[i++];
+    for (i = 0; i < list.length; i++) {
+        if (list[i].type == "set") {
+            list[i].forEach(function (el) {
+                node.appendChild(el.node);
+            });
+        } else {
+            node.appendChild(list[i].node);
+        }
+    }
+    var children = node.childNodes;
+    for (i = 0; i < children.length; i++) {
+        this[j++] = wrap(children[i]);
+    }
+    return this;
+}
+// Hub garbage collector every 10s
+setInterval(function () {
+    for (var key in hub) if (hub[has](key)) {
+        var el = hub[key],
+            node = el.node;
+        if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
+            delete hub[key];
+        }
+    }
+}, 1e4);
+function Element(el) {
+    if (el.snap in hub) {
+        return hub[el.snap];
+    }
+    var svg;
+    try {
+        svg = el.ownerSVGElement;
+    } catch(e) {}
+    /*\
+     * Element.node
+     [ property (object) ]
+     **
+     * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
+     > Usage
+     | // draw a circle at coordinate 10,10 with radius of 10
+     | var c = paper.circle(10, 10, 10);
+     | c.node.onclick = function () {
+     |     c.attr("fill", "red");
+     | };
+    \*/
+    this.node = el;
+    if (svg) {
+        this.paper = new Paper(svg);
+    }
+    /*\
+     * Element.type
+     [ property (string) ]
+     **
+     * SVG tag name of the given element.
+    \*/
+    this.type = el.tagName || el.nodeName;
+    var id = this.id = ID(this);
+    this.anims = {};
+    this._ = {
+        transform: []
+    };
+    el.snap = id;
+    hub[id] = this;
+    if (this.type == "g") {
+        this.add = add2group;
+    }
+    if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) {
+        for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
+            this[method] = Paper.prototype[method];
+        }
+    }
+}
+   /*\
+     * Element.attr
+     [ method ]
+     **
+     * Gets or sets given attributes of the element.
+     **
+     - params (object) contains key-value pairs of attributes you want to set
+     * or
+     - param (string) name of the attribute
+     = (Element) the current element
+     * or
+     = (string) value of attribute
+     > Usage
+     | el.attr({
+     |     fill: "#fc0",
+     |     stroke: "#000",
+     |     strokeWidth: 2, // CamelCase...
+     |     "fill-opacity": 0.5, // or dash-separated names
+     |     width: "*=2" // prefixed values
+     | });
+     | console.log(el.attr("fill")); // #fc0
+     * Prefixed values in format `"+=10"` supported. All four operations
+     * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+`
+     * and `-`: `"+=2em"`.
+    \*/
+    Element.prototype.attr = function (params, value) {
+        var el = this,
+            node = el.node;
+        if (!params) {
+            if (node.nodeType != 1) {
+                return {
+                    text: node.nodeValue
+                };
+            }
+            var attr = node.attributes,
+                out = {};
+            for (var i = 0, ii = attr.length; i < ii; i++) {
+                out[attr[i].nodeName] = attr[i].nodeValue;
+            }
+            return out;
+        }
+        if (is(params, "string")) {
+            if (arguments.length > 1) {
+                var json = {};
+                json[params] = value;
+                params = json;
+            } else {
+                return eve("snap.util.getattr." + params, el).firstDefined();
+            }
+        }
+        for (var att in params) {
+            if (params[has](att)) {
+                eve("snap.util.attr." + att, el, params[att]);
+            }
+        }
+        return el;
+    };
+/*\
+ * Snap.parse
+ [ method ]
+ **
+ * Parses SVG fragment and converts it into a @Fragment
+ **
+ - svg (string) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.parse = function (svg) {
+    var f = glob.doc.createDocumentFragment(),
+        full = true,
+        div = glob.doc.createElement("div");
+    svg = Str(svg);
+    if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) {
+        svg = "<svg>" + svg + "</svg>";
+        full = false;
+    }
+    div.innerHTML = svg;
+    svg = div.getElementsByTagName("svg")[0];
+    if (svg) {
+        if (full) {
+            f = svg;
+        } else {
+            while (svg.firstChild) {
+                f.appendChild(svg.firstChild);
+            }
+        }
+    }
+    return new Fragment(f);
+};
+function Fragment(frag) {
+    this.node = frag;
+}
+/*\
+ * Snap.fragment
+ [ method ]
+ **
+ * Creates a DOM fragment from a given list of elements or strings
+ **
+ - varargs (…) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.fragment = function () {
+    var args = Array.prototype.slice.call(arguments, 0),
+        f = glob.doc.createDocumentFragment();
+    for (var i = 0, ii = args.length; i < ii; i++) {
+        var item = args[i];
+        if (item.node && item.node.nodeType) {
+            f.appendChild(item.node);
+        }
+        if (item.nodeType) {
+            f.appendChild(item);
+        }
+        if (typeof item == "string") {
+            f.appendChild(Snap.parse(item).node);
+        }
+    }
+    return new Fragment(f);
+};
+
+function make(name, parent) {
+    var res = $(name);
+    parent.appendChild(res);
+    var el = wrap(res);
+    return el;
+}
+function Paper(w, h) {
+    var res,
+        desc,
+        defs,
+        proto = Paper.prototype;
+    if (w && w.tagName == "svg") {
+        if (w.snap in hub) {
+            return hub[w.snap];
+        }
+        var doc = w.ownerDocument;
+        res = new Element(w);
+        desc = w.getElementsByTagName("desc")[0];
+        defs = w.getElementsByTagName("defs")[0];
+        if (!desc) {
+            desc = $("desc");
+            desc.appendChild(doc.createTextNode("Created with Snap"));
+            res.node.appendChild(desc);
+        }
+        if (!defs) {
+            defs = $("defs");
+            res.node.appendChild(defs);
+        }
+        res.defs = defs;
+        for (var key in proto) if (proto[has](key)) {
+            res[key] = proto[key];
+        }
+        res.paper = res.root = res;
+    } else {
+        res = make("svg", glob.doc.body);
+        $(res.node, {
+            height: h,
+            version: 1.1,
+            width: w,
+            xmlns: xmlns
+        });
+    }
+    return res;
+}
+function wrap(dom) {
+    if (!dom) {
+        return dom;
+    }
+    if (dom instanceof Element || dom instanceof Fragment) {
+        return dom;
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "svg") {
+        return new Paper(dom);
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") {
+        return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]);
+    }
+    return new Element(dom);
+}
+
+Snap._.make = make;
+Snap._.wrap = wrap;
+/*\
+ * Paper.el
+ [ method ]
+ **
+ * Creates an element on paper with a given name and no attributes
+ **
+ - name (string) tag name
+ - attr (object) attributes
+ = (Element) the current element
+ > Usage
+ | var c = paper.circle(10, 10, 10); // is the same as...
+ | var c = paper.el("circle").attr({
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+ | // and the same as
+ | var c = paper.el("circle", {
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+\*/
+Paper.prototype.el = function (name, attr) {
+    var el = make(name, this.node);
+    attr && el.attr(attr);
+    return el;
+};
+/*\
+ * Element.children
+ [ method ]
+ **
+ * Returns array of all the children of the element.
+ = (array) array of Elements
+\*/
+Element.prototype.children = function () {
+    var out = [],
+        ch = this.node.childNodes;
+    for (var i = 0, ii = ch.length; i < ii; i++) {
+        out[i] = Snap(ch[i]);
+    }
+    return out;
+};
+function jsonFiller(root, o) {
+    for (var i = 0, ii = root.length; i < ii; i++) {
+        var item = {
+                type: root[i].type,
+                attr: root[i].attr()
+            },
+            children = root[i].children();
+        o.push(item);
+        if (children.length) {
+            jsonFiller(children, item.childNodes = []);
+        }
+    }
+}
+/*\
+ * Element.toJSON
+ [ method ]
+ **
+ * Returns object representation of the given element and all its children.
+ = (object) in format
+ o {
+ o     type (string) this.type,
+ o     attr (object) attributes map,
+ o     childNodes (array) optional array of children in the same format
+ o }
+\*/
+Element.prototype.toJSON = function () {
+    var out = [];
+    jsonFiller([this], out);
+    return out[0];
+};
+// default
+eve.on("snap.util.getattr", function () {
+    var att = eve.nt();
+    att = att.substring(att.lastIndexOf(".") + 1);
+    var css = att.replace(/[A-Z]/g, function (letter) {
+        return "-" + letter.toLowerCase();
+    });
+    if (cssAttr[has](css)) {
+        return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css);
+    } else {
+        return $(this.node, att);
+    }
+});
+var cssAttr = {
+    "alignment-baseline": 0,
+    "baseline-shift": 0,
+    "clip": 0,
+    "clip-path": 0,
+    "clip-rule": 0,
+    "color": 0,
+    "color-interpolation": 0,
+    "color-interpolation-filters": 0,
+    "color-profile": 0,
+    "color-rendering": 0,
+    "cursor": 0,
+    "direction": 0,
+    "display": 0,
+    "dominant-baseline": 0,
+    "enable-background": 0,
+    "fill": 0,
+    "fill-opacity": 0,
+    "fill-rule": 0,
+    "filter": 0,
+    "flood-color": 0,
+    "flood-opacity": 0,
+    "font": 0,
+    "font-family": 0,
+    "font-size": 0,
+    "font-size-adjust": 0,
+    "font-stretch": 0,
+    "font-style": 0,
+    "font-variant": 0,
+    "font-weight": 0,
+    "glyph-orientation-horizontal": 0,
+    "glyph-orientation-vertical": 0,
+    "image-rendering": 0,
+    "kerning": 0,
+    "letter-spacing": 0,
+    "lighting-color": 0,
+    "marker": 0,
+    "marker-end": 0,
+    "marker-mid": 0,
+    "marker-start": 0,
+    "mask": 0,
+    "opacity": 0,
+    "overflow": 0,
+    "pointer-events": 0,
+    "shape-rendering": 0,
+    "stop-color": 0,
+    "stop-opacity": 0,
+    "stroke": 0,
+    "stroke-dasharray": 0,
+    "stroke-dashoffset": 0,
+    "stroke-linecap": 0,
+    "stroke-linejoin": 0,
+    "stroke-miterlimit": 0,
+    "stroke-opacity": 0,
+    "stroke-width": 0,
+    "text-anchor": 0,
+    "text-decoration": 0,
+    "text-rendering": 0,
+    "unicode-bidi": 0,
+    "visibility": 0,
+    "word-spacing": 0,
+    "writing-mode": 0
+};
+
+eve.on("snap.util.attr", function (value) {
+    var att = eve.nt(),
+        attr = {};
+    att = att.substring(att.lastIndexOf(".") + 1);
+    attr[att] = value;
+    var style = att.replace(/-(\w)/gi, function (all, letter) {
+            return letter.toUpperCase();
+        }),
+        css = att.replace(/[A-Z]/g, function (letter) {
+            return "-" + letter.toLowerCase();
+        });
+    if (cssAttr[has](css)) {
+        this.node.style[style] = value == null ? E : value;
+    } else {
+        $(this.node, attr);
+    }
+});
+(function (proto) {}(Paper.prototype));
+
+// simple ajax
+/*\
+ * Snap.ajax
+ [ method ]
+ **
+ * Simple implementation of Ajax
+ **
+ - url (string) URL
+ - postData (object|string) data for post request
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ * or
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ = (XMLHttpRequest) the XMLHttpRequest object, just in case
+\*/
+Snap.ajax = function (url, postData, callback, scope){
+    var req = new XMLHttpRequest,
+        id = ID();
+    if (req) {
+        if (is(postData, "function")) {
+            scope = callback;
+            callback = postData;
+            postData = null;
+        } else if (is(postData, "object")) {
+            var pd = [];
+            for (var key in postData) if (postData.hasOwnProperty(key)) {
+                pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
+            }
+            postData = pd.join("&");
+        }
+        req.open((postData ? "POST" : "GET"), url, true);
+        if (postData) {
+            req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+            req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+        }
+        if (callback) {
+            eve.once("snap.ajax." + id + ".0", callback);
+            eve.once("snap.ajax." + id + ".200", callback);
+            eve.once("snap.ajax." + id + ".304", callback);
+        }
+        req.onreadystatechange = function() {
+            if (req.readyState != 4) return;
+            eve("snap.ajax." + id + "." + req.status, scope, req);
+        };
+        if (req.readyState == 4) {
+            return req;
+        }
+        req.send(postData);
+        return req;
+    }
+};
+/*\
+ * Snap.load
+ [ method ]
+ **
+ * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX)
+ **
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+\*/
+Snap.load = function (url, callback, scope) {
+    Snap.ajax(url, function (req) {
+        var f = Snap.parse(req.responseText);
+        scope ? callback.call(scope, f) : callback(f);
+    });
+};
+var getOffset = function (elem) {
+    var box = elem.getBoundingClientRect(),
+        doc = elem.ownerDocument,
+        body = doc.body,
+        docElem = doc.documentElement,
+        clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+        top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
+        left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+    return {
+        y: top,
+        x: left
+    };
+};
+/*\
+ * Snap.getElementByPoint
+ [ method ]
+ **
+ * Returns you topmost element under given point.
+ **
+ = (object) Snap element object
+ - x (number) x coordinate from the top left corner of the window
+ - y (number) y coordinate from the top left corner of the window
+ > Usage
+ | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
+\*/
+Snap.getElementByPoint = function (x, y) {
+    var paper = this,
+        svg = paper.canvas,
+        target = glob.doc.elementFromPoint(x, y);
+    if (glob.win.opera && target.tagName == "svg") {
+        var so = getOffset(target),
+            sr = target.createSVGRect();
+        sr.x = x - so.x;
+        sr.y = y - so.y;
+        sr.width = sr.height = 1;
+        var hits = target.getIntersectionList(sr, null);
+        if (hits.length) {
+            target = hits[hits.length - 1];
+        }
+    }
+    if (!target) {
+        return null;
+    }
+    return wrap(target);
+};
+/*\
+ * Snap.plugin
+ [ method ]
+ **
+ * Let you write plugins. You pass in a function with five arguments, like this:
+ | Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
+ |     Snap.newmethod = function () {};
+ |     Element.prototype.newmethod = function () {};
+ |     Paper.prototype.newmethod = function () {};
+ | });
+ * Inside the function you have access to all main objects (and their
+ * prototypes). This allow you to extend anything you want.
+ **
+ - f (function) your plugin body
+\*/
+Snap.plugin = function (f) {
+    f(Snap, Element, Paper, glob, Fragment);
+};
+glob.win.Snap = Snap;
+return Snap;
+}(window || this));
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        Str = String,
+        unit2px = Snap._unit2px,
+        $ = Snap._.$,
+        make = Snap._.make,
+        getSomeDefs = Snap._.getSomeDefs,
+        has = "hasOwnProperty",
+        wrap = Snap._.wrap;
+    /*\
+     * Element.getBBox
+     [ method ]
+     **
+     * Returns the bounding box descriptor for the given element
+     **
+     = (object) bounding box descriptor:
+     o {
+     o     cx: (number) x of the center,
+     o     cy: (number) x of the center,
+     o     h: (number) height,
+     o     height: (number) height,
+     o     path: (string) path command for the box,
+     o     r0: (number) radius of a circle that fully encloses the box,
+     o     r1: (number) radius of the smallest circle that can be enclosed,
+     o     r2: (number) radius of the largest circle that can be enclosed,
+     o     vb: (string) box as a viewbox command,
+     o     w: (number) width,
+     o     width: (number) width,
+     o     x2: (number) x of the right side,
+     o     x: (number) x of the left side,
+     o     y2: (number) y of the bottom edge,
+     o     y: (number) y of the top edge
+     o }
+    \*/
+    elproto.getBBox = function (isWithoutTransform) {
+        if (!Snap.Matrix || !Snap.path) {
+            return this.node.getBBox();
+        }
+        var el = this,
+            m = new Snap.Matrix;
+        if (el.removed) {
+            return Snap._.box();
+        }
+        while (el.type == "use") {
+            if (!isWithoutTransform) {
+                m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0));
+            }
+            if (el.original) {
+                el = el.original;
+            } else {
+                var href = el.attr("xlink:href");
+                el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1));
+            }
+        }
+        var _ = el._,
+            pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt;
+        try {
+            if (isWithoutTransform) {
+                _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox());
+                return Snap._.box(_.bboxwt);
+            } else {
+                el.realPath = pathfinder(el);
+                el.matrix = el.transform().localMatrix;
+                _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix)));
+                return Snap._.box(_.bbox);
+            }
+        } catch (e) {
+            // Firefox doesn’t give you bbox of hidden element
+            return Snap._.box();
+        }
+    };
+    var propString = function () {
+        return this.string;
+    };
+    function extractTransform(el, tstr) {
+        if (tstr == null) {
+            var doReturn = true;
+            if (el.type == "linearGradient" || el.type == "radialGradient") {
+                tstr = el.node.getAttribute("gradientTransform");
+            } else if (el.type == "pattern") {
+                tstr = el.node.getAttribute("patternTransform");
+            } else {
+                tstr = el.node.getAttribute("transform");
+            }
+            if (!tstr) {
+                return new Snap.Matrix;
+            }
+            tstr = Snap._.svgTransform2string(tstr);
+        } else {
+            if (!Snap._.rgTransform.test(tstr)) {
+                tstr = Snap._.svgTransform2string(tstr);
+            } else {
+                tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || "");
+            }
+            if (is(tstr, "array")) {
+                tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr);
+            }
+            el._.transform = tstr;
+        }
+        var m = Snap._.transform2matrix(tstr, el.getBBox(1));
+        if (doReturn) {
+            return m;
+        } else {
+            el.matrix = m;
+        }
+    }
+    /*\
+     * Element.transform
+     [ method ]
+     **
+     * Gets or sets transformation of the element
+     **
+     - tstr (string) transform string in Snap or SVG format
+     = (Element) the current element
+     * or
+     = (object) transformation descriptor:
+     o {
+     o     string (string) transform string,
+     o     globalMatrix (Matrix) matrix of all transformations applied to element or its parents,
+     o     localMatrix (Matrix) matrix of transformations applied only to the element,
+     o     diffMatrix (Matrix) matrix of difference between global and local transformations,
+     o     global (string) global transformation as string,
+     o     local (string) local transformation as string,
+     o     toString (function) returns `string` property
+     o }
+    \*/
+    elproto.transform = function (tstr) {
+        var _ = this._;
+        if (tstr == null) {
+            var papa = this,
+                global = new Snap.Matrix(this.node.getCTM()),
+                local = extractTransform(this),
+                ms = [local],
+                m = new Snap.Matrix,
+                i,
+                localString = local.toTransformString(),
+                string = Str(local) == Str(this.matrix) ?
+                            Str(_.transform) : localString;
+            while (papa.type != "svg" && (papa = papa.parent())) {
+                ms.push(extractTransform(papa));
+            }
+            i = ms.length;
+            while (i--) {
+                m.add(ms[i]);
+            }
+            return {
+                string: string,
+                globalMatrix: global,
+                totalMatrix: m,
+                localMatrix: local,
+                diffMatrix: global.clone().add(local.invert()),
+                global: global.toTransformString(),
+                total: m.toTransformString(),
+                local: localString,
+                toString: propString
+            };
+        }
+        if (tstr instanceof Snap.Matrix) {
+            this.matrix = tstr;
+            this._.transform = tstr.toTransformString();
+        } else {
+            extractTransform(this, tstr);
+        }
+
+        if (this.node) {
+            if (this.type == "linearGradient" || this.type == "radialGradient") {
+                $(this.node, {gradientTransform: this.matrix});
+            } else if (this.type == "pattern") {
+                $(this.node, {patternTransform: this.matrix});
+            } else {
+                $(this.node, {transform: this.matrix});
+            }
+        }
+
+        return this;
+    };
+    /*\
+     * Element.parent
+     [ method ]
+     **
+     * Returns the element's parent
+     **
+     = (Element) the parent element
+    \*/
+    elproto.parent = function () {
+        return wrap(this.node.parentNode);
+    };
+    /*\
+     * Element.append
+     [ method ]
+     **
+     * Appends the given element to current one
+     **
+     - el (Element|Set) element to append
+     = (Element) the parent element
+    \*/
+    /*\
+     * Element.add
+     [ method ]
+     **
+     * See @Element.append
+    \*/
+    elproto.append = elproto.add = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this;
+                el.forEach(function (el) {
+                    it.add(el);
+                });
+                return this;
+            }
+            el = wrap(el);
+            this.node.appendChild(el.node);
+            el.paper = this.paper;
+        }
+        return this;
+    };
+    /*\
+     * Element.appendTo
+     [ method ]
+     **
+     * Appends the current element to the given one
+     **
+     - el (Element) parent element to append to
+     = (Element) the child element
+    \*/
+    elproto.appendTo = function (el) {
+        if (el) {
+            el = wrap(el);
+            el.append(this);
+        }
+        return this;
+    };
+    /*\
+     * Element.prepend
+     [ method ]
+     **
+     * Prepends the given element to the current one
+     **
+     - el (Element) element to prepend
+     = (Element) the parent element
+    \*/
+    elproto.prepend = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this,
+                    first;
+                el.forEach(function (el) {
+                    if (first) {
+                        first.after(el);
+                    } else {
+                        it.prepend(el);
+                    }
+                    first = el;
+                });
+                return this;
+            }
+            el = wrap(el);
+            var parent = el.parent();
+            this.node.insertBefore(el.node, this.node.firstChild);
+            this.add && this.add();
+            el.paper = this.paper;
+            this.parent() && this.parent().add();
+            parent && parent.add();
+        }
+        return this;
+    };
+    /*\
+     * Element.prependTo
+     [ method ]
+     **
+     * Prepends the current element to the given one
+     **
+     - el (Element) parent element to prepend to
+     = (Element) the child element
+    \*/
+    elproto.prependTo = function (el) {
+        el = wrap(el);
+        el.prepend(this);
+        return this;
+    };
+    /*\
+     * Element.before
+     [ method ]
+     **
+     * Inserts given element before the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.before = function (el) {
+        if (el.type == "set") {
+            var it = this;
+            el.forEach(function (el) {
+                var parent = el.parent();
+                it.node.parentNode.insertBefore(el.node, it.node);
+                parent && parent.add();
+            });
+            this.parent().add();
+            return this;
+        }
+        el = wrap(el);
+        var parent = el.parent();
+        this.node.parentNode.insertBefore(el.node, this.node);
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.after
+     [ method ]
+     **
+     * Inserts given element after the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.after = function (el) {
+        el = wrap(el);
+        var parent = el.parent();
+        if (this.node.nextSibling) {
+            this.node.parentNode.insertBefore(el.node, this.node.nextSibling);
+        } else {
+            this.node.parentNode.appendChild(el.node);
+        }
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.insertBefore
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertBefore = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.insertAfter
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertAfter = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node.nextSibling);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.remove
+     [ method ]
+     **
+     * Removes element from the DOM
+     = (Element) the detached element
+    \*/
+    elproto.remove = function () {
+        var parent = this.parent();
+        this.node.parentNode && this.node.parentNode.removeChild(this.node);
+        delete this.paper;
+        this.removed = true;
+        parent && parent.add();
+        return this;
+    };
+    /*\
+     * Element.select
+     [ method ]
+     **
+     * Gathers the nested @Element matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Element) result of query selection
+    \*/
+    elproto.select = function (query) {
+        return wrap(this.node.querySelector(query));
+    };
+    /*\
+     * Element.selectAll
+     [ method ]
+     **
+     * Gathers nested @Element objects matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Set|array) result of query selection
+    \*/
+    elproto.selectAll = function (query) {
+        var nodelist = this.node.querySelectorAll(query),
+            set = (Snap.set || Array)();
+        for (var i = 0; i < nodelist.length; i++) {
+            set.push(wrap(nodelist[i]));
+        }
+        return set;
+    };
+    /*\
+     * Element.asPX
+     [ method ]
+     **
+     * Returns given attribute of the element as a `px` value (not %, em, etc.)
+     **
+     - attr (string) attribute name
+     - value (string) #optional attribute value
+     = (Element) result of query selection
+    \*/
+    elproto.asPX = function (attr, value) {
+        if (value == null) {
+            value = this.attr(attr);
+        }
+        return +unit2px(this, attr, value);
+    };
+    // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned <use> instantiates. It's a part of SVG with which ordinary web developers may be least familiar.
+    /*\
+     * Element.use
+     [ method ]
+     **
+     * Creates a `<use>` element linked to the current element
+     **
+     = (Element) the `<use>` element
+    \*/
+    elproto.use = function () {
+        var use,
+            id = this.node.id;
+        if (!id) {
+            id = this.id;
+            $(this.node, {
+                id: id
+            });
+        }
+        if (this.type == "linearGradient" || this.type == "radialGradient" ||
+            this.type == "pattern") {
+            use = make(this.type, this.node.parentNode);
+        } else {
+            use = make("use", this.node.parentNode);
+        }
+        $(use.node, {
+            "xlink:href": "#" + id
+        });
+        use.original = this;
+        return use;
+    };
+    function fixids(el) {
+        var els = el.selectAll("*"),
+            it,
+            url = /^\s*url\(("|'|)(.*)\1\)\s*$/,
+            ids = [],
+            uses = {};
+        function urltest(it, name) {
+            var val = $(it.node, name);
+            val = val && val.match(url);
+            val = val && val[2];
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    var attr = {};
+                    attr[name] = URL(id);
+                    $(it.node, attr);
+                });
+            }
+        }
+        function linktest(it) {
+            var val = $(it.node, "xlink:href");
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    it.attr("xlink:href", "#" + id);
+                });
+            }
+        }
+        for (var i = 0, ii = els.length; i < ii; i++) {
+            it = els[i];
+            urltest(it, "fill");
+            urltest(it, "stroke");
+            urltest(it, "filter");
+            urltest(it, "mask");
+            urltest(it, "clip-path");
+            linktest(it);
+            var oldid = $(it.node, "id");
+            if (oldid) {
+                $(it.node, {id: it.id});
+                ids.push({
+                    old: oldid,
+                    id: it.id
+                });
+            }
+        }
+        for (i = 0, ii = ids.length; i < ii; i++) {
+            var fs = uses[ids[i].old];
+            if (fs) {
+                for (var j = 0, jj = fs.length; j < jj; j++) {
+                    fs[j](ids[i].id);
+                }
+            }
+        }
+    }
+    /*\
+     * Element.clone
+     [ method ]
+     **
+     * Creates a clone of the element and inserts it after the element
+     **
+     = (Element) the clone
+    \*/
+    elproto.clone = function () {
+        var clone = wrap(this.node.cloneNode(true));
+        if ($(clone.node, "id")) {
+            $(clone.node, {id: clone.id});
+        }
+        fixids(clone);
+        clone.insertAfter(this);
+        return clone;
+    };
+    /*\
+     * Element.toDefs
+     [ method ]
+     **
+     * Moves element to the shared `<defs>` area
+     **
+     = (Element) the element
+    \*/
+    elproto.toDefs = function () {
+        var defs = getSomeDefs(this);
+        defs.appendChild(this.node);
+        return this;
+    };
+    /*\
+     * Element.toPattern
+     [ method ]
+     **
+     * Creates a `<pattern>` element from the current element
+     **
+     * To create a pattern you have to specify the pattern rect:
+     - x (string|number)
+     - y (string|number)
+     - width (string|number)
+     - height (string|number)
+     = (Element) the `<pattern>` element
+     * You can use pattern later on as an argument for `fill` attribute:
+     | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
+     |         fill: "none",
+     |         stroke: "#bada55",
+     |         strokeWidth: 5
+     |     }).pattern(0, 0, 10, 10),
+     |     c = paper.circle(200, 200, 100);
+     | c.attr({
+     |     fill: p
+     | });
+    \*/
+    elproto.pattern = elproto.toPattern = function (x, y, width, height) {
+        var p = make("pattern", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        $(p.node, {
+            x: x,
+            y: y,
+            width: width,
+            height: height,
+            patternUnits: "userSpaceOnUse",
+            id: p.id,
+            viewBox: [x, y, width, height].join(" ")
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+// SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path.
+// SIERRA Element.marker(): I suggest the method should accept default reference point values.  Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where?  Couldn't they also be assigned default values?
+    /*\
+     * Element.marker
+     [ method ]
+     **
+     * Creates a `<marker>` element from the current element
+     **
+     * To create a marker you have to specify the bounding rect and reference point:
+     - x (number)
+     - y (number)
+     - width (number)
+     - height (number)
+     - refX (number)
+     - refY (number)
+     = (Element) the `<marker>` element
+     * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end.
+    \*/
+    // TODO add usage for markers
+    elproto.marker = function (x, y, width, height, refX, refY) {
+        var p = make("marker", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            refX = x.refX || x.cx;
+            refY = x.refY || x.cy;
+            x = x.x;
+        }
+        $(p.node, {
+            viewBox: [x, y, width, height].join(" "),
+            markerWidth: width,
+            markerHeight: height,
+            orient: "auto",
+            refX: refX || 0,
+            refY: refY || 0,
+            id: p.id
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+    // animation
+    function slice(from, to, f) {
+        return function (arr) {
+            var res = arr.slice(from, to);
+            if (res.length == 1) {
+                res = res[0];
+            }
+            return f ? f(res) : res;
+        };
+    }
+    var Animation = function (attr, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        this.attr = attr;
+        this.dur = ms;
+        easing && (this.easing = easing);
+        callback && (this.callback = callback);
+    };
+    Snap._.Animation = Animation;
+    /*\
+     * Snap.animation
+     [ method ]
+     **
+     * Creates an animation object
+     **
+     - attr (object) attributes of final destination
+     - duration (number) duration of the animation, in milliseconds
+     - easing (function) #optional one of easing functions of @mina or custom one
+     - callback (function) #optional callback function that fires when animation ends
+     = (object) animation object
+    \*/
+    Snap.animation = function (attr, ms, easing, callback) {
+        return new Animation(attr, ms, easing, callback);
+    };
+    /*\
+     * Element.inAnim
+     [ method ]
+     **
+     * Returns a set of animations that may be able to manipulate the current element
+     **
+     = (object) in format:
+     o {
+     o     anim (object) animation object,
+     o     mina (object) @mina object,
+     o     curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+    \*/
+    elproto.inAnim = function () {
+        var el = this,
+            res = [];
+        for (var id in el.anims) if (el.anims[has](id)) {
+            (function (a) {
+                res.push({
+                    anim: new Animation(a._attrs, a.dur, a.easing, a._callback),
+                    mina: a,
+                    curStatus: a.status(),
+                    status: function (val) {
+                        return a.status(val);
+                    },
+                    stop: function () {
+                        a.stop();
+                    }
+                });
+            }(el.anims[id]));
+        }
+        return res;
+    };
+    /*\
+     * Snap.animate
+     [ method ]
+     **
+     * Runs generic animation of one number into another with a caring function
+     **
+     - from (number|array) number or array of numbers
+     - to (number|array) number or array of numbers
+     - setter (function) caring function that accepts one number argument
+     - duration (number) duration, in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function to execute when animation ends
+     = (object) animation object in @mina format
+     o {
+     o     id (string) animation id, consider it read-only,
+     o     duration (function) gets or sets the duration of the animation,
+     o     easing (function) easing,
+     o     speed (function) gets or sets the speed of the animation,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+     | var rect = Snap().rect(0, 0, 10, 10);
+     | Snap.animate(0, 10, function (val) {
+     |     rect.attr({
+     |         x: val
+     |     });
+     | }, 1000);
+     | // in given context is equivalent to
+     | rect.animate({x: 10}, 1000);
+    \*/
+    Snap.animate = function (from, to, setter, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        var now = mina.time(),
+            anim = mina(from, to, now, now + ms, mina.time, setter, easing);
+        callback && eve.once("mina.finish." + anim.id, callback);
+        return anim;
+    };
+    /*\
+     * Element.stop
+     [ method ]
+     **
+     * Stops all the animations for the current element
+     **
+     = (Element) the current element
+    \*/
+    elproto.stop = function () {
+        var anims = this.inAnim();
+        for (var i = 0, ii = anims.length; i < ii; i++) {
+            anims[i].stop();
+        }
+        return this;
+    };
+    /*\
+     * Element.animate
+     [ method ]
+     **
+     * Animates the given attributes of the element
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     = (Element) the current element
+    \*/
+    elproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = attrs.dur;
+            attrs = attrs.attr;
+        }
+        var fkeys = [], tkeys = [], keys = {}, from, to, f, eq,
+            el = this;
+        for (var key in attrs) if (attrs[has](key)) {
+            if (el.equal) {
+                eq = el.equal(key, Str(attrs[key]));
+                from = eq.from;
+                to = eq.to;
+                f = eq.f;
+            } else {
+                from = +el.attr(key);
+                to = +attrs[key];
+            }
+            var len = is(from, "array") ? from.length : 1;
+            keys[key] = slice(fkeys.length, fkeys.length + len, f);
+            fkeys = fkeys.concat(from);
+            tkeys = tkeys.concat(to);
+        }
+        var now = mina.time(),
+            anim = mina(fkeys, tkeys, now, now + ms, mina.time, function (val) {
+                var attr = {};
+                for (var key in keys) if (keys[has](key)) {
+                    attr[key] = keys[key](val);
+                }
+                el.attr(attr);
+            }, easing);
+        el.anims[anim.id] = anim;
+        anim._attrs = attrs;
+        anim._callback = callback;
+        eve("snap.animcreated." + el.id, anim);
+        eve.once("mina.finish." + anim.id, function () {
+            delete el.anims[anim.id];
+            callback && callback.call(el);
+        });
+        eve.once("mina.stop." + anim.id, function () {
+            delete el.anims[anim.id];
+        });
+        return el;
+    };
+    var eldata = {};
+    /*\
+     * Element.data
+     [ method ]
+     **
+     * Adds or retrieves given value associated with given key. (Don’t confuse
+     * with `data-` attributes)
+     *
+     * See also @Element.removeData
+     - key (string) key to store data
+     - value (any) #optional value to store
+     = (object) @Element
+     * or, if value is not specified:
+     = (any) value
+     > Usage
+     | for (var i = 0, i < 5, i++) {
+     |     paper.circle(10 + 15 * i, 10, 10)
+     |          .attr({fill: "#000"})
+     |          .data("i", i)
+     |          .click(function () {
+     |             alert(this.data("i"));
+     |          });
+     | }
+    \*/
+    elproto.data = function (key, value) {
+        var data = eldata[this.id] = eldata[this.id] || {};
+        if (arguments.length == 0){
+            eve("snap.data.get." + this.id, this, data, null);
+            return data;
+        }
+        if (arguments.length == 1) {
+            if (Snap.is(key, "object")) {
+                for (var i in key) if (key[has](i)) {
+                    this.data(i, key[i]);
+                }
+                return this;
+            }
+            eve("snap.data.get." + this.id, this, data[key], key);
+            return data[key];
+        }
+        data[key] = value;
+        eve("snap.data.set." + this.id, this, value, key);
+        return this;
+    };
+    /*\
+     * Element.removeData
+     [ method ]
+     **
+     * Removes value associated with an element by given key.
+     * If key is not provided, removes all the data of the element.
+     - key (string) #optional key
+     = (object) @Element
+    \*/
+    elproto.removeData = function (key) {
+        if (key == null) {
+            eldata[this.id] = {};
+        } else {
+            eldata[this.id] && delete eldata[this.id][key];
+        }
+        return this;
+    };
+    /*\
+     * Element.outerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element, equivalent to HTML's `outerHTML`.
+     *
+     * See also @Element.innerSVG
+     = (string) SVG code for the element
+    \*/
+    /*\
+     * Element.toString
+     [ method ]
+     **
+     * See @Element.outerSVG
+    \*/
+    elproto.outerSVG = elproto.toString = toString(1);
+    /*\
+     * Element.innerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML`
+     = (string) SVG code for the element
+    \*/
+    elproto.innerSVG = toString();
+    function toString(type) {
+        return function () {
+            var res = type ? "<" + this.type : "",
+                attr = this.node.attributes,
+                chld = this.node.childNodes;
+            if (type) {
+                for (var i = 0, ii = attr.length; i < ii; i++) {
+                    res += " " + attr[i].name + '="' +
+                            attr[i].value.replace(/"/g, '\\"') + '"';
+                }
+            }
+            if (chld.length) {
+                type && (res += ">");
+                for (i = 0, ii = chld.length; i < ii; i++) {
+                    if (chld[i].nodeType == 3) {
+                        res += chld[i].nodeValue;
+                    } else if (chld[i].nodeType == 1) {
+                        res += wrap(chld[i]).toString();
+                    }
+                }
+                type && (res += "</" + this.type + ">");
+            } else {
+                type && (res += "/>");
+            }
+            return res;
+        };
+    }
+    elproto.toDataURL = function () {
+        if (window && window.btoa) {
+            var bb = this.getBBox(),
+                svg = Snap.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>', {
+                x: +bb.x.toFixed(3),
+                y: +bb.y.toFixed(3),
+                width: +bb.width.toFixed(3),
+                height: +bb.height.toFixed(3),
+                contents: this.outerSVG()
+            });
+            return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
+        }
+    };
+    /*\
+     * Fragment.select
+     [ method ]
+     **
+     * See @Element.select
+    \*/
+    Fragment.prototype.select = elproto.select;
+    /*\
+     * Fragment.selectAll
+     [ method ]
+     **
+     * See @Element.selectAll
+    \*/
+    Fragment.prototype.selectAll = elproto.selectAll;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var objectToString = Object.prototype.toString,
+        Str = String,
+        math = Math,
+        E = "";
+    function Matrix(a, b, c, d, e, f) {
+        if (b == null && objectToString.call(a) == "[object SVGMatrix]") {
+            this.a = a.a;
+            this.b = a.b;
+            this.c = a.c;
+            this.d = a.d;
+            this.e = a.e;
+            this.f = a.f;
+            return;
+        }
+        if (a != null) {
+            this.a = +a;
+            this.b = +b;
+            this.c = +c;
+            this.d = +d;
+            this.e = +e;
+            this.f = +f;
+        } else {
+            this.a = 1;
+            this.b = 0;
+            this.c = 0;
+            this.d = 1;
+            this.e = 0;
+            this.f = 0;
+        }
+    }
+    (function (matrixproto) {
+        /*\
+         * Matrix.add
+         [ method ]
+         **
+         * Adds the given matrix to existing one
+         - a (number)
+         - b (number)
+         - c (number)
+         - d (number)
+         - e (number)
+         - f (number)
+         * or
+         - matrix (object) @Matrix
+        \*/
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
+
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
+
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
+                }
+            }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+            return this;
+        };
+        /*\
+         * Matrix.invert
+         [ method ]
+         **
+         * Returns an inverted version of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        /*\
+         * Matrix.clone
+         [ method ]
+         **
+         * Returns a copy of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        /*\
+         * Matrix.translate
+         [ method ]
+         **
+         * Translate the matrix
+         - x (number) horizontal offset distance
+         - y (number) vertical offset distance
+        \*/
+        matrixproto.translate = function (x, y) {
+            return this.add(1, 0, 0, 1, x, y);
+        };
+        /*\
+         * Matrix.scale
+         [ method ]
+         **
+         * Scales the matrix
+         - x (number) amount to be scaled, with `1` resulting in no change
+         - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.)
+         - cx (number) #optional horizontal origin point from which to scale
+         - cy (number) #optional vertical origin point from which to scale
+         * Default cx, cy is the middle point of the element.
+        \*/
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+            return this;
+        };
+        /*\
+         * Matrix.rotate
+         [ method ]
+         **
+         * Rotates the matrix
+         - a (number) angle of rotation, in degrees
+         - x (number) horizontal origin point from which to rotate
+         - y (number) vertical origin point from which to rotate
+        \*/
+        matrixproto.rotate = function (a, x, y) {
+            a = Snap.rad(a);
+            x = x || 0;
+            y = y || 0;
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            return this.add(1, 0, 0, 1, -x, -y);
+        };
+        /*\
+         * Matrix.x
+         [ method ]
+         **
+         * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y
+         - x (number)
+         - y (number)
+         = (number) x
+        \*/
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        /*\
+         * Matrix.y
+         [ method ]
+         **
+         * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x
+         - x (number)
+         - y (number)
+         = (number) y
+        \*/
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
+        }
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] && (a[0] /= mag);
+            a[1] && (a[1] /= mag);
+        }
+        /*\
+         * Matrix.determinant
+         [ method ]
+         **
+         * Finds determinant of the given matrix.
+         = (number) determinant
+        \*/
+        matrixproto.determinant = function () {
+            return this.a * this.d - this.b * this.c;
+        };
+        /*\
+         * Matrix.split
+         [ method ]
+         **
+         * Splits matrix into primitive transformations
+         = (object) in format:
+         o dx (number) translation by x
+         o dy (number) translation by y
+         o scalex (number) scale by x
+         o scaley (number) scale by y
+         o shear (number) shear
+         o rotate (number) rotation in deg
+         o isSimple (boolean) could it be represented via simple transformations
+        \*/
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
+
+            if (this.determinant() < 0) {
+                out.scalex = -out.scalex;
+            }
+
+            // rotation
+            var sin = -row[0][1],
+                cos = row[1][1];
+            if (cos < 0) {
+                out.rotate = Snap.deg(math.acos(cos));
+                if (sin < 0) {
+                    out.rotate = 360 - out.rotate;
+                }
+            } else {
+                out.rotate = Snap.deg(math.asin(sin));
+            }
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+            return out;
+        };
+        /*\
+         * Matrix.toTransformString
+         [ method ]
+         **
+         * Returns transform string that represents given matrix
+         = (string) transform string
+        \*/
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this.split();
+            if (!+s.shear.toFixed(9)) {
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) +
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E);
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+    /*\
+     * Snap.Matrix
+     [ method ]
+     **
+     * Matrix constructor, extend on your own risk.
+     * To create matrices use @Snap.matrix.
+    \*/
+    Snap.Matrix = Matrix;
+    /*\
+     * Snap.matrix
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns a matrix based on the given parameters
+     - a (number)
+     - b (number)
+     - c (number)
+     - d (number)
+     - e (number)
+     - f (number)
+     * or
+     - svgMatrix (SVGMatrix)
+     = (object) @Matrix
+    \*/
+    Snap.matrix = function (a, b, c, d, e, f) {
+        return new Matrix(a, b, c, d, e, f);
+    };
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var has = "hasOwnProperty",
+        make = Snap._.make,
+        wrap = Snap._.wrap,
+        is = Snap.is,
+        getSomeDefs = Snap._.getSomeDefs,
+        reURLValue = /^url\(#?([^)]+)\)$/,
+        $ = Snap._.$,
+        URL = Snap.url,
+        Str = String,
+        separator = Snap._.separator,
+        E = "";
+    // Attributes event handlers
+    eve.on("snap.util.attr.mask", function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value.type == "mask") {
+                var mask = value;
+            } else {
+                mask = make("mask", getSomeDefs(this));
+                mask.node.appendChild(value.node);
+            }
+            !mask.node.id && $(mask.node, {
+                id: mask.id
+            });
+            $(this.node, {
+                mask: URL(mask.id)
+            });
+        }
+    });
+    (function (clipIt) {
+        eve.on("snap.util.attr.clip", clipIt);
+        eve.on("snap.util.attr.clip-path", clipIt);
+        eve.on("snap.util.attr.clipPath", clipIt);
+    }(function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value.type == "clipPath") {
+                var clip = value;
+            } else {
+                clip = make("clipPath", getSomeDefs(this));
+                clip.node.appendChild(value.node);
+                !clip.node.id && $(clip.node, {
+                    id: clip.id
+                });
+            }
+            $(this.node, {
+                "clip-path": URL(clip.node.id || clip.id)
+            });
+        }
+    }));
+    function fillStroke(name) {
+        return function (value) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1 &&
+                (value.node.firstChild.tagName == "radialGradient" ||
+                value.node.firstChild.tagName == "linearGradient" ||
+                value.node.firstChild.tagName == "pattern")) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value instanceof Element) {
+                if (value.type == "radialGradient" || value.type == "linearGradient"
+                   || value.type == "pattern") {
+                    if (!value.node.id) {
+                        $(value.node, {
+                            id: value.id
+                        });
+                    }
+                    var fill = URL(value.node.id);
+                } else {
+                    fill = value.attr(name);
+                }
+            } else {
+                fill = Snap.color(value);
+                if (fill.error) {
+                    var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value);
+                    if (grad) {
+                        if (!grad.node.id) {
+                            $(grad.node, {
+                                id: grad.id
+                            });
+                        }
+                        fill = URL(grad.node.id);
+                    } else {
+                        fill = value;
+                    }
+                } else {
+                    fill = Str(fill);
+                }
+            }
+            var attrs = {};
+            attrs[name] = fill;
+            $(this.node, attrs);
+            this.node.style[name] = E;
+        };
+    }
+    eve.on("snap.util.attr.fill", fillStroke("fill"));
+    eve.on("snap.util.attr.stroke", fillStroke("stroke"));
+    var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i;
+    eve.on("snap.util.grad.parse", function parseGrad(string) {
+        string = Str(string);
+        var tokens = string.match(gradrg);
+        if (!tokens) {
+            return null;
+        }
+        var type = tokens[1],
+            params = tokens[2],
+            stops = tokens[3];
+        params = params.split(/\s*,\s*/).map(function (el) {
+            return +el == el ? +el : el;
+        });
+        if (params.length == 1 && params[0] == 0) {
+            params = [];
+        }
+        stops = stops.split("-");
+        stops = stops.map(function (el) {
+            el = el.split(":");
+            var out = {
+                color: el[0]
+            };
+            if (el[1]) {
+                out.offset = parseFloat(el[1]);
+            }
+            return out;
+        });
+        return {
+            type: type,
+            params: params,
+            stops: stops
+        };
+    });
+
+    eve.on("snap.util.attr.d", function (value) {
+        eve.stop();
+        if (is(value, "array") && is(value[0], "array")) {
+            value = Snap.path.toString.call(value);
+        }
+        value = Str(value);
+        if (value.match(/[ruo]/i)) {
+            value = Snap.path.toAbsolute(value);
+        }
+        $(this.node, {d: value});
+    })(-1);
+    eve.on("snap.util.attr.#text", function (value) {
+        eve.stop();
+        value = Str(value);
+        var txt = glob.doc.createTextNode(value);
+        while (this.node.firstChild) {
+            this.node.removeChild(this.node.firstChild);
+        }
+        this.node.appendChild(txt);
+    })(-1);
+    eve.on("snap.util.attr.path", function (value) {
+        eve.stop();
+        this.attr({d: value});
+    })(-1);
+    eve.on("snap.util.attr.class", function (value) {
+        eve.stop();
+        this.node.className.baseVal = value;
+    })(-1);
+    eve.on("snap.util.attr.viewBox", function (value) {
+        var vb;
+        if (is(value, "object") && "x" in value) {
+            vb = [value.x, value.y, value.width, value.height].join(" ");
+        } else if (is(value, "array")) {
+            vb = value.join(" ");
+        } else {
+            vb = value;
+        }
+        $(this.node, {
+            viewBox: vb
+        });
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.transform", function (value) {
+        this.transform(value);
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.r", function (value) {
+        if (this.type == "rect") {
+            eve.stop();
+            $(this.node, {
+                rx: value,
+                ry: value
+            });
+        }
+    })(-1);
+    eve.on("snap.util.attr.textpath", function (value) {
+        eve.stop();
+        if (this.type == "text") {
+            var id, tp, node;
+            if (!value && this.textPath) {
+                tp = this.textPath;
+                while (tp.node.firstChild) {
+                    this.node.appendChild(tp.node.firstChild);
+                }
+                tp.remove();
+                delete this.textPath;
+                return;
+            }
+            if (is(value, "string")) {
+                var defs = getSomeDefs(this),
+                    path = wrap(defs.parentNode).path(value);
+                defs.appendChild(path.node);
+                id = path.id;
+                path.attr({id: id});
+            } else {
+                value = wrap(value);
+                if (value instanceof Element) {
+                    id = value.attr("id");
+                    if (!id) {
+                        id = value.id;
+                        value.attr({id: id});
+                    }
+                }
+            }
+            if (id) {
+                tp = this.textPath;
+                node = this.node;
+                if (tp) {
+                    tp.attr({"xlink:href": "#" + id});
+                } else {
+                    tp = $("textPath", {
+                        "xlink:href": "#" + id
+                    });
+                    while (node.firstChild) {
+                        tp.appendChild(node.firstChild);
+                    }
+                    node.appendChild(tp);
+                    this.textPath = wrap(tp);
+                }
+            }
+        }
+    })(-1);
+    eve.on("snap.util.attr.text", function (value) {
+        if (this.type == "text") {
+            var i = 0,
+                node = this.node,
+                tuner = function (chunk) {
+                    var out = $("tspan");
+                    if (is(chunk, "array")) {
+                        for (var i = 0; i < chunk.length; i++) {
+                            out.appendChild(tuner(chunk[i]));
+                        }
+                    } else {
+                        out.appendChild(glob.doc.createTextNode(chunk));
+                    }
+                    out.normalize && out.normalize();
+                    return out;
+                };
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            var tuned = tuner(value);
+            while (tuned.firstChild) {
+                node.appendChild(tuned.firstChild);
+            }
+        }
+        eve.stop();
+    })(-1);
+    function setFontSize(value) {
+        eve.stop();
+        if (value == +value) {
+            value += "px";
+        }
+        this.node.style.fontSize = value;
+    }
+    eve.on("snap.util.attr.fontSize", setFontSize)(-1);
+    eve.on("snap.util.attr.font-size", setFontSize)(-1);
+
+
+    eve.on("snap.util.getattr.transform", function () {
+        eve.stop();
+        return this.transform();
+    })(-1);
+    eve.on("snap.util.getattr.textpath", function () {
+        eve.stop();
+        return this.textPath;
+    })(-1);
+    // Markers
+    (function () {
+        function getter(end) {
+            return function () {
+                eve.stop();
+                var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end);
+                if (style == "none") {
+                    return style;
+                } else {
+                    return Snap(glob.doc.getElementById(style.match(reURLValue)[1]));
+                }
+            };
+        }
+        function setter(end) {
+            return function (value) {
+                eve.stop();
+                var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1);
+                if (value == "" || !value) {
+                    this.node.style[name] = "none";
+                    return;
+                }
+                if (value.type == "marker") {
+                    var id = value.node.id;
+                    if (!id) {
+                        $(value.node, {id: value.id});
+                    }
+                    this.node.style[name] = URL(id);
+                    return;
+                }
+            };
+        }
+        eve.on("snap.util.getattr.marker-end", getter("end"))(-1);
+        eve.on("snap.util.getattr.markerEnd", getter("end"))(-1);
+        eve.on("snap.util.getattr.marker-start", getter("start"))(-1);
+        eve.on("snap.util.getattr.markerStart", getter("start"))(-1);
+        eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1);
+        eve.on("snap.util.getattr.markerMid", getter("mid"))(-1);
+        eve.on("snap.util.attr.marker-end", setter("end"))(-1);
+        eve.on("snap.util.attr.markerEnd", setter("end"))(-1);
+        eve.on("snap.util.attr.marker-start", setter("start"))(-1);
+        eve.on("snap.util.attr.markerStart", setter("start"))(-1);
+        eve.on("snap.util.attr.marker-mid", setter("mid"))(-1);
+        eve.on("snap.util.attr.markerMid", setter("mid"))(-1);
+    }());
+    eve.on("snap.util.getattr.r", function () {
+        if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
+            eve.stop();
+            return $(this.node, "rx");
+        }
+    })(-1);
+    function textExtract(node) {
+        var out = [];
+        var children = node.childNodes;
+        for (var i = 0, ii = children.length; i < ii; i++) {
+            var chi = children[i];
+            if (chi.nodeType == 3) {
+                out.push(chi.nodeValue);
+            }
+            if (chi.tagName == "tspan") {
+                if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) {
+                    out.push(chi.firstChild.nodeValue);
+                } else {
+                    out.push(textExtract(chi));
+                }
+            }
+        }
+        return out;
+    }
+    eve.on("snap.util.getattr.text", function () {
+        if (this.type == "text" || this.type == "tspan") {
+            eve.stop();
+            var out = textExtract(this.node);
+            return out.length == 1 ? out[0] : out;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.#text", function () {
+        return this.node.textContent;
+    })(-1);
+    eve.on("snap.util.getattr.viewBox", function () {
+        eve.stop();
+        var vb = $(this.node, "viewBox");
+        if (vb) {
+            vb = vb.split(separator);
+            return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.points", function () {
+        var p = $(this.node, "points");
+        eve.stop();
+        if (p) {
+            return p.split(separator);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.path", function () {
+        var p = $(this.node, "d");
+        eve.stop();
+        return p;
+    })(-1);
+    eve.on("snap.util.getattr.class", function () {
+        return this.node.className.baseVal;
+    })(-1);
+    function getFontSize() {
+        eve.stop();
+        return this.node.style.fontSize;
+    }
+    eve.on("snap.util.getattr.fontSize", getFontSize)(-1);
+    eve.on("snap.util.getattr.font-size", getFontSize)(-1);
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var rgNotSpace = /\S+/g,
+        rgBadSpace = /[\t\r\n\f]/g,
+        rgTrim = /(^\s+|\s+$)/g,
+        Str = String,
+        elproto = Element.prototype;
+    /*\
+     * Element.addClass
+     [ method ]
+     **
+     * Adds given class name or list of class names to the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.addClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+
+        if (classes.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (!~pos) {
+                    curClasses.push(clazz);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.removeClass
+     [ method ]
+     **
+     * Removes given class name or list of class names from the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.removeClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        if (curClasses.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (~pos) {
+                    curClasses.splice(pos, 1);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.hasClass
+     [ method ]
+     **
+     * Checks if the element has a given class name in the list of class names applied to it.
+     - value (string) class name
+     **
+     = (boolean) `true` if the element has given class
+    \*/
+    elproto.hasClass = function (value) {
+        var elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [];
+        return !!~curClasses.indexOf(value);
+    };
+    /*\
+     * Element.toggleClass
+     [ method ]
+     **
+     * Add or remove one or more classes from the element, depending on either
+     * the class’s presence or the value of the `flag` argument.
+     - value (string) class name or space separated list of class names
+     - flag (boolean) value to determine whether the class should be added or removed
+     **
+     = (Element) original element.
+    \*/
+    elproto.toggleClass = function (value, flag) {
+        if (flag != null) {
+            if (flag) {
+                return this.addClass(value);
+            } else {
+                return this.removeClass(value);
+            }
+        }
+        var classes = (value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        j = 0;
+        while ((clazz = classes[j++])) {
+            pos = curClasses.indexOf(clazz);
+            if (~pos) {
+                curClasses.splice(pos, 1);
+            } else {
+                curClasses.push(clazz);
+            }
+        }
+
+        finalValue = curClasses.join(" ");
+        if (className != finalValue) {
+            elem.className.baseVal = finalValue;
+        }
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var operators = {
+            "+": function (x, y) {
+                    return x + y;
+                },
+            "-": function (x, y) {
+                    return x - y;
+                },
+            "/": function (x, y) {
+                    return x / y;
+                },
+            "*": function (x, y) {
+                    return x * y;
+                }
+        },
+        Str = String,
+        reUnit = /[a-z]+$/i,
+        reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    eve.on("snap.util.attr", function (val) {
+        var plus = Str(val).match(reAddon);
+        if (plus) {
+            var evnt = eve.nt(),
+                name = evnt.substring(evnt.lastIndexOf(".") + 1),
+                a = this.attr(name),
+                atr = {};
+            eve.stop();
+            var unit = plus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[plus[1]];
+            if (aUnit && aUnit == unit) {
+                val = op(parseFloat(a), +plus[2]);
+            } else {
+                a = this.asPX(name);
+                val = op(this.asPX(name), this.asPX(name, plus[2] + unit));
+            }
+            if (isNaN(a) || isNaN(val)) {
+                return;
+            }
+            atr[name] = val;
+            this.attr(atr);
+        }
+    })(-10);
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this,
+            bplus = Str(b).match(reAddon);
+        if (bplus) {
+            eve.stop();
+            var unit = bplus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[bplus[1]];
+            if (aUnit && aUnit == unit) {
+                return {
+                    from: parseFloat(a),
+                    to: op(parseFloat(a), +bplus[2]),
+                    f: getUnit(aUnit)
+                };
+            } else {
+                a = this.asPX(name);
+                return {
+                    from: a,
+                    to: op(a, this.asPX(name, bplus[2] + unit)),
+                    f: getNumber
+                };
+            }
+        }
+    })(-10);
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var proto = Paper.prototype,
+        is = Snap.is;
+    /*\
+     * Paper.rect
+     [ method ]
+     *
+     * Draws a rectangle
+     **
+     - x (number) x coordinate of the top left corner
+     - y (number) y coordinate of the top left corner
+     - width (number) width
+     - height (number) height
+     - rx (number) #optional horizontal radius for rounded corners, default is 0
+     - ry (number) #optional vertical radius for rounded corners, default is rx or 0
+     = (object) the `rect` element
+     **
+     > Usage
+     | // regular rectangle
+     | var c = paper.rect(10, 10, 50, 50);
+     | // rectangle with rounded corners
+     | var c = paper.rect(40, 40, 50, 50, 10);
+    \*/
+    proto.rect = function (x, y, w, h, rx, ry) {
+        var attr;
+        if (ry == null) {
+            ry = rx;
+        }
+        if (is(x, "object") && x == "[object Object]") {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                width: w,
+                height: h
+            };
+            if (rx != null) {
+                attr.rx = rx;
+                attr.ry = ry;
+            }
+        }
+        return this.el("rect", attr);
+    };
+    /*\
+     * Paper.circle
+     [ method ]
+     **
+     * Draws a circle
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - r (number) radius
+     = (object) the `circle` element
+     **
+     > Usage
+     | var c = paper.circle(50, 50, 40);
+    \*/
+    proto.circle = function (cx, cy, r) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr = {
+                cx: cx,
+                cy: cy,
+                r: r
+            };
+        }
+        return this.el("circle", attr);
+    };
+
+    var preload = (function () {
+        function onerror() {
+            this.parentNode.removeChild(this);
+        }
+        return function (src, f) {
+            var img = glob.doc.createElement("img"),
+                body = glob.doc.body;
+            img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+            img.onload = function () {
+                f.call(img);
+                img.onload = img.onerror = null;
+                body.removeChild(img);
+            };
+            img.onerror = onerror;
+            body.appendChild(img);
+            img.src = src;
+        };
+    }());
+
+    /*\
+     * Paper.image
+     [ method ]
+     **
+     * Places an image on the surface
+     **
+     - src (string) URI of the source image
+     - x (number) x offset position
+     - y (number) y offset position
+     - width (number) width of the image
+     - height (number) height of the image
+     = (object) the `image` element
+     * or
+     = (object) Snap element object with type `image`
+     **
+     > Usage
+     | var c = paper.image("apple.png", 10, 10, 80, 80);
+    \*/
+    proto.image = function (src, x, y, width, height) {
+        var el = this.el("image");
+        if (is(src, "object") && "src" in src) {
+            el.attr(src);
+        } else if (src != null) {
+            var set = {
+                "xlink:href": src,
+                preserveAspectRatio: "none"
+            };
+            if (x != null && y != null) {
+                set.x = x;
+                set.y = y;
+            }
+            if (width != null && height != null) {
+                set.width = width;
+                set.height = height;
+            } else {
+                preload(src, function () {
+                    Snap._.$(el.node, {
+                        width: this.offsetWidth,
+                        height: this.offsetHeight
+                    });
+                });
+            }
+            Snap._.$(el.node, set);
+        }
+        return el;
+    };
+    /*\
+     * Paper.ellipse
+     [ method ]
+     **
+     * Draws an ellipse
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - rx (number) horizontal radius
+     - ry (number) vertical radius
+     = (object) the `ellipse` element
+     **
+     > Usage
+     | var c = paper.ellipse(50, 50, 40, 20);
+    \*/
+    proto.ellipse = function (cx, cy, rx, ry) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr ={
+                cx: cx,
+                cy: cy,
+                rx: rx,
+                ry: ry
+            };
+        }
+        return this.el("ellipse", attr);
+    };
+    // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier.
+    /*\
+     * Paper.path
+     [ method ]
+     **
+     * Creates a `<path>` element using the given string as the path's definition
+     - pathString (string) #optional path string in SVG format
+     * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:
+     | "M10,20L30,40"
+     * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.
+     *
+     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
+     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
+     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
+     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
+     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
+     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
+     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
+     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
+     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
+     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
+     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
+     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
+     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
+     * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier.
+     * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point.
+     > Usage
+     | var c = paper.path("M10 10L90 90");
+     | // draw a diagonal line:
+     | // move to 10,10, line to 90,90
+    \*/
+    proto.path = function (d) {
+        var attr;
+        if (is(d, "object") && !is(d, "array")) {
+            attr = d;
+        } else if (d) {
+            attr = {d: d};
+        }
+        return this.el("path", attr);
+    };
+    /*\
+     * Paper.g
+     [ method ]
+     **
+     * Creates a group element
+     **
+     - varargs (…) #optional elements to nest within the group
+     = (object) the `g` element
+     **
+     > Usage
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g(c2, c1); // note that the order of elements is different
+     * or
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g();
+     | g.add(c2, c1);
+    \*/
+    /*\
+     * Paper.group
+     [ method ]
+     **
+     * See @Paper.g
+    \*/
+    proto.group = proto.g = function (first) {
+        var attr,
+            el = this.el("g");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.svg
+     [ method ]
+     **
+     * Creates a nested SVG element.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `svg` element
+     **
+    \*/
+    proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) {
+        var attrs = {};
+        if (is(x, "object") && y == null) {
+            attrs = x;
+        } else {
+            if (x != null) {
+                attrs.x = x;
+            }
+            if (y != null) {
+                attrs.y = y;
+            }
+            if (width != null) {
+                attrs.width = width;
+            }
+            if (height != null) {
+                attrs.height = height;
+            }
+            if (vbx != null && vby != null && vbw != null && vbh != null) {
+                attrs.viewBox = [vbx, vby, vbw, vbh];
+            }
+        }
+        return this.el("svg", attrs);
+    };
+    /*\
+     * Paper.mask
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a mask.
+     **
+     = (object) the `mask` element
+     **
+    \*/
+    proto.mask = function (first) {
+        var attr,
+            el = this.el("mask");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.ptrn
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a pattern.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `pattern` element
+     **
+    \*/
+    proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) {
+        if (is(x, "object")) {
+            var attr = x;
+        } else {
+            attr = {patternUnits: "userSpaceOnUse"};
+            if (x) {
+                attr.x = x;
+            }
+            if (y) {
+                attr.y = y;
+            }
+            if (width != null) {
+                attr.width = width;
+            }
+            if (height != null) {
+                attr.height = height;
+            }
+            if (vx != null && vy != null && vw != null && vh != null) {
+                attr.viewBox = [vx, vy, vw, vh];
+            } else {
+                attr.viewBox = [x || 0, y || 0, width || 0, height || 0];
+            }
+        }
+        return this.el("pattern", attr);
+    };
+    /*\
+     * Paper.use
+     [ method ]
+     **
+     * Creates a <use> element.
+     - id (string) @optional id of element to link
+     * or
+     - id (Element) @optional element to link
+     **
+     = (object) the `use` element
+     **
+    \*/
+    proto.use = function (id) {
+        if (id != null) {
+            if (id instanceof Element) {
+                if (!id.attr("id")) {
+                    id.attr({id: Snap._.id(id)});
+                }
+                id = id.attr("id");
+            }
+            if (String(id).charAt() == "#") {
+                id = id.substring(1);
+            }
+            return this.el("use", {"xlink:href": "#" + id});
+        } else {
+            return Element.prototype.use.call(this);
+        }
+    };
+    /*\
+     * Paper.symbol
+     [ method ]
+     **
+     * Creates a <symbol> element.
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     = (object) the `symbol` element
+     **
+    \*/
+    proto.symbol = function (vx, vy, vw, vh) {
+        var attr = {};
+        if (vx != null && vy != null && vw != null && vh != null) {
+            attr.viewBox = [vx, vy, vw, vh];
+        }
+
+        return this.el("symbol", attr);
+    };
+    /*\
+     * Paper.text
+     [ method ]
+     **
+     * Draws a text string
+     **
+     - x (number) x coordinate position
+     - y (number) y coordinate position
+     - text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
+     = (object) the `text` element
+     **
+     > Usage
+     | var t1 = paper.text(50, 50, "Snap");
+     | var t2 = paper.text(50, 50, ["S","n","a","p"]);
+     | // Text path usage
+     | t1.attr({textpath: "M10,10L100,100"});
+     | // or
+     | var pth = paper.path("M10,10L100,100");
+     | t1.attr({textpath: pth});
+    \*/
+    proto.text = function (x, y, text) {
+        var attr = {};
+        if (is(x, "object")) {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                text: text || ""
+            };
+        }
+        return this.el("text", attr);
+    };
+    /*\
+     * Paper.line
+     [ method ]
+     **
+     * Draws a line
+     **
+     - x1 (number) x coordinate position of the start
+     - y1 (number) y coordinate position of the start
+     - x2 (number) x coordinate position of the end
+     - y2 (number) y coordinate position of the end
+     = (object) the `line` element
+     **
+     > Usage
+     | var t1 = paper.line(50, 50, 100, 100);
+    \*/
+    proto.line = function (x1, y1, x2, y2) {
+        var attr = {};
+        if (is(x1, "object")) {
+            attr = x1;
+        } else if (x1 != null) {
+            attr = {
+                x1: x1,
+                x2: x2,
+                y1: y1,
+                y2: y2
+            };
+        }
+        return this.el("line", attr);
+    };
+    /*\
+     * Paper.polyline
+     [ method ]
+     **
+     * Draws a polyline
+     **
+     - points (array) array of points
+     * or
+     - varargs (…) points
+     = (object) the `polyline` element
+     **
+     > Usage
+     | var p1 = paper.polyline([10, 10, 100, 100]);
+     | var p2 = paper.polyline(10, 10, 100, 100);
+    \*/
+    proto.polyline = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polyline", attr);
+    };
+    /*\
+     * Paper.polygon
+     [ method ]
+     **
+     * Draws a polygon. See @Paper.polyline
+    \*/
+    proto.polygon = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polygon", attr);
+    };
+    // gradients
+    (function () {
+        var $ = Snap._.$;
+        // gradients' helpers
+        function Gstops() {
+            return this.selectAll("stop");
+        }
+        function GaddStop(color, offset) {
+            var stop = $("stop"),
+                attr = {
+                    offset: +offset + "%"
+                };
+            color = Snap.color(color);
+            attr["stop-color"] = color.hex;
+            if (color.opacity < 1) {
+                attr["stop-opacity"] = color.opacity;
+            }
+            $(stop, attr);
+            this.node.appendChild(stop);
+            return this;
+        }
+        function GgetBBox() {
+            if (this.type == "linearGradient") {
+                var x1 = $(this.node, "x1") || 0,
+                    x2 = $(this.node, "x2") || 1,
+                    y1 = $(this.node, "y1") || 0,
+                    y2 = $(this.node, "y2") || 0;
+                return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1));
+            } else {
+                var cx = this.node.cx || .5,
+                    cy = this.node.cy || .5,
+                    r = this.node.r || 0;
+                return Snap._.box(cx - r, cy - r, r * 2, r * 2);
+            }
+        }
+        function gradient(defs, str) {
+            var grad = eve("snap.util.grad.parse", null, str).firstDefined(),
+                el;
+            if (!grad) {
+                return null;
+            }
+            grad.params.unshift(defs);
+            if (grad.type.toLowerCase() == "l") {
+                el = gradientLinear.apply(0, grad.params);
+            } else {
+                el = gradientRadial.apply(0, grad.params);
+            }
+            if (grad.type != grad.type.toLowerCase()) {
+                $(el.node, {
+                    gradientUnits: "userSpaceOnUse"
+                });
+            }
+            var stops = grad.stops,
+                len = stops.length,
+                start = 0,
+                j = 0;
+            function seed(i, end) {
+                var step = (end - start) / (i - j);
+                for (var k = j; k < i; k++) {
+                    stops[k].offset = +(+start + step * (k - j)).toFixed(2);
+                }
+                j = i;
+                start = end;
+            }
+            len--;
+            for (var i = 0; i < len; i++) if ("offset" in stops[i]) {
+                seed(i, stops[i].offset);
+            }
+            stops[len].offset = stops[len].offset || 100;
+            seed(len, stops[len].offset);
+            for (i = 0; i <= len; i++) {
+                var stop = stops[i];
+                el.addStop(stop.color, stop.offset);
+            }
+            return el;
+        }
+        function gradientLinear(defs, x1, y1, x2, y2) {
+            var el = Snap._.make("linearGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (x1 != null) {
+                $(el.node, {
+                    x1: x1,
+                    y1: y1,
+                    x2: x2,
+                    y2: y2
+                });
+            }
+            return el;
+        }
+        function gradientRadial(defs, cx, cy, r, fx, fy) {
+            var el = Snap._.make("radialGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (cx != null) {
+                $(el.node, {
+                    cx: cx,
+                    cy: cy,
+                    r: r
+                });
+            }
+            if (fx != null && fy != null) {
+                $(el.node, {
+                    fx: fx,
+                    fy: fy
+                });
+            }
+            return el;
+        }
+        /*\
+         * Paper.gradient
+         [ method ]
+         **
+         * Creates a gradient element
+         **
+         - gradient (string) gradient descriptor
+         > Gradient Descriptor
+         * The gradient descriptor is an expression formatted as
+         * follows: `<type>(<coords>)<colors>`.  The `<type>` can be
+         * either linear or radial.  The uppercase `L` or `R` letters
+         * indicate absolute coordinates offset from the SVG surface.
+         * Lowercase `l` or `r` letters indicate coordinates
+         * calculated relative to the element to which the gradient is
+         * applied.  Coordinates specify a linear gradient vector as
+         * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`,
+         * `r` and optional `fx`, `fy` specifying a focal point away
+         * from the center of the circle. Specify `<colors>` as a list
+         * of dash-separated CSS color values.  Each color may be
+         * followed by a custom offset value, separated with a colon
+         * character.
+         > Examples
+         * Linear gradient, relative from top-left corner to bottom-right
+         * corner, from black through red to white:
+         | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
+         * Linear gradient, absolute from (0, 0) to (100, 100), from black
+         * through red at 25% to white:
+         | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");
+         * Radial gradient, relative from the center of the element with radius
+         * half the width, from black to white:
+         | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
+         * To apply the gradient:
+         | paper.circle(50, 50, 40).attr({
+         |     fill: g
+         | });
+         = (object) the `gradient` element
+        \*/
+        proto.gradient = function (str) {
+            return gradient(this.defs, str);
+        };
+        proto.gradientLinear = function (x1, y1, x2, y2) {
+            return gradientLinear(this.defs, x1, y1, x2, y2);
+        };
+        proto.gradientRadial = function (cx, cy, r, fx, fy) {
+            return gradientRadial(this.defs, cx, cy, r, fx, fy);
+        };
+        /*\
+         * Paper.toString
+         [ method ]
+         **
+         * Returns SVG code for the @Paper
+         = (string) SVG code for the @Paper
+        \*/
+        proto.toString = function () {
+            var doc = this.node.ownerDocument,
+                f = doc.createDocumentFragment(),
+                d = doc.createElement("div"),
+                svg = this.node.cloneNode(true),
+                res;
+            f.appendChild(d);
+            d.appendChild(svg);
+            Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"});
+            res = d.innerHTML;
+            f.removeChild(f.firstChild);
+            return res;
+        };
+        /*\
+         * Paper.toDataURL
+         [ method ]
+         **
+         * Returns SVG code for the @Paper as Data URI string.
+         = (string) Data URI string
+        \*/
+        proto.toDataURL = function () {
+            if (window && window.btoa) {
+                return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this)));
+            }
+        };
+        /*\
+         * Paper.clear
+         [ method ]
+         **
+         * Removes all child nodes of the paper, except <defs>.
+        \*/
+        proto.clear = function () {
+            var node = this.node.firstChild,
+                next;
+            while (node) {
+                next = node.nextSibling;
+                if (node.tagName != "defs") {
+                    node.parentNode.removeChild(node);
+                } else {
+                    proto.clear.call({node: node});
+                }
+                node = next;
+            }
+        };
+    }());
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        clone = Snap._.clone,
+        has = "hasOwnProperty",
+        p2s = /,?([a-z]),?/gi,
+        toFloat = parseFloat,
+        math = Math,
+        PI = math.PI,
+        mmin = math.min,
+        mmax = math.max,
+        pow = math.pow,
+        abs = math.abs;
+    function paths(ps) {
+        var p = paths.ps = paths.ps || {};
+        if (p[ps]) {
+            p[ps].sleep = 100;
+        } else {
+            p[ps] = {
+                sleep: 100
+            };
+        }
+        setTimeout(function () {
+            for (var key in p) if (p[has](key) && key != ps) {
+                p[key].sleep--;
+                !p[key].sleep && delete p[key];
+            }
+        });
+        return p[ps];
+    }
+    function box(x, y, width, height) {
+        if (x == null) {
+            x = y = width = height = 0;
+        }
+        if (y == null) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        return {
+            x: x,
+            y: y,
+            width: width,
+            w: width,
+            height: height,
+            h: height,
+            x2: x + width,
+            y2: y + height,
+            cx: x + width / 2,
+            cy: y + height / 2,
+            r1: math.min(width, height) / 2,
+            r2: math.max(width, height) / 2,
+            r0: math.sqrt(width * width + height * height) / 2,
+            path: rectPath(x, y, width, height),
+            vb: [x, y, width, height].join(" ")
+        };
+    }
+    function toString() {
+        return this.join(",").replace(p2s, "$1");
+    }
+    function pathClone(pathArray) {
+        var res = clone(pathArray);
+        res.toString = toString;
+        return res;
+    }
+    function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        if (length == null) {
+            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+        } else {
+            return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
+                getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+        }
+    }
+    function getLengthFactory(istotal, subpath) {
+        function O(val) {
+            return +(+val).toFixed(3);
+        }
+        return Snap._.cacher(function (path, length, onlystart) {
+            if (path instanceof Element) {
+                path = path.attr("d");
+            }
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            sp += [
+                                "C" + O(point.start.x),
+                                O(point.start.y),
+                                O(point.m.x),
+                                O(point.m.y),
+                                O(point.x),
+                                O(point.y)
+                            ];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = [
+                                "M" + O(point.x),
+                                O(point.y) + "C" + O(point.n.x),
+                                O(point.n.y),
+                                O(point.end.x),
+                                O(point.end.y),
+                                O(p[5]),
+                                O(p[6])
+                            ].join();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            return point;
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p.shift() + p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+            return point;
+        }, null, Snap._.clone);
+    }
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            t13 = pow(t1, 3),
+            t12 = pow(t1, 2),
+            t2 = t * t,
+            t3 = t2 * t,
+            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+            ax = t1 * p1x + t * c1x,
+            ay = t1 * p1y + t * c1y,
+            cx = t1 * c2x + t * p2x,
+            cy = t1 * c2y + t * p2y,
+            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+        // (mx > nx || my < ny) && (alpha += 180);
+        return {
+            x: x,
+            y: y,
+            m: {x: mx, y: my},
+            n: {x: nx, y: ny},
+            start: {x: ax, y: ay},
+            end: {x: cx, y: cy},
+            alpha: alpha
+        };
+    }
+    function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        if (!Snap.is(p1x, "array")) {
+            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+        }
+        var bbox = curveDim.apply(null, p1x);
+        return box(
+            bbox.min.x,
+            bbox.min.y,
+            bbox.max.x - bbox.min.x,
+            bbox.max.y - bbox.min.y
+        );
+    }
+    function isPointInsideBBox(bbox, x, y) {
+        return  x >= bbox.x &&
+                x <= bbox.x + bbox.width &&
+                y >= bbox.y &&
+                y <= bbox.y + bbox.height;
+    }
+    function isBBoxIntersect(bbox1, bbox2) {
+        bbox1 = box(bbox1);
+        bbox2 = box(bbox2);
+        return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2)
+            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
+                || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
+                || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+    }
+    function base3(t, p1, p2, p3, p4) {
+        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+        return t * t2 - 3 * p1 + 3 * p2;
+    }
+    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+        if (z == null) {
+            z = 1;
+        }
+        z = z > 1 ? 1 : z < 0 ? 0 : z;
+        var z2 = z / 2,
+            n = 12,
+            Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
+            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
+            sum = 0;
+        for (var i = 0; i < n; i++) {
+            var ct = z2 * Tvalues[i] + z2,
+                xbase = base3(ct, x1, x2, x3, x4),
+                ybase = base3(ct, y1, y2, y3, y4),
+                comb = xbase * xbase + ybase * ybase;
+            sum += Cvalues[i] * math.sqrt(comb);
+        }
+        return z2 * sum;
+    }
+    function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+            return;
+        }
+        var t = 1,
+            step = t / 2,
+            t2 = t - step,
+            l,
+            e = .01;
+        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        while (abs(l - ll) > e) {
+            step /= 2;
+            t2 += (l < ll ? 1 : -1) * step;
+            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        }
+        return t2;
+    }
+    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+        if (
+            mmax(x1, x2) < mmin(x3, x4) ||
+            mmin(x1, x2) > mmax(x3, x4) ||
+            mmax(y1, y2) < mmin(y3, y4) ||
+            mmin(y1, y2) > mmax(y3, y4)
+        ) {
+            return;
+        }
+        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+        if (!denominator) {
+            return;
+        }
+        var px = nx / denominator,
+            py = ny / denominator,
+            px2 = +px.toFixed(2),
+            py2 = +py.toFixed(2);
+        if (
+            px2 < +mmin(x1, x2).toFixed(2) ||
+            px2 > +mmax(x1, x2).toFixed(2) ||
+            px2 < +mmin(x3, x4).toFixed(2) ||
+            px2 > +mmax(x3, x4).toFixed(2) ||
+            py2 < +mmin(y1, y2).toFixed(2) ||
+            py2 > +mmax(y1, y2).toFixed(2) ||
+            py2 < +mmin(y3, y4).toFixed(2) ||
+            py2 > +mmax(y3, y4).toFixed(2)
+        ) {
+            return;
+        }
+        return {x: px, y: py};
+    }
+    function inter(bez1, bez2) {
+        return interHelper(bez1, bez2);
+    }
+    function interCount(bez1, bez2) {
+        return interHelper(bez1, bez2, 1);
+    }
+    function interHelper(bez1, bez2, justCount) {
+        var bbox1 = bezierBBox(bez1),
+            bbox2 = bezierBBox(bez2);
+        if (!isBBoxIntersect(bbox1, bbox2)) {
+            return justCount ? 0 : [];
+        }
+        var l1 = bezlen.apply(0, bez1),
+            l2 = bezlen.apply(0, bez2),
+            n1 = ~~(l1 / 8),
+            n2 = ~~(l2 / 8),
+            dots1 = [],
+            dots2 = [],
+            xy = {},
+            res = justCount ? 0 : [];
+        for (var i = 0; i < n1 + 1; i++) {
+            var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
+            dots1.push({x: p.x, y: p.y, t: i / n1});
+        }
+        for (i = 0; i < n2 + 1; i++) {
+            p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
+            dots2.push({x: p.x, y: p.y, t: i / n2});
+        }
+        for (i = 0; i < n1; i++) {
+            for (var j = 0; j < n2; j++) {
+                var di = dots1[i],
+                    di1 = dots1[i + 1],
+                    dj = dots2[j],
+                    dj1 = dots2[j + 1],
+                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+                if (is) {
+                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+                        continue;
+                    }
+                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
+                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+                        if (justCount) {
+                            res++;
+                        } else {
+                            res.push({
+                                x: is.x,
+                                y: is.y,
+                                t1: t1,
+                                t2: t2
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function pathIntersection(path1, path2) {
+        return interPathHelper(path1, path2);
+    }
+    function pathIntersectionNumber(path1, path2) {
+        return interPathHelper(path1, path2, 1);
+    }
+    function interPathHelper(path1, path2, justCount) {
+        path1 = path2curve(path1);
+        path2 = path2curve(path2);
+        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+            res = justCount ? 0 : [];
+        for (var i = 0, ii = path1.length; i < ii; i++) {
+            var pi = path1[i];
+            if (pi[0] == "M") {
+                x1 = x1m = pi[1];
+                y1 = y1m = pi[2];
+            } else {
+                if (pi[0] == "C") {
+                    bez1 = [x1, y1].concat(pi.slice(1));
+                    x1 = bez1[6];
+                    y1 = bez1[7];
+                } else {
+                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+                    x1 = x1m;
+                    y1 = y1m;
+                }
+                for (var j = 0, jj = path2.length; j < jj; j++) {
+                    var pj = path2[j];
+                    if (pj[0] == "M") {
+                        x2 = x2m = pj[1];
+                        y2 = y2m = pj[2];
+                    } else {
+                        if (pj[0] == "C") {
+                            bez2 = [x2, y2].concat(pj.slice(1));
+                            x2 = bez2[6];
+                            y2 = bez2[7];
+                        } else {
+                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+                            x2 = x2m;
+                            y2 = y2m;
+                        }
+                        var intr = interHelper(bez1, bez2, justCount);
+                        if (justCount) {
+                            res += intr;
+                        } else {
+                            for (var k = 0, kk = intr.length; k < kk; k++) {
+                                intr[k].segment1 = i;
+                                intr[k].segment2 = j;
+                                intr[k].bez1 = bez1;
+                                intr[k].bez2 = bez2;
+                            }
+                            res = res.concat(intr);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function isPointInsidePath(path, x, y) {
+        var bbox = pathBBox(path);
+        return isPointInsideBBox(bbox, x, y) &&
+               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+    }
+    function pathBBox(path) {
+        var pth = paths(path);
+        if (pth.bbox) {
+            return clone(pth.bbox);
+        }
+        if (!path) {
+            return box();
+        }
+        path = path2curve(path);
+        var x = 0,
+            y = 0,
+            X = [],
+            Y = [],
+            p;
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            p = path[i];
+            if (p[0] == "M") {
+                x = p[1];
+                y = p[2];
+                X.push(x);
+                Y.push(y);
+            } else {
+                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                X = X.concat(dim.min.x, dim.max.x);
+                Y = Y.concat(dim.min.y, dim.max.y);
+                x = p[5];
+                y = p[6];
+            }
+        }
+        var xmin = mmin.apply(0, X),
+            ymin = mmin.apply(0, Y),
+            xmax = mmax.apply(0, X),
+            ymax = mmax.apply(0, Y),
+            bb = box(xmin, ymin, xmax - xmin, ymax - ymin);
+        pth.bbox = clone(bb);
+        return bb;
+    }
+    function rectPath(x, y, w, h, r) {
+        if (r) {
+            return [
+                ["M", +x + (+r), y],
+                ["l", w - r * 2, 0],
+                ["a", r, r, 0, 0, 1, r, r],
+                ["l", 0, h - r * 2],
+                ["a", r, r, 0, 0, 1, -r, r],
+                ["l", r * 2 - w, 0],
+                ["a", r, r, 0, 0, 1, -r, -r],
+                ["l", 0, r * 2 - h],
+                ["a", r, r, 0, 0, 1, r, -r],
+                ["z"]
+            ];
+        }
+        var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+        res.toString = toString;
+        return res;
+    }
+    function ellipsePath(x, y, rx, ry, a) {
+        if (a == null && ry == null) {
+            ry = rx;
+        }
+        x = +x;
+        y = +y;
+        rx = +rx;
+        ry = +ry;
+        if (a != null) {
+            var rad = Math.PI / 180,
+                x1 = x + rx * Math.cos(-ry * rad),
+                x2 = x + rx * Math.cos(-a * rad),
+                y1 = y + rx * Math.sin(-ry * rad),
+                y2 = y + rx * Math.sin(-a * rad),
+                res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]];
+        } else {
+            res = [
+                ["M", x, y],
+                ["m", 0, -ry],
+                ["a", rx, ry, 0, 1, 1, 0, 2 * ry],
+                ["a", rx, ry, 0, 1, 1, 0, -2 * ry],
+                ["z"]
+            ];
+        }
+        res.toString = toString;
+        return res;
+    }
+    var unit2px = Snap._unit2px,
+        getPath = {
+        path: function (el) {
+            return el.attr("path");
+        },
+        circle: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx, attr.cy, attr.r);
+        },
+        ellipse: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry);
+        },
+        rect: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry);
+        },
+        image: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height);
+        },
+        line: function (el) {
+            return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")];
+        },
+        polyline: function (el) {
+            return "M" + el.attr("points");
+        },
+        polygon: function (el) {
+            return "M" + el.attr("points") + "z";
+        },
+        deflt: function (el) {
+            var bbox = el.node.getBBox();
+            return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+        }
+    };
+    function pathToRelative(pathArray) {
+        var pth = paths(pathArray),
+            lowerCase = String.prototype.toLowerCase;
+        if (pth.rel) {
+            return pathClone(pth.rel);
+        }
+        if (!Snap.is(pathArray, "array") || !Snap.is(pathArray && pathArray[0], "array")) {
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0;
+        if (pathArray[0][0] == "M") {
+            x = pathArray[0][1];
+            y = pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res.push(["M", x, y]);
+        }
+        for (var i = start, ii = pathArray.length; i < ii; i++) {
+            var r = res[i] = [],
+                pa = pathArray[i];
+            if (pa[0] != lowerCase.call(pa[0])) {
+                r[0] = lowerCase.call(pa[0]);
+                switch (r[0]) {
+                    case "a":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +(pa[6] - x).toFixed(3);
+                        r[7] = +(pa[7] - y).toFixed(3);
+                        break;
+                    case "v":
+                        r[1] = +(pa[1] - y).toFixed(3);
+                        break;
+                    case "m":
+                        mx = pa[1];
+                        my = pa[2];
+                    default:
+                        for (var j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+                        }
+                }
+            } else {
+                r = res[i] = [];
+                if (pa[0] == "m") {
+                    mx = pa[1] + x;
+                    my = pa[2] + y;
+                }
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    res[i][k] = pa[k];
+                }
+            }
+            var len = res[i].length;
+            switch (res[i][0]) {
+                case "z":
+                    x = mx;
+                    y = my;
+                    break;
+                case "h":
+                    x += +res[i][len - 1];
+                    break;
+                case "v":
+                    y += +res[i][len - 1];
+                    break;
+                default:
+                    x += +res[i][len - 2];
+                    y += +res[i][len - 1];
+            }
+        }
+        res.toString = toString;
+        pth.rel = pathClone(res);
+        return res;
+    }
+    function pathToAbsolute(pathArray) {
+        var pth = paths(pathArray);
+        if (pth.abs) {
+            return pathClone(pth.abs);
+        }
+        if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        if (!pathArray || !pathArray.length) {
+            return [["M", 0, 0]];
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0,
+            pa0;
+        if (pathArray[0][0] == "M") {
+            x = +pathArray[0][1];
+            y = +pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res[0] = ["M", x, y];
+        }
+        var crz = pathArray.length == 3 &&
+            pathArray[0][0] == "M" &&
+            pathArray[1][0].toUpperCase() == "R" &&
+            pathArray[2][0].toUpperCase() == "Z";
+        for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+            res.push(r = []);
+            pa = pathArray[i];
+            pa0 = pa[0];
+            if (pa0 != pa0.toUpperCase()) {
+                r[0] = pa0.toUpperCase();
+                switch (r[0]) {
+                    case "A":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +pa[6] + x;
+                        r[7] = +pa[7] + y;
+                        break;
+                    case "V":
+                        r[1] = +pa[1] + y;
+                        break;
+                    case "H":
+                        r[1] = +pa[1] + x;
+                        break;
+                    case "R":
+                        var dots = [x, y].concat(pa.slice(1));
+                        for (var j = 2, jj = dots.length; j < jj; j++) {
+                            dots[j] = +dots[j] + x;
+                            dots[++j] = +dots[j] + y;
+                        }
+                        res.pop();
+                        res = res.concat(catmullRom2bezier(dots, crz));
+                        break;
+                    case "O":
+                        res.pop();
+                        dots = ellipsePath(x, y, pa[1], pa[2]);
+                        dots.push(dots[0]);
+                        res = res.concat(dots);
+                        break;
+                    case "U":
+                        res.pop();
+                        res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                        r = ["U"].concat(res[res.length - 1].slice(-2));
+                        break;
+                    case "M":
+                        mx = +pa[1] + x;
+                        my = +pa[2] + y;
+                    default:
+                        for (j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +pa[j] + ((j % 2) ? x : y);
+                        }
+                }
+            } else if (pa0 == "R") {
+                dots = [x, y].concat(pa.slice(1));
+                res.pop();
+                res = res.concat(catmullRom2bezier(dots, crz));
+                r = ["R"].concat(pa.slice(-2));
+            } else if (pa0 == "O") {
+                res.pop();
+                dots = ellipsePath(x, y, pa[1], pa[2]);
+                dots.push(dots[0]);
+                res = res.concat(dots);
+            } else if (pa0 == "U") {
+                res.pop();
+                res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                r = ["U"].concat(res[res.length - 1].slice(-2));
+            } else {
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    r[k] = pa[k];
+                }
+            }
+            pa0 = pa0.toUpperCase();
+            if (pa0 != "O") {
+                switch (r[0]) {
+                    case "Z":
+                        x = +mx;
+                        y = +my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
+                    default:
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
+                }
+            }
+        }
+        res.toString = toString;
+        pth.abs = pathClone(res);
+        return res;
+    }
+    function l2c(x1, y1, x2, y2) {
+        return [x1, y1, x2, y2, x2, y2];
+    }
+    function q2c(x1, y1, ax, ay, x2, y2) {
+        var _13 = 1 / 3,
+            _23 = 2 / 3;
+        return [
+                _13 * x1 + _23 * ax,
+                _13 * y1 + _23 * ay,
+                _13 * x2 + _23 * ax,
+                _13 * y2 + _23 * ay,
+                x2,
+                y2
+            ];
+    }
+    function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+        // for more information of where this math came from visit:
+        // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+        var _120 = PI * 120 / 180,
+            rad = PI / 180 * (+angle || 0),
+            res = [],
+            xy,
+            rotate = Snap._.cacher(function (x, y, rad) {
+                var X = x * math.cos(rad) - y * math.sin(rad),
+                    Y = x * math.sin(rad) + y * math.cos(rad);
+                return {x: X, y: Y};
+            });
+        if (!recursive) {
+            xy = rotate(x1, y1, -rad);
+            x1 = xy.x;
+            y1 = xy.y;
+            xy = rotate(x2, y2, -rad);
+            x2 = xy.x;
+            y2 = xy.y;
+            var cos = math.cos(PI / 180 * angle),
+                sin = math.sin(PI / 180 * angle),
+                x = (x1 - x2) / 2,
+                y = (y1 - y2) / 2;
+            var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+            if (h > 1) {
+                h = math.sqrt(h);
+                rx = h * rx;
+                ry = h * ry;
+            }
+            var rx2 = rx * rx,
+                ry2 = ry * ry,
+                k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                    math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                cx = k * rx * y / ry + (x1 + x2) / 2,
+                cy = k * -ry * x / rx + (y1 + y2) / 2,
+                f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+                f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+            f1 = x1 < cx ? PI - f1 : f1;
+            f2 = x2 < cx ? PI - f2 : f2;
+            f1 < 0 && (f1 = PI * 2 + f1);
+            f2 < 0 && (f2 = PI * 2 + f2);
+            if (sweep_flag && f1 > f2) {
+                f1 = f1 - PI * 2;
+            }
+            if (!sweep_flag && f2 > f1) {
+                f2 = f2 - PI * 2;
+            }
+        } else {
+            f1 = recursive[0];
+            f2 = recursive[1];
+            cx = recursive[2];
+            cy = recursive[3];
+        }
+        var df = f2 - f1;
+        if (abs(df) > _120) {
+            var f2old = f2,
+                x2old = x2,
+                y2old = y2;
+            f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+            x2 = cx + rx * math.cos(f2);
+            y2 = cy + ry * math.sin(f2);
+            res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+        }
+        df = f2 - f1;
+        var c1 = math.cos(f1),
+            s1 = math.sin(f1),
+            c2 = math.cos(f2),
+            s2 = math.sin(f2),
+            t = math.tan(df / 4),
+            hx = 4 / 3 * rx * t,
+            hy = 4 / 3 * ry * t,
+            m1 = [x1, y1],
+            m2 = [x1 + hx * s1, y1 - hy * c1],
+            m3 = [x2 + hx * s2, y2 - hy * c2],
+            m4 = [x2, y2];
+        m2[0] = 2 * m1[0] - m2[0];
+        m2[1] = 2 * m1[1] - m2[1];
+        if (recursive) {
+            return [m2, m3, m4].concat(res);
+        } else {
+            res = [m2, m3, m4].concat(res).join().split(",");
+            var newres = [];
+            for (var i = 0, ii = res.length; i < ii; i++) {
+                newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+            }
+            return newres;
+        }
+    }
+    function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t;
+        return {
+            x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+            y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+        };
+    }
+
+    // Returns bounding box of cubic bezier curve.
+    // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+    // Original version: NISHIO Hirokazu
+    // Modifications: https://github.com/timo22345
+    function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) {
+        var tvalues = [],
+            bounds = [[], []],
+            a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+        for (var i = 0; i < 2; ++i) {
+            if (i == 0) {
+                b = 6 * x0 - 12 * x1 + 6 * x2;
+                a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+                c = 3 * x1 - 3 * x0;
+            } else {
+                b = 6 * y0 - 12 * y1 + 6 * y2;
+                a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+                c = 3 * y1 - 3 * y0;
+            }
+            if (abs(a) < 1e-12) {
+                if (abs(b) < 1e-12) {
+                    continue;
+                }
+                t = -c / b;
+                if (0 < t && t < 1) {
+                    tvalues.push(t);
+                }
+                continue;
+            }
+            b2ac = b * b - 4 * c * a;
+            sqrtb2ac = math.sqrt(b2ac);
+            if (b2ac < 0) {
+                continue;
+            }
+            t1 = (-b + sqrtb2ac) / (2 * a);
+            if (0 < t1 && t1 < 1) {
+                tvalues.push(t1);
+            }
+            t2 = (-b - sqrtb2ac) / (2 * a);
+            if (0 < t2 && t2 < 1) {
+                tvalues.push(t2);
+            }
+        }
+
+        var x, y, j = tvalues.length,
+            jlen = j,
+            mt;
+        while (j--) {
+            t = tvalues[j];
+            mt = 1 - t;
+            bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
+            bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
+        }
+
+        bounds[0][jlen] = x0;
+        bounds[1][jlen] = y0;
+        bounds[0][jlen + 1] = x3;
+        bounds[1][jlen + 1] = y3;
+        bounds[0].length = bounds[1].length = jlen + 2;
+
+
+        return {
+          min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])},
+          max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])}
+        };
+    }
+
+    function path2curve(path, path2) {
+        var pth = !path2 && paths(path);
+        if (!path2 && pth.curve) {
+            return pathClone(pth.curve);
+        }
+        var p = pathToAbsolute(path),
+            p2 = path2 && pathToAbsolute(path2),
+            attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            processPath = function (path, d, pcom) {
+                var nx, ny;
+                if (!path) {
+                    return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+                }
+                !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
+                switch (path[0]) {
+                    case "M":
+                        d.X = path[1];
+                        d.Y = path[2];
+                        break;
+                    case "A":
+                        path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
+                        break;
+                    case "S":
+                        if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
+                            nx = d.x * 2 - d.bx;          // And reflect the previous
+                            ny = d.y * 2 - d.by;          // command's control point relative to the current point.
+                        }
+                        else {                            // or some else or nothing
+                            nx = d.x;
+                            ny = d.y;
+                        }
+                        path = ["C", nx, ny].concat(path.slice(1));
+                        break;
+                    case "T":
+                        if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
+                            d.qx = d.x * 2 - d.qx;        // And make a reflection similar
+                            d.qy = d.y * 2 - d.qy;        // to case "S".
+                        }
+                        else {                            // or something else or nothing
+                            d.qx = d.x;
+                            d.qy = d.y;
+                        }
+                        path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+                        break;
+                    case "Q":
+                        d.qx = path[1];
+                        d.qy = path[2];
+                        path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                        break;
+                    case "L":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
+                        break;
+                    case "H":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
+                        break;
+                    case "V":
+                        path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
+                        break;
+                    case "Z":
+                        path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
+                        break;
+                }
+                return path;
+            },
+            fixArc = function (pp, i) {
+                if (pp[i].length > 7) {
+                    pp[i].shift();
+                    var pi = pp[i];
+                    while (pi.length) {
+                        pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved
+                        p2 && (pcoms2[i] = "A"); // the same as above
+                        pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
+                    }
+                    pp.splice(i, 1);
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            fixM = function (path1, path2, a1, a2, i) {
+                if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                    path2.splice(i, 0, ["M", a2.x, a2.y]);
+                    a1.bx = 0;
+                    a1.by = 0;
+                    a1.x = path1[i][1];
+                    a1.y = path1[i][2];
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            pcoms1 = [], // path commands of original path p
+            pcoms2 = [], // path commands of original path p2
+            pfirst = "", // temporary holder for original path command
+            pcom = ""; // holder for previous path command of original path
+        for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+            p[i] && (pfirst = p[i][0]); // save current path command
+
+            if (pfirst != "C") // C is not saved yet, because it may be result of conversion
+            {
+                pcoms1[i] = pfirst; // Save current path command
+                i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom
+            }
+            p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
+
+            if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command
+            // which may produce multiple C:s
+            // so we have to make sure that C is also C in original path
+
+            fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
+
+            if (p2) { // the same procedures is done to p2
+                p2[i] && (pfirst = p2[i][0]);
+                if (pfirst != "C") {
+                    pcoms2[i] = pfirst;
+                    i && (pcom = pcoms2[i - 1]);
+                }
+                p2[i] = processPath(p2[i], attrs2, pcom);
+
+                if (pcoms2[i] != "A" && pfirst == "C") {
+                    pcoms2[i] = "C";
+                }
+
+                fixArc(p2, i);
+            }
+            fixM(p, p2, attrs, attrs2, i);
+            fixM(p2, p, attrs2, attrs, i);
+            var seg = p[i],
+                seg2 = p2 && p2[i],
+                seglen = seg.length,
+                seg2len = p2 && seg2.length;
+            attrs.x = seg[seglen - 2];
+            attrs.y = seg[seglen - 1];
+            attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+            attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+            attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+            attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+            attrs2.x = p2 && seg2[seg2len - 2];
+            attrs2.y = p2 && seg2[seg2len - 1];
+        }
+        if (!p2) {
+            pth.curve = pathClone(p);
+        }
+        return p2 ? [p, p2] : p;
+    }
+    function mapPath(path, matrix) {
+        if (!matrix) {
+            return path;
+        }
+        var x, y, i, j, ii, jj, pathi;
+        path = path2curve(path);
+        for (i = 0, ii = path.length; i < ii; i++) {
+            pathi = path[i];
+            for (j = 1, jj = pathi.length; j < jj; j += 2) {
+                x = matrix.x(pathi[j], pathi[j + 1]);
+                y = matrix.y(pathi[j], pathi[j + 1]);
+                pathi[j] = x;
+                pathi[j + 1] = y;
+            }
+        }
+        return path;
+    }
+
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp, z) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+            var p = [
+                        {x: +crp[i - 2], y: +crp[i - 1]},
+                        {x: +crp[i],     y: +crp[i + 1]},
+                        {x: +crp[i + 2], y: +crp[i + 3]},
+                        {x: +crp[i + 4], y: +crp[i + 5]}
+                    ];
+            if (z) {
+                if (!i) {
+                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+                } else if (iLen - 4 == i) {
+                    p[3] = {x: +crp[0], y: +crp[1]};
+                } else if (iLen - 2 == i) {
+                    p[2] = {x: +crp[0], y: +crp[1]};
+                    p[3] = {x: +crp[2], y: +crp[3]};
+                }
+            } else {
+                if (iLen - 4 == i) {
+                    p[3] = p[2];
+                } else if (!i) {
+                    p[0] = {x: +crp[i], y: +crp[i + 1]};
+                }
+            }
+            d.push(["C",
+                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                  (p[1].y + 6*p[2].y - p[3].y) / 6,
+                  p[2].x,
+                  p[2].y
+            ]);
+        }
+
+        return d;
+    }
+
+    // export
+    Snap.path = paths;
+
+    /*\
+     * Snap.path.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the given path in pixels
+     **
+     - path (string) SVG path string
+     **
+     = (number) length
+    \*/
+    Snap.path.getTotalLength = getTotalLength;
+    /*\
+     * Snap.path.getPointAtLength
+     [ method ]
+     **
+     * Returns the coordinates of the point located at the given length along the given path
+     **
+     - path (string) SVG path string
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    Snap.path.getPointAtLength = getPointAtLength;
+    /*\
+     * Snap.path.getSubpath
+     [ method ]
+     **
+     * Returns the subpath of a given path between given start and end lengths
+     **
+     - path (string) SVG path string
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    Snap.path.getSubpath = function (path, from, to) {
+        if (this.getTotalLength(path) - to < 1e-6) {
+            return getSubpathsAtLength(path, from).end;
+        }
+        var a = getSubpathsAtLength(path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+    /*\
+     * Element.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the path in pixels (only works for `path` elements)
+     = (number) length
+    \*/
+    elproto.getTotalLength = function () {
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+    };
+    // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a <path> is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length?
+    /*\
+     * Element.getPointAtLength
+     [ method ]
+     **
+     * Returns coordinates of the point located at the given length on the given path (only works for `path` elements)
+     **
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    elproto.getPointAtLength = function (length) {
+        return getPointAtLength(this.attr("d"), length);
+    };
+    // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear.
+    /*\
+     * Element.getSubpath
+     [ method ]
+     **
+     * Returns subpath of a given element from given start and end lengths (only works for `path` elements)
+     **
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    elproto.getSubpath = function (from, to) {
+        return Snap.path.getSubpath(this.attr("d"), from, to);
+    };
+    Snap._.box = box;
+    /*\
+     * Snap.path.findDotsAtSegment
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds dot coordinates on the given cubic beziér curve at the given t
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     - t (number) position on the curve (0..1)
+     = (object) point information in format:
+     o {
+     o     x: (number) x coordinate of the point,
+     o     y: (number) y coordinate of the point,
+     o     m: {
+     o         x: (number) x coordinate of the left anchor,
+     o         y: (number) y coordinate of the left anchor
+     o     },
+     o     n: {
+     o         x: (number) x coordinate of the right anchor,
+     o         y: (number) y coordinate of the right anchor
+     o     },
+     o     start: {
+     o         x: (number) x coordinate of the start of the curve,
+     o         y: (number) y coordinate of the start of the curve
+     o     },
+     o     end: {
+     o         x: (number) x coordinate of the end of the curve,
+     o         y: (number) y coordinate of the end of the curve
+     o     },
+     o     alpha: (number) angle of the curve derivative at the point
+     o }
+    \*/
+    Snap.path.findDotsAtSegment = findDotsAtSegment;
+    /*\
+     * Snap.path.bezierBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given cubic beziér curve
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     * or
+     - bez (array) array of six points for beziér curve
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.bezierBBox = bezierBBox;
+    /*\
+     * Snap.path.isPointInsideBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside bounding box
+     - bbox (string) bounding box
+     - x (string) x coordinate of the point
+     - y (string) y coordinate of the point
+     = (boolean) `true` if point is inside
+    \*/
+    Snap.path.isPointInsideBBox = isPointInsideBBox;
+    Snap.closest = function (x, y, X, Y) {
+        var r = 100,
+            b = box(x - r / 2, y - r / 2, r, r),
+            inside = [],
+            getter = X[0].hasOwnProperty("x") ? function (i) {
+                return {
+                    x: X[i].x,
+                    y: X[i].y
+                };
+            } : function (i) {
+                return {
+                    x: X[i],
+                    y: Y[i]
+                };
+            },
+            found = 0;
+        while (r <= 1e6 && !found) {
+            for (var i = 0, ii = X.length; i < ii; i++) {
+                var xy = getter(i);
+                if (isPointInsideBBox(b, xy.x, xy.y)) {
+                    found++;
+                    inside.push(xy);
+                    break;
+                }
+            }
+            if (!found) {
+                r *= 2;
+                b = box(x - r / 2, y - r / 2, r, r)
+            }
+        }
+        if (r == 1e6) {
+            return;
+        }
+        var len = Infinity,
+            res;
+        for (i = 0, ii = inside.length; i < ii; i++) {
+            var l = Snap.len(x, y, inside[i].x, inside[i].y);
+            if (len > l) {
+                len = l;
+                inside[i].len = l;
+                res = inside[i];
+            }
+        }
+        return res;
+    };
+    /*\
+     * Snap.path.isBBoxIntersect
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if two bounding boxes intersect
+     - bbox1 (string) first bounding box
+     - bbox2 (string) second bounding box
+     = (boolean) `true` if bounding boxes intersect
+    \*/
+    Snap.path.isBBoxIntersect = isBBoxIntersect;
+    /*\
+     * Snap.path.intersection
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds intersections of two paths
+     - path1 (string) path string
+     - path2 (string) path string
+     = (array) dots of intersection
+     o [
+     o     {
+     o         x: (number) x coordinate of the point,
+     o         y: (number) y coordinate of the point,
+     o         t1: (number) t value for segment of path1,
+     o         t2: (number) t value for segment of path2,
+     o         segment1: (number) order number for segment of path1,
+     o         segment2: (number) order number for segment of path2,
+     o         bez1: (array) eight coordinates representing beziér curve for the segment of path1,
+     o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
+     o     }
+     o ]
+    \*/
+    Snap.path.intersection = pathIntersection;
+    Snap.path.intersectionNumber = pathIntersectionNumber;
+    /*\
+     * Snap.path.isPointInside
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside a given closed path.
+     *
+     * Note: fill mode doesn’t affect the result of this method.
+     - path (string) path string
+     - x (number) x of the point
+     - y (number) y of the point
+     = (boolean) `true` if point is inside the path
+    \*/
+    Snap.path.isPointInside = isPointInsidePath;
+    /*\
+     * Snap.path.getBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given path
+     - path (string) path string
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.getBBox = pathBBox;
+    Snap.path.get = getPath;
+    /*\
+     * Snap.path.toRelative
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into relative values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toRelative = pathToRelative;
+    /*\
+     * Snap.path.toAbsolute
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into absolute values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toAbsolute = pathToAbsolute;
+    /*\
+     * Snap.path.toCubic
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path to a new path where all segments are cubic beziér curves
+     - pathString (string|array) path string or array of segments
+     = (array) array of segments
+    \*/
+    Snap.path.toCubic = path2curve;
+    /*\
+     * Snap.path.map
+     [ method ]
+     **
+     * Transform the path string with the given matrix
+     - path (string) path string
+     - matrix (object) see @Matrix
+     = (string) transformed path string
+    \*/
+    Snap.path.map = mapPath;
+    Snap.path.toString = toString;
+    Snap.path.clone = pathClone;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var mmax = Math.max,
+        mmin = Math.min;
+
+    // Set
+    var Set = function (items) {
+        this.items = [];
+	this.bindings = {};
+        this.length = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items.length; i < ii; i++) {
+                if (items[i]) {
+                    this[this.items.length] = this.items[this.items.length] = items[i];
+                    this.length++;
+                }
+            }
+        }
+    },
+    setproto = Set.prototype;
+    /*\
+     * Set.push
+     [ method ]
+     **
+     * Adds each argument to the current set
+     = (object) original element
+    \*/
+    setproto.push = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments.length; i < ii; i++) {
+            item = arguments[i];
+            if (item) {
+                len = this.items.length;
+                this[len] = this.items[len] = item;
+                this.length++;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.pop
+     [ method ]
+     **
+     * Removes last element and returns it
+     = (object) element
+    \*/
+    setproto.pop = function () {
+        this.length && delete this[this.length--];
+        return this.items.pop();
+    };
+    /*\
+     * Set.forEach
+     [ method ]
+     **
+     * Executes given function for each element in the set
+     *
+     * If the function returns `false`, the loop stops running.
+     **
+     - callback (function) function to run
+     - thisArg (object) context object for the callback
+     = (object) Set object
+    \*/
+    setproto.forEach = function (callback, thisArg) {
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
+                return this;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.animate
+     [ method ]
+     **
+     * Animates each element in set in sync.
+     *
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     * or
+     - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]`
+     > Usage
+     | // animate all elements in set to radius 10
+     | set.animate({r: 10}, 500, mina.easein);
+     | // or
+     | // animate first element to radius 10, but second to radius 20 and in different time
+     | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]);
+     = (Element) the current element
+    \*/
+    setproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Snap._.Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = easing.dur;
+            attrs = attrs.attr;
+        }
+        var args = arguments;
+        if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) {
+            var each = true;
+        }
+        var begin,
+            handler = function () {
+                if (begin) {
+                    this.b = begin;
+                } else {
+                    begin = this.b;
+                }
+            },
+            cb = 0,
+            set = this,
+            callbacker = callback && function () {
+                if (++cb == set.length) {
+                    callback.call(this);
+                }
+            };
+        return this.forEach(function (el, i) {
+            eve.once("snap.animcreated." + el.id, handler);
+            if (each) {
+                args[i] && el.animate.apply(el, args[i]);
+            } else {
+                el.animate(attrs, ms, easing, callbacker);
+            }
+        });
+    };
+    setproto.remove = function () {
+        while (this.length) {
+            this.pop().remove();
+        }
+        return this;
+    };
+    /*\
+     * Set.bind
+     [ method ]
+     **
+     * Specifies how to handle a specific attribute when applied
+     * to a set.
+     *
+     **
+     - attr (string) attribute name
+     - callback (function) function to run
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     - eattr (string) attribute on the element to bind the attribute to
+     = (object) Set object
+    \*/
+    setproto.bind = function (attr, a, b) {
+        var data = {};
+        if (typeof a == "function") {
+            this.bindings[attr] = a;
+        } else {
+            var aname = b || attr;
+            this.bindings[attr] = function (v) {
+                data[aname] = v;
+                a.attr(data);
+            };
+        }
+        return this;
+    };
+    setproto.attr = function (value) {
+        var unbound = {};
+        for (var k in value) {
+            if (this.bindings[k]) {
+                this.bindings[k](value[k]);
+            } else {
+                unbound[k] = value[k];
+            }
+        }
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            this.items[i].attr(unbound);
+        }
+        return this;
+    };
+    /*\
+     * Set.clear
+     [ method ]
+     **
+     * Removes all elements from the set
+    \*/
+    setproto.clear = function () {
+        while (this.length) {
+            this.pop();
+        }
+    };
+    /*\
+     * Set.splice
+     [ method ]
+     **
+     * Removes range of elements from the set
+     **
+     - index (number) position of the deletion
+     - count (number) number of element to remove
+     - insertion… (object) #optional elements to insert
+     = (object) set elements that were deleted
+    \*/
+    setproto.splice = function (index, count, insertion) {
+        index = index < 0 ? mmax(this.length + index, 0) : index;
+        count = mmax(0, mmin(this.length - index, count));
+        var tail = [],
+            todel = [],
+            args = [],
+            i;
+        for (i = 2; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        }
+        for (i = 0; i < count; i++) {
+            todel.push(this[index + i]);
+        }
+        for (; i < this.length - index; i++) {
+            tail.push(this[index + i]);
+        }
+        var arglen = args.length;
+        for (i = 0; i < arglen + tail.length; i++) {
+            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+        }
+        i = this.items.length = this.length -= count - arglen;
+        while (this[i]) {
+            delete this[i++];
+        }
+        return new Set(todel);
+    };
+    /*\
+     * Set.exclude
+     [ method ]
+     **
+     * Removes given element from the set
+     **
+     - element (object) element to remove
+     = (boolean) `true` if object was found and removed from the set
+    \*/
+    setproto.exclude = function (el) {
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
+            return true;
+        }
+        return false;
+    };
+    setproto.insertAfter = function (el) {
+        var i = this.items.length;
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    setproto.getBBox = function () {
+        var x = [],
+            y = [],
+            x2 = [],
+            y2 = [];
+        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+            var box = this.items[i].getBBox();
+            x.push(box.x);
+            y.push(box.y);
+            x2.push(box.x + box.width);
+            y2.push(box.y + box.height);
+        }
+        x = mmin.apply(0, x);
+        y = mmin.apply(0, y);
+        x2 = mmax.apply(0, x2);
+        y2 = mmax.apply(0, y2);
+        return {
+            x: x,
+            y: y,
+            x2: x2,
+            y2: y2,
+            width: x2 - x,
+            height: y2 - y,
+            cx: x + (x2 - x) / 2,
+            cy: y + (y2 - y) / 2
+        };
+    };
+    setproto.clone = function (s) {
+        s = new Set;
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            s.push(this.items[i].clone());
+        }
+        return s;
+    };
+    setproto.toString = function () {
+        return "Snap\u2018s set";
+    };
+    setproto.type = "set";
+    // export
+    Snap.Set = Set;
+    Snap.set = function () {
+        var set = new Set;
+        if (arguments.length) {
+            set.push.apply(set, Array.prototype.slice.call(arguments, 0));
+        }
+        return set;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var names = {},
+        reUnit = /[a-z]+$/i,
+        Str = String;
+    names.stroke = names.fill = "colour";
+    function getEmpty(item) {
+        var l = item[0];
+        switch (l.toLowerCase()) {
+            case "t": return [l, 0, 0];
+            case "m": return [l, 1, 0, 0, 1, 0, 0];
+            case "r": if (item.length == 4) {
+                return [l, 0, item[2], item[3]];
+            } else {
+                return [l, 0];
+            }
+            case "s": if (item.length == 5) {
+                return [l, 1, 1, item[3], item[4]];
+            } else if (item.length == 3) {
+                return [l, 1, 1];
+            } else {
+                return [l, 1];
+            }
+        }
+    }
+    function equaliseTransform(t1, t2, getBBox) {
+        t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+        t1 = Snap.parseTransformString(t1) || [];
+        t2 = Snap.parseTransformString(t2) || [];
+        var maxlength = Math.max(t1.length, t2.length),
+            from = [],
+            to = [],
+            i = 0, j, jj,
+            tt1, tt2;
+        for (; i < maxlength; i++) {
+            tt1 = t1[i] || getEmpty(t2[i]);
+            tt2 = t2[i] || getEmpty(tt1);
+            if ((tt1[0] != tt2[0]) ||
+                (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                ) {
+                    t1 = Snap._.transform2matrix(t1, getBBox());
+                    t2 = Snap._.transform2matrix(t2, getBBox());
+                    from = [["m", t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]];
+                    to = [["m", t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]];
+                    break;
+            }
+            from[i] = [];
+            to[i] = [];
+            for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) {
+                j in tt1 && (from[i][j] = tt1[j]);
+                j in tt2 && (to[i][j] = tt2[j]);
+            }
+        }
+        return {
+            from: path2array(from),
+            to: path2array(to),
+            f: getPath(from)
+        };
+    }
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    function getViewBox(val) {
+        return val.join(" ");
+    }
+    function getColour(clr) {
+        return Snap.rgb(clr[0], clr[1], clr[2]);
+    }
+    function getPath(path) {
+        var k = 0, i, ii, j, jj, out, a, b = [];
+        for (i = 0, ii = path.length; i < ii; i++) {
+            out = "[";
+            a = ['"' + path[i][0] + '"'];
+            for (j = 1, jj = path[i].length; j < jj; j++) {
+                a[j] = "val[" + (k++) + "]";
+            }
+            out += a + "]";
+            b[i] = out;
+        }
+        return Function("val", "return Snap.path.toString.call([" + b + "])");
+    }
+    function path2array(path) {
+        var out = [];
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            for (var j = 1, jj = path[i].length; j < jj; j++) {
+                out.push(path[i][j]);
+            }
+        }
+        return out;
+    }
+    function isNumeric(obj) {
+        return isFinite(parseFloat(obj));
+    }
+    function arrayEqual(arr1, arr2) {
+        if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) {
+            return false;
+        }
+        return arr1.toString() == arr2.toString();
+    }
+    Element.prototype.equal = function (name, b) {
+        return eve("snap.util.equal", this, name, b).firstDefined();
+    };
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this;
+        if (isNumeric(a) && isNumeric(b)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getNumber
+            };
+        }
+        if (names[name] == "colour") {
+            A = Snap.color(a);
+            B = Snap.color(b);
+            return {
+                from: [A.r, A.g, A.b, A.opacity],
+                to: [B.r, B.g, B.b, B.opacity],
+                f: getColour
+            };
+        }
+        if (name == "viewBox") {
+            A = this.attr(name).vb.split(" ").map(Number);
+            B = b.split(" ").map(Number);
+            return {
+                from: A,
+                to: B,
+                f: getViewBox
+            };
+        }
+        if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
+            if (b instanceof Snap.Matrix) {
+                b = b.toTransformString();
+            }
+            if (!Snap._.rgTransform.test(b)) {
+                b = Snap._.svgTransform2string(b);
+            }
+            return equaliseTransform(a, b, function () {
+                return el.getBBox(1);
+            });
+        }
+        if (name == "d" || name == "path") {
+            A = Snap.path.toCubic(a, b);
+            return {
+                from: path2array(A[0]),
+                to: path2array(A[1]),
+                f: getPath(A[0])
+            };
+        }
+        if (name == "points") {
+            A = Str(a).split(Snap._.separator);
+            B = Str(b).split(Snap._.separator);
+            return {
+                from: A,
+                to: B,
+                f: function (val) { return val; }
+            };
+        }
+        var aUnit = a.match(reUnit),
+            bUnit = Str(b).match(reUnit);
+        if (aUnit && arrayEqual(aUnit, bUnit)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getUnit(aUnit)
+            };
+        } else {
+            return {
+                from: this.asPX(name),
+                to: this.asPX(name, b),
+                f: getNumber
+            };
+        }
+    });
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+    has = "hasOwnProperty",
+    supportsTouch = "createTouch" in glob.doc,
+    events = [
+        "click", "dblclick", "mousedown", "mousemove", "mouseout",
+        "mouseover", "mouseup", "touchstart", "touchmove", "touchend",
+        "touchcancel"
+    ],
+    touchMap = {
+        mousedown: "touchstart",
+        mousemove: "touchmove",
+        mouseup: "touchend"
+    },
+    getScroll = function (xy, el) {
+        var name = xy == "y" ? "scrollTop" : "scrollLeft",
+            doc = el && el.node ? el.node.ownerDocument : glob.doc;
+        return doc[name in doc.documentElement ? "documentElement" : "body"][name];
+    },
+    preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = function (obj, type, fn, element) {
+        var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+            f = function (e) {
+                var scrollY = getScroll("y", element),
+                    scrollX = getScroll("x", element);
+                if (supportsTouch && touchMap[has](type)) {
+                    for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                        if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) {
+                            var olde = e;
+                            e = e.targetTouches[i];
+                            e.originalEvent = olde;
+                            e.preventDefault = preventTouch;
+                            e.stopPropagation = stopTouch;
+                            break;
+                        }
+                    }
+                }
+                var x = e.clientX + scrollX,
+                    y = e.clientY + scrollY;
+                return fn.call(element, e, x, y);
+            };
+
+        if (type !== realName) {
+            obj.addEventListener(type, f, false);
+        }
+
+        obj.addEventListener(realName, f, false);
+
+        return function () {
+            if (type !== realName) {
+                obj.removeEventListener(type, f, false);
+            }
+
+            obj.removeEventListener(realName, f, false);
+            return true;
+        };
+    },
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = getScroll("y"),
+            scrollX = getScroll("x"),
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches && e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            var node = dragi.el.node,
+                o,
+                next = node.nextSibling,
+                parent = node.parentNode,
+                display = node.style.display;
+            // glob.win.opera && parent.removeChild(node);
+            // node.style.display = "none";
+            // o = dragi.el.paper.getElementByPoint(x, y);
+            // node.style.display = display;
+            // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+            // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o);
+            x += scrollX;
+            y += scrollY;
+            eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        Snap.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+            eve.off("snap.drag.*." + dragi.el.id);
+        }
+        drag = [];
+    };
+    /*\
+     * Element.click
+     [ method ]
+     **
+     * Adds a click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unclick
+     [ method ]
+     **
+     * Removes a click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.dblclick
+     [ method ]
+     **
+     * Adds a double click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.undblclick
+     [ method ]
+     **
+     * Removes a double click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousedown
+     [ method ]
+     **
+     * Adds a mousedown event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousedown
+     [ method ]
+     **
+     * Removes a mousedown event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousemove
+     [ method ]
+     **
+     * Adds a mousemove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousemove
+     [ method ]
+     **
+     * Removes a mousemove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseout
+     [ method ]
+     **
+     * Adds a mouseout event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseout
+     [ method ]
+     **
+     * Removes a mouseout event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseover
+     [ method ]
+     **
+     * Adds a mouseover event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseover
+     [ method ]
+     **
+     * Removes a mouseover event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseup
+     [ method ]
+     **
+     * Adds a mouseup event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseup
+     [ method ]
+     **
+     * Removes a mouseup event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchstart
+     [ method ]
+     **
+     * Adds a touchstart event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchstart
+     [ method ]
+     **
+     * Removes a touchstart event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchmove
+     [ method ]
+     **
+     * Adds a touchmove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchmove
+     [ method ]
+     **
+     * Removes a touchmove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchend
+     [ method ]
+     **
+     * Adds a touchend event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchend
+     [ method ]
+     **
+     * Removes a touchend event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchcancel
+     [ method ]
+     **
+     * Adds a touchcancel event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchcancel
+     [ method ]
+     **
+     * Removes a touchcancel event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    for (var i = events.length; i--;) {
+        (function (eventName) {
+            Snap[eventName] = elproto[eventName] = function (fn, scope) {
+                if (Snap.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({
+                        name: eventName,
+                        f: fn,
+                        unbind: addEvent(this.node || document, eventName, fn, scope || this)
+                    });
+                } else {
+                    for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) {
+                        try {
+                            this.events[i].f.call(this);
+                        } catch (e) {}
+                    }
+                }
+                return this;
+            };
+            Snap["un" + eventName] =
+            elproto["un" + eventName] = function (fn) {
+                var events = this.events || [],
+                    l = events.length;
+                while (l--) if (events[l].name == eventName &&
+                               (events[l].f == fn || !fn)) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    /*\
+     * Element.hover
+     [ method ]
+     **
+     * Adds hover event handlers to the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     - icontext (object) #optional context for hover in handler
+     - ocontext (object) #optional context for hover out handler
+     = (object) @Element
+    \*/
+    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+    };
+    /*\
+     * Element.unhover
+     [ method ]
+     **
+     * Removes hover event handlers from the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     = (object) @Element
+    \*/
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    var draggable = [];
+    // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture.
+    // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from?
+    // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason.
+    // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start.<id> on start, drag.end.<id> on end and drag.move.<id> on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID?
+    /*\
+     * Element.drag
+     [ method ]
+     **
+     * Adds event handlers for an element's drag gesture
+     **
+     - onmove (function) handler for moving
+     - onstart (function) handler for drag start
+     - onend (function) handler for drag end
+     - mcontext (object) #optional context for moving handler
+     - scontext (object) #optional context for drag start handler
+     - econtext (object) #optional context for drag end handler
+     * Additionaly following `drag` events are triggered: `drag.start.<id>` on start,
+     * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element is dragged over another element
+     * `drag.over.<id>` fires as well.
+     *
+     * Start event and start handler are called in specified context or in context of the element with following parameters:
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * Move event and move handler are called in specified context or in context of the element with following parameters:
+     o dx (number) shift by x from the start point
+     o dy (number) shift by y from the start point
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * End event and end handler are called in specified context or in context of the element with following parameters:
+     o event (object) DOM event object
+     = (object) @Element
+    \*/
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        var el = this;
+        if (!arguments.length) {
+            var origTransform;
+            return el.drag(function (dx, dy) {
+                this.attr({
+                    transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
+                });
+            }, function () {
+                origTransform = this.transform().local;
+            });
+        }
+        function start(e, x, y) {
+            (e.originalEvent || e).preventDefault();
+            el._drag.x = x;
+            el._drag.y = y;
+            el._drag.id = e.identifier;
+            !drag.length && Snap.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+            onstart && eve.on("snap.drag.start." + el.id, onstart);
+            onmove && eve.on("snap.drag.move." + el.id, onmove);
+            onend && eve.on("snap.drag.end." + el.id, onend);
+            eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e);
+        }
+        function init(e, x, y) {
+            eve("snap.draginit." + el.id, el, e, x, y);
+        }
+        eve.on("snap.draginit." + el.id, start);
+        el._drag = {};
+        draggable.push({el: el, start: start, init: init});
+        el.mousedown(init);
+        return el;
+    };
+    /*
+     * Element.onDragOver
+     [ method ]
+     **
+     * Shortcut to assign event handler for `drag.over.<id>` event, where `id` is the element's `id` (see @Element.id)
+     - f (function) handler for event, first argument would be the element you are dragging over
+    \*/
+    // elproto.onDragOver = function (f) {
+    //     f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id);
+    // };
+    /*\
+     * Element.undrag
+     [ method ]
+     **
+     * Removes all drag event handlers from the given element
+    \*/
+    elproto.undrag = function () {
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].init);
+            draggable.splice(i, 1);
+            eve.unbind("snap.drag.*." + this.id);
+            eve.unbind("snap.draginit." + this.id);
+        }
+        !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp);
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        pproto = Paper.prototype,
+        rgurl = /^\s*url\((.+)\)/,
+        Str = String,
+        $ = Snap._.$;
+    Snap.filter = {};
+    /*\
+     * Paper.filter
+     [ method ]
+     **
+     * Creates a `<filter>` element
+     **
+     - filstr (string) SVG fragment of filter provided as a string
+     = (object) @Element
+     * Note: It is recommended to use filters embedded into the page inside an empty SVG element.
+     > Usage
+     | var f = paper.filter('<feGaussianBlur stdDeviation="2"/>'),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    pproto.filter = function (filstr) {
+        var paper = this;
+        if (paper.type != "svg") {
+            paper = paper.paper;
+        }
+        var f = Snap.parse(Str(filstr)),
+            id = Snap._.id(),
+            width = paper.node.offsetWidth,
+            height = paper.node.offsetHeight,
+            filter = $("filter");
+        $(filter, {
+            id: id,
+            filterUnits: "userSpaceOnUse"
+        });
+        filter.appendChild(f.node);
+        paper.defs.appendChild(filter);
+        return new Element(filter);
+    };
+
+    eve.on("snap.util.getattr.filter", function () {
+        eve.stop();
+        var p = $(this.node, "filter");
+        if (p) {
+            var match = Str(p).match(rgurl);
+            return match && Snap.select(match[1]);
+        }
+    });
+    eve.on("snap.util.attr.filter", function (value) {
+        if (value instanceof Element && value.type == "filter") {
+            eve.stop();
+            var id = value.node.id;
+            if (!id) {
+                $(value.node, {id: value.id});
+                id = value.id;
+            }
+            $(this.node, {
+                filter: Snap.url(id)
+            });
+        }
+        if (!value || value == "none") {
+            eve.stop();
+            this.node.removeAttribute("filter");
+        }
+    });
+    /*\
+     * Snap.filter.blur
+     [ method ]
+     **
+     * Returns an SVG markup string for the blur filter
+     **
+     - x (number) amount of horizontal blur, in pixels
+     - y (number) #optional amount of vertical blur, in pixels
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.blur(5, 10)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.blur = function (x, y) {
+        if (x == null) {
+            x = 2;
+        }
+        var def = y == null ? x : [x, y];
+        return Snap.format('\<feGaussianBlur stdDeviation="{def}"/>', {
+            def: def
+        });
+    };
+    Snap.filter.blur.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.shadow
+     [ method ]
+     **
+     * Returns an SVG markup string for the shadow filter
+     **
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - blur (number) #optional amount of blur
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * which makes blur default to `4`. Or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - opacity (number) #optional `0..1` opacity of the shadow
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.shadow(0, 2, 3)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.shadow = function (dx, dy, blur, color, opacity) {
+        if (typeof blur == "string") {
+            color = blur;
+            opacity = color;
+            blur = 4;
+        }
+        if (typeof color != "string") {
+            opacity = color;
+            color = "#000";
+        }
+        color = color || "#000";
+        if (blur == null) {
+            blur = 4;
+        }
+        if (opacity == null) {
+            opacity = 1;
+        }
+        if (dx == null) {
+            dx = 0;
+            dy = 2;
+        }
+        if (dy == null) {
+            dy = dx;
+        }
+        color = Snap.color(color);
+        return Snap.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', {
+            color: color,
+            dx: dx,
+            dy: dy,
+            blur: blur,
+            opacity: opacity
+        });
+    };
+    Snap.filter.shadow.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.grayscale
+     [ method ]
+     **
+     * Returns an SVG markup string for the grayscale filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.grayscale = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>', {
+            a: 0.2126 + 0.7874 * (1 - amount),
+            b: 0.7152 - 0.7152 * (1 - amount),
+            c: 0.0722 - 0.0722 * (1 - amount),
+            d: 0.2126 - 0.2126 * (1 - amount),
+            e: 0.7152 + 0.2848 * (1 - amount),
+            f: 0.0722 - 0.0722 * (1 - amount),
+            g: 0.2126 - 0.2126 * (1 - amount),
+            h: 0.0722 + 0.9278 * (1 - amount)
+        });
+    };
+    Snap.filter.grayscale.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.sepia
+     [ method ]
+     **
+     * Returns an SVG markup string for the sepia filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.sepia = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>', {
+            a: 0.393 + 0.607 * (1 - amount),
+            b: 0.769 - 0.769 * (1 - amount),
+            c: 0.189 - 0.189 * (1 - amount),
+            d: 0.349 - 0.349 * (1 - amount),
+            e: 0.686 + 0.314 * (1 - amount),
+            f: 0.168 - 0.168 * (1 - amount),
+            g: 0.272 - 0.272 * (1 - amount),
+            h: 0.534 - 0.534 * (1 - amount),
+            i: 0.131 + 0.869 * (1 - amount)
+        });
+    };
+    Snap.filter.sepia.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.saturate
+     [ method ]
+     **
+     * Returns an SVG markup string for the saturate filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.saturate = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="saturate" values="{amount}"/>', {
+            amount: 1 - amount
+        });
+    };
+    Snap.filter.saturate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.hueRotate
+     [ method ]
+     **
+     * Returns an SVG markup string for the hue-rotate filter
+     **
+     - angle (number) angle of rotation
+     = (string) filter representation
+    \*/
+    Snap.filter.hueRotate = function (angle) {
+        angle = angle || 0;
+        return Snap.format('<feColorMatrix type="hueRotate" values="{angle}"/>', {
+            angle: angle
+        });
+    };
+    Snap.filter.hueRotate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.invert
+     [ method ]
+     **
+     * Returns an SVG markup string for the invert filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.invert = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+//        <feColorMatrix type="matrix" values="-1 0 0 0 1  0 -1 0 0 1  0 0 -1 0 1  0 0 0 1 0" color-interpolation-filters="sRGB"/>
+        return Snap.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: 1 - amount
+        });
+    };
+    Snap.filter.invert.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.brightness
+     [ method ]
+     **
+     * Returns an SVG markup string for the brightness filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.brightness = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>', {
+            amount: amount
+        });
+    };
+    Snap.filter.brightness.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.contrast
+     [ method ]
+     **
+     * Returns an SVG markup string for the contrast filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.contrast = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: .5 - amount / 2
+        });
+    };
+    Snap.filter.contrast.toString = function () {
+        return this();
+    };
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var box = Snap._.box,
+        is = Snap.is,
+        firstLetter = /^[^a-z]*([tbmlrc])/i,
+        toString = function () {
+            return "T" + this.dx + "," + this.dy;
+        };
+    /*\
+     * Element.getAlign
+     [ method ]
+     **
+     * Returns shift needed to align the element relatively to given element.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string
+     > Usage
+     | el.transform(el.getAlign(el2, "top"));
+     * or
+     | var dy = el.getAlign(el2, "top").dy;
+    \*/
+    Element.prototype.getAlign = function (el, way) {
+        if (way == null && is(el, "string")) {
+            way = el;
+            el = null;
+        }
+        el = el || this.paper;
+        var bx = el.getBBox ? el.getBBox() : box(el),
+            bb = this.getBBox(),
+            out = {};
+        way = way && way.match(firstLetter);
+        way = way ? way[1].toLowerCase() : "c";
+        switch (way) {
+            case "t":
+                out.dx = 0;
+                out.dy = bx.y - bb.y;
+            break;
+            case "b":
+                out.dx = 0;
+                out.dy = bx.y2 - bb.y2;
+            break;
+            case "m":
+                out.dx = 0;
+                out.dy = bx.cy - bb.cy;
+            break;
+            case "l":
+                out.dx = bx.x - bb.x;
+                out.dy = 0;
+            break;
+            case "r":
+                out.dx = bx.x2 - bb.x2;
+                out.dy = 0;
+            break;
+            default:
+                out.dx = bx.cx - bb.cx;
+                out.dy = 0;
+            break;
+        }
+        out.toString = toString;
+        return out;
+    };
+    /*\
+     * Element.align
+     [ method ]
+     **
+     * Aligns the element relatively to given one via transformation.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object) this element
+     > Usage
+     | el.align(el2, "top");
+     * or
+     | el.align("middle");
+    \*/
+    Element.prototype.align = function (el, way) {
+        return this.transform("..." + this.getAlign(el, way));
+    };
+});
+
+return Snap;
+}));
diff --git a/web/pgadmin/misc/templates/explain/js/explain.js b/web/pgadmin/misc/templates/explain/js/explain.js
new file mode 100644
index 0000000..911e107
--- /dev/null
+++ b/web/pgadmin/misc/templates/explain/js/explain.js
@@ -0,0 +1,688 @@
+define (
+  'pgadmin.misc.explain',
+  ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'backbone', 'snap.svg'],
+  function($, _, S, pgAdmin, Backbone, Snap) {
+
+pgAdmin = pgAdmin || window.pgAdmin || {};
+var pgExplain = pgAdmin.Explain;
+
+// Snap.svg plug-in to write multitext as image name
+Snap.plugin(function (Snap, Element, Paper, glob) {
+  Paper.prototype.multitext = function (x, y, txt, max_width, attributes) {
+    var svg = Snap(),
+        abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+        isWordBroken = false,
+        temp = svg.text(0, 0, abc);
+
+    temp.attr(attributes);
+
+    /*
+     * Find letter width in pixels and
+     * index from where the text should be broken
+     */
+    var letter_width = temp.getBBox().width / abc.length,
+        word_break_index = Math.round((max_width / letter_width)) - 1;
+
+    svg.remove();
+
+    var words = txt.split(" "),
+        width_so_far = 0,
+        lines=[], curr_line = '',
+        /*
+         * Function to divide string into multiple lines
+         * and store them in an array if it size crosses
+         * the max-width boundary.
+         */
+        splitTextInMultiLine = function(leading, so_far, line) {
+          var l = line.length,
+              res = [];
+
+          if (l == 0)
+            return res;
+
+          if (so_far && (so_far + (l * letter_width) > max_width)) {
+            res.push(leading);
+            res = res.concat(splitTextInMultiLine('', 0, line));
+          } else if (so_far) {
+            res.push(leading + ' ' + line);
+          } else {
+            if (leading)
+                res.push(leading);
+            if (line.length > word_break_index + 1)
+                res.push(line.slice(0, word_break_index) + '-');
+            else
+                res.push(line);
+            res = res.concat(splitTextInMultiLine('', 0, line.slice(word_break_index)));
+          }
+
+          return res;
+        };
+
+    for (var i = 0; i < words.length; i++) {
+      var tmpArr = splitTextInMultiLine(
+            curr_line, width_so_far, words[i]
+          );
+
+      if (curr_line) {
+        lines = lines.slice(0, lines.length - 2);
+      }
+      lines = lines.concat(tmpArr);
+      curr_line = lines[lines.length - 1];
+      width_so_far = (curr_line.length * letter_width);
+    }
+
+    // Create multiple tspan for each string in array
+    var t = this.text(x,y,lines).attr(attributes);
+    t.selectAll("tspan:nth-child(n+2)").attr({
+      dy: "1.2em",
+      x: x
+    });
+    return t;
+  };
+});
+
+if (pgAdmin.Explain)
+    return pgAdmin.Explain;
+
+var pgExplain = pgAdmin.Explain = {
+   // Prefix path where images are stored
+   prefix: '{{ url_for('misc.static', filename='explain/img') }}/'
+};
+
+/*
+ * A map which is used to fetch the image to be drawn and
+ * text which will appear below it
+ */
+var imageMapper = {
+    "Aggregate" : {
+        "image":"ex_aggregate.png", "image_text":"Aggregate"
+    },
+    'Append' : {
+        "image":"ex_append.png","image_text":"Append"
+    },
+    "Bitmap Index Scan" : function(data) {
+        return {
+            "image":"ex_bmp_index.png", "image_text":data['Index Name']
+        };
+    },
+    "Bitmap Heap Scan" : function(data) {
+  return {"image":"ex_bmp_heap.png","image_text":data['Relation Name']};
+},
+"BitmapAnd" : {"image":"ex_bmp_and.png","image_text":"Bitmap AND"},
+"BitmapOr" : {"image":"ex_bmp_or.png","image_text":"Bitmap OR"},
+"CTE Scan" : {"image":"ex_cte_scan.png","image_text":"CTE Scan"},
+"Function Scan" : {"image":"ex_result.png","image_text":"Function Scan"},
+"Foreign Scan" : {"image":"ex_foreign_scan.png","image_text":"Foreign Scan"},
+"Gather" : {"image":"ex_gather_motion.png","image_text":"Gather"},
+"Group" : {"image":"ex_group.png","image_text":"Group"},
+"GroupAggregate": {"image":"ex_aggregate.png","image_text":"Group Aggregate"},
+"Hash" : {"image":"ex_hash.png","image_text":"Hash"},
+"Hash Join": function(data) {
+  if (!data['Join Type']) return {"image":"ex_join.png","image_text":"Join"};
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_hash_anti_join.png","image_text":"Hash Anti Join"};
+    case 'Semi': return {"image":"ex_hash_semi_join.png","image_text":"Hash Semi Join"};
+    default: return {"image":"ex_hash.png","image_text":String("Hash " + data['Join Type'] + " Join" )};
+  }
+},
+"HashAggregate" : {"image":"ex_aggregate.png","image_text":"Hash Aggregate"},
+"Index Only Scan" : function(data) {
+  return {"image":"ex_index_only_scan.png","image_text":data['Index Name']};
+},
+"Index Scan" : function(data) {
+  return {"image":"ex_index_scan.png","image_text":data['Index Name']};
+},
+"Index Scan Backword" : {"image":"ex_index_scan.png","image_text":"Index Backward Scan"},
+"Limit" : {"image":"ex_limit.png","image_text":"Limit"},
+"LockRows" : {"image":"ex_lock_rows.png","image_text":"Lock Rows"},
+"Materialize" : {"image":"ex_materialize.png","image_text":"Materialize"},
+"Merge Append": {"image":"ex_merge_append.png","image_text":"Merge Append"},
+"Merge Join": function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_merge_anti_join.png","image_text":"Merge Anti Join"};
+    case 'Semi': return {"image":"ex_merge_semi_join.png","image_text":"Merge Semi Join"};
+    default: return {"image":"ex_merge.png","image_text":String("Merge " + data['Join Type'] + " Join" )};
+  }
+},
+"ModifyTable" : function(data) {
+  switch (data['Operaton']) {
+    case "insert": return { "image":"ex_insert.png",
+                            "image_text":"Insert"
+                           };
+    case "update": return {"image":"ex_update.png","image_text":"Update"};
+    case "Delete": return {"image":"ex_delete.png","image_text":"Delete"};
+  }
+},
+'Nested Loop' : function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_nested_loop_anti_join.png","image_text":"Nested Loop Anti Join"};
+    case 'Semi': return {"image":"ex_nested_loop_semi_join.png","image_text":"Nested Loop Semi Join"};
+    default: return {"image":"ex_nested.png","image_text":"Nested Loop " + data['Join Type'] + " Join"};
+  }
+},
+"Recursive Union" : {"image":"ex_recursive_union.png","image_text":"Recursive Union"},
+"Result" : {"image":"ex_result.png","image_text":"Result"},
+"Sample Scan" : {"image":"ex_scan.png","image_text":"Sample Scan"},
+"Scan" : {"image":"ex_scan.png","image_text":"Scan"},
+"Seek" : {"image":"ex_seek.png","image_text":"Seek"},
+"SetOp" : function(data) {
+  var strategy = data['Strategy'],
+      command = data['Command'];
+
+  if(strategy == "Hashed") {
+    if(command.startsWith("Intersect")) {
+      if(command == "Intersect All")
+        return {"image":"ex_hash_setop_intersect_all.png","image_text":"Hashed Intersect All"};
+      return {"image":"ex_hash_setop_intersect.png","image_text":"Hashed Intersect"};
+    }
+    else if (command.startsWith("Except")) {
+      if(command == "Except All")
+        return {"image":"ex_hash_setop_except_all.png","image_text":"Hashed Except All"};
+      return {"image":"ex_hash_setop_except.png","image_text":"Hash Except"};
+    }
+    return {"image":"ex_hash_setop_unknown.png","image_text":"Hashed SetOp Unknown"};
+  }
+  return {"image":"ex_setop.png","image_text":"SetOp"};
+},
+"Seq Scan": function(data) {
+  return {"image":"ex_scan.png","image_text":data['Relation Name']};
+},
+"Subquery Scan" : {"image":"ex_subplan.png","image_text":"SubQuery Scan"},
+"Sort" : {"image":"ex_sort.png","image_text":"Sort"},
+"Tid Scan" : {"image":"ex_tid_scan.png","image_text":"Tid Scan"},
+"Unique" : {"image":"ex_unique.png","image_text":"Unique"},
+"Values Scan" : {"image":"ex_values_scan.png","image_text":"Values Scan"},
+"WindowAgg" : {"image":"ex_window_aggregate.png","image_text":"Window Aggregate"},
+"WorkTable Scan" : {"image":"ex_worktable_scan.png","image_text":"WorkTable Scan"},
+"Undefined" : {"image":"ex_unknown.png","image_text":"Undefined"},
+}
+
+// Some predefined constants used to calculate image location and its border
+var pWIDTH = pHEIGHT = 100.
+    IMAGE_WIDTH = IMAGE_HEIGHT = 50;
+var offsetX = 200,
+    offsetY = 60;
+var ARROW_WIDTH = 10,
+    ARROW_HEIGHT = 10;
+var TXT_ALLIGN = 5,
+    TXT_SIZE = "15px";
+var TOTAL_WIDTH = undefined,
+    TOTAL_HEIGHT = undefined;
+var xMargin = 25,
+    yMargin = 25;
+var MIN_ZOOM_FACTOR = 0.01,
+    MAX_ZOOM_FACTOR = 2,
+    INIT_ZOOM_FACTOR = 1;
+    ZOOM_RATIO = 0.05;
+
+
+// Backbone model for each plan property of input JSON object
+var PlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plans": [],
+        level: [],
+        "image": undefined,
+        "image_text": undefined,
+        xpos: undefined,
+        ypos: undefined,
+        width: pWIDTH,
+        height: pHEIGHT
+    },
+    parse: function(data) {
+        var idx = 1,
+            lvl = data.level = data.level || [idx],
+            plans = [],
+            node_type = data['Node Type'],
+            // Calculating relative xpos of current node from top node
+            xpos = data.xpos = data.xpos - pWIDTH,
+            // Calculating relative ypos of current node from top node
+            ypos = data.ypos,
+            maxChildWidth = 0;
+
+        data['width'] = pWIDTH;
+        data['height'] = pHEIGHT;
+
+        /*
+         * calculating xpos, ypos, width and height if current node is a subplan
+         */
+        if (data['Parent Relationship'] === "SubPlan") {
+            data['width'] += (xMargin * 2) + (xMargin / 2);
+            data['height'] += (yMargin * 2);
+            data['ypos'] += yMargin;
+            xpos -= xMargin;
+            ypos += yMargin;
+        }
+
+        if(node_type.startsWith("(slice"))
+            node_type = node_type.substring(0,7);
+
+        // Get the image information for current node
+        var mapperObj = (_.isFunction(imageMapper[node_type]) &&
+                imageMapper[node_type].apply(undefined, [data])) ||
+                imageMapper[node_type] || 'Undefined';
+
+        data["image"] = mapperObj["image"];
+        data["image_text"] = mapperObj["image_text"];
+
+        // Start calculating xpos, ypos, width and height for child plans if any
+        if ('Plans' in data) {
+
+            data['width'] += offsetX;
+
+            _.each(data['Plans'], function(p) {
+                var level = _.clone(lvl),
+                    plan = new PlanModel();
+
+                level.push(idx);
+                plan.set(plan.parse(_.extend(
+                    p, {
+                        "level": level,
+                        xpos: xpos - offsetX,
+                        ypos: ypos
+                    })));
+
+                if (maxChildWidth < plan.get('width')) {
+                    maxChildWidth = plan.get('width');
+                }
+
+                var childHeight = plan.get('height');
+
+                if (idx !== 1) {
+                    data['height'] = data['height'] + childHeight + offsetY;
+                } else if (childHeight > data['height']) {
+                    data['height'] = childHeight;
+                }
+                ypos += childHeight + offsetY;
+
+                plans.push(plan);
+                idx++;
+            });
+        }
+
+        // Final Width and Height of current node
+        data['width'] += maxChildWidth;
+        data['Plans'] = plans;
+
+        return data;
+    },
+
+    /*
+     * Required to parse and include non-default params of
+     * plan into backbone model
+     */
+    toJSON: function(non_recursive) {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (non_recursive) {
+            delete res['Plans'];
+      } else {
+            var plans = [];
+            _.each(res['Plans'], function(p) {
+              plans.push(p.toJSON());
+            });
+            res['Plans'] = plans;
+      }
+      return res;
+    },
+
+    // Draw an arrow to parent node
+    drawPolyLine: function(g, startX, startY, endX, endY, opts, arrowOpts) {
+      // Calculate end point of first starting straight line (startx1, starty1)
+      // Calculate start point of 2nd straight line (endx1, endy1)
+      var midX1 = startX + ((endX - startX) / 3),
+          midX2 = startX + (2 * ((endX - startX) / 3));
+
+      //create arrow head
+      var arrow = g.polygon(
+                    [0, ARROW_HEIGHT,
+                    (ARROW_WIDTH / 2),ARROW_HEIGHT,
+                    (ARROW_HEIGHT / 4), 0,
+                    0, ARROW_WIDTH]
+                    ).transform("r90");
+      var marker = arrow.marker(
+                         0, 0, ARROW_WIDTH, ARROW_HEIGHT, 0, (ARROW_WIDTH / 2)
+                         ).attr(arrowOpts);
+
+      // First straight line
+      g.line(
+        startX, startY, midX1, startY
+        ).attr(opts);
+
+      // Diagonal line
+      g.line(
+        midX1-1, startY, midX2, endY
+        ).attr(opts);
+
+      // Last straight line
+      var line = g.line(
+                   midX2, endY, endX, endY
+                   ).attr(opts);
+      line.attr({markerEnd: marker})
+    },
+
+    // Draw image, its name and its tooltip
+    draw: function(s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer) {
+        var g = s.g();
+        var toolTipTable = {};
+        var currentXpos = xpos + this.get('xpos') ,
+            currentYpos = ypos + this.get('ypos'),
+            isSubPlan = (this.get('Parent Relationship') === "SubPlan");
+
+        // Draw the subplan rectangle
+        if (isSubPlan) {
+          g.rect(
+            currentXpos - this.get('width') + pWIDTH + xMargin,
+            currentYpos - yMargin,
+            this.get('width') - xMargin,
+            this.get('height'), 5
+          ).attr({
+              stroke: '#444444',
+              'strokeWidth': 1.2,
+              fill: 'gray',
+              fillOpacity: 0.2
+          });
+
+          //provide subplan name
+          var text = g.text(
+            currentXpos  + pWIDTH - ( this.get('width') / 2) - xMargin,
+            currentYpos + pHEIGHT  - (this.get('height') / 2) - yMargin,
+            this.get('Subplan Name')
+          ).attr({
+            fontSize: TXT_SIZE, "text-anchor":"start",
+            fill: 'red'
+          });
+        }
+
+        // Draw the actual image for current node
+        var image = g.image(
+            pgExplain.prefix + this.get('image'),
+            currentXpos + (pWIDTH - IMAGE_WIDTH) / 2,
+            currentYpos + (pHEIGHT - IMAGE_HEIGHT) / 2,
+            IMAGE_WIDTH,
+            IMAGE_HEIGHT
+        );
+
+        // Draw tooltip
+        var image_data = this.toJSON();
+        image.mouseover(function(evt){
+
+          // Empty the tooltip content if it has any and add new data
+          toolTipContainer.empty();
+          var tooltip = $('<table></table>',
+                           {
+                             class: "pgadmin-tooltip-table",
+                             style: "border-collapse: collapse; border-spacing: 1px; top: auto; left: auto;"
+                           }
+                         ).appendTo(toolTipContainer);
+          _.each(image_data, function(value,key) {
+            if(key !== 'image' && key !== 'Plans' &&
+               key !== 'level' && key !== 'image' &&
+               key !== 'image_text' && key !== 'xpos' &&
+               key !== 'ypos' && key !== 'width' &&
+               key !== 'height') {
+              tooltip.append( '<tr><td class="label explain-tooltip">' + key + '</td><td class="label explain-tooltip-val">' + value + '</td></tr>' );
+            };
+          });
+
+          var zoomFactor = graphContainer.data('zoom-factor');
+
+          // Calculate co-ordinates for tooltip
+          var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft());
+          var toolTipY = ((currentYpos + pHEIGHT) * zoomFactor - graphContainer.scrollTop());
+
+          // Recalculate x.y if tooltip is going out of screen
+          if(graphContainer.width() < (toolTipX + toolTipContainer[0].clientWidth))
+            toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH*zoomFactor));
+          //if(document.children[0].clientHeight < (toolTipY + toolTipContainer[0].clientHeight))
+          if(graphContainer.height() < (toolTipY + toolTipContainer[0].clientHeight))
+            toolTipY -= (toolTipContainer[0].clientHeight + ((pHEIGHT/2)*zoomFactor));
+
+          toolTipX = toolTipX < 0 ? 0 : (toolTipX);
+          toolTipY = toolTipY < 0 ? 0 : (toolTipY);
+
+          // Show toolTip at respective x,y coordinates
+          toolTipContainer.css({'opacity': '0.8'});
+          toolTipContainer.css('left', toolTipX);
+          toolTipContainer.css( 'top', toolTipY);
+        });
+
+        // Remove tooltip when mouse is out from node's area
+        image.mouseout(function() {
+          toolTipContainer.empty();
+          toolTipContainer.css({'opacity': '0'});
+          toolTipContainer.css('left', 0);
+          toolTipContainer.css( 'top', 0);
+        });
+
+        // Draw text below the node
+        var label = g.g();
+        g.multitext(
+          currentXpos + (pWIDTH / 2),
+          currentYpos + pHEIGHT - TXT_ALLIGN,
+          this.get('image_text'),
+          150,
+          {"font-size": TXT_SIZE ,"text-anchor":"middle"}
+        );
+
+        // Draw Arrow to parent only its not the first node
+        if (!_.isUndefined(pYpos)) {
+            var startx = currentXpos + pWIDTH;
+            var starty = currentYpos + (pHEIGHT / 2);
+            var endx = pXpos - ARROW_WIDTH;
+            var endy = pYpos + (pHEIGHT / 2);
+            var start_cost = this.get("Startup Cost"),
+                total_cost = this.get("Total Cost");
+
+            // Calculate arrow width according to cost of a particular plan
+            var arrow_size = Math.round(Math.log((start_cost+total_cost)/2 + start_cost));
+            arrow_size = arrow_size < 1 ? 1 : arrow_size > 10 ? 10 : arrow_size;
+
+            var arrow_view_box = [0, 0, 2*ARROW_WIDTH, 2*ARROW_HEIGHT];
+            var opts = {stroke: "#000000", strokeWidth: arrow_size + 1},
+                subplanOpts = {stroke: "#866486", strokeWidth: arrow_size + 1},
+                arrowOpts = {viewBox: arrow_view_box.join(" ")};
+
+            // Draw an arrow from current node to its parent
+            this.drawPolyLine(
+              g, startx, starty, endx, endy,
+              isSubPlan ? subplanOpts : opts, arrowOpts
+            );
+        }
+
+        var plans = this.get('Plans');
+
+        // Draw nodes for current plan's children
+        _.each(plans, function(p) {
+            p.draw(s, xpos, ypos, currentXpos, currentYpos, graphContainer, toolTipContainer);
+        });
+    }
+});
+
+// Main backbone model to store JSON object
+var MainPlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plan": undefined,
+        xpos: 0,
+        ypos: 0,
+    },
+    initialize: function() {
+        this.set("Plan", new PlanModel());
+    },
+
+    // Parse the JSON data and fetch its children plans
+    parse: function(data) {
+       if (data && 'Plan' in data) {
+            var plan = this.get("Plan");
+            plan.set(
+              plan.parse(
+                _.extend(
+                  data['Plan'], {
+                    xpos: 0,
+                    ypos: 0
+                  })));
+
+            data['xpos'] = 0;
+            data['ypos'] = 0;
+            data['width'] = plan.get('width') + (xMargin * 2);
+            data['height'] = plan.get('height') + (yMargin * 2);
+
+            delete data['Plan'];
+        }
+
+        return data;
+    },
+    toJSON: function() {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (res.Plan) {
+        res.Plan = res.Plan.toJSON();
+      }
+
+      return res;
+    },
+    draw: function(s, xpos, ypos, graphContainer, toolTipContainer) {
+        var g = s.g();
+
+        //draw the border
+        g.rect(
+	        0, 0, this.get('width') - 10, this.get('height') - 10, 5
+	    ).attr({
+            stroke: '#FFEBCD', 'strokeWidth': 1.2,
+            fill: '#FFF8DC', fillOpacity: 0.5
+        });
+
+        //Fetch total width, height
+        TOTAL_WIDTH = this.get('width');
+        TOTAL_HEIGHT = this.get('height');
+        var plan = this.get('Plan');
+
+        //Draw explain graph
+        plan.draw(g, xpos, ypos, undefined, undefined, graphContainer, toolTipContainer);
+    }
+});
+
+// Parse and draw full graphical explain
+_.extend(
+    pgExplain, {
+        // Assumption container is a jQuery object
+        DrawJSONPlan: function(container, plan) {
+          var my_plans = [];
+          container.empty();
+          var curr_zoom_factor = 1.0;
+
+          var zoomArea =$('<div></div>', {
+                class: 'pg-explain-zoom-area btn-group',
+                role: 'group'
+                }).appendTo(container),
+              zoomInBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom in'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-search-plus'
+                  })),
+              zoomToNormal = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom to original'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-arrows-alt'
+                  }))
+              zoomOutBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom out'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>', {
+                    class: 'fa fa-search-minus'
+                  }));
+
+          // Main div to be drawn all images on
+          var planDiv = $('<div></div>',
+                           {class: "pgadmin-explain-container"}
+                         ).appendTo(container),
+              // Div to draw tool-tip on
+              toolTip = $('<div></div>',
+                           {id: "toolTip",
+                           class: "pgadmin-explain-tooltip"
+                           }
+                         ).appendTo(container);
+          toolTip.empty();
+          planDiv.data('zoom-factor', curr_zoom_factor);
+
+          var w = 0, h = 0,
+              x = xMargin, h = yMargin;
+
+          _.each(plan, function(p) {
+            var main_plan = new MainPlanModel();
+
+            // Parse JSON data to backbone model
+            main_plan.set(main_plan.parse(p));
+            w = main_plan.get('width');
+            h = main_plan.get('height');
+
+            var s = Snap(w, h),
+                $svg = $(s.node).detach();
+            planDiv.append($svg);
+
+            main_plan.draw(s, w - xMargin, yMargin, planDiv, toolTip);
+
+            var initPanelWidth = planDiv.width(),
+                initPanelHeight = planDiv.height();
+
+             /*
+              * Scale graph in case its width is bigger than panel width
+              * in which the graph is displayed
+              */
+            if(initPanelWidth < w) {
+              var width_ratio = initPanelWidth / w;
+
+              curr_zoom_factor = width_ratio;
+              curr_zoom_factor = curr_zoom_factor < MIN_ZOOM_FACTOR ? MIN_ZOOM_FACTOR : curr_zoom_factor;
+              curr_zoom_factor = curr_zoom_factor > INIT_ZOOM_FACTOR ? INIT_ZOOM_FACTOR : curr_zoom_factor;
+
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+            }
+
+            zoomInBtn.on('click', function(e){
+              curr_zoom_factor = ((curr_zoom_factor + ZOOM_RATIO) > MAX_ZOOM_FACTOR) ? MAX_ZOOM_FACTOR : (curr_zoom_factor + ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomInBtn.blur();
+            });
+
+            zoomOutBtn.on('click', function(e) {
+              curr_zoom_factor = ((curr_zoom_factor - ZOOM_RATIO) < MIN_ZOOM_FACTOR) ? MIN_ZOOM_FACTOR : (curr_zoom_factor - ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomOutBtn.blur();
+            });
+
+            zoomToNormal.on('click', function(e) {
+              curr_zoom_factor = INIT_ZOOM_FACTOR;
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomToNormal.blur();
+            });
+          });
+        }
+    });
+
+    return pgExplain;
+});
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index 45612f6..cc74b29 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -247,3 +247,9 @@
   background: #5B9CEF;
   color: white;
 }
+
+.sql-editor-explain {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index d617d7a..9c065e2 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -1,9 +1,9 @@
 define(
-  ['jquery', 'underscore', 'alertify', 'pgadmin', 'backbone', 'backgrid', 'codemirror',
+  ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', 'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain',
    'codemirror/mode/sql', 'codemirror/addon/selection/mark-selection', 'codemirror/addon/selection/active-line',
    'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator', 'backgrid.filter',
    'bootstrap', 'pgadmin.browser', 'wcdocker'],
-  function($, _, alertify, pgAdmin, Backbone, Backgrid, CodeMirror) {
+  function($, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain) {
 
     // Some scripts do export their object in the window only.
     // Generally the one, which do no have AMD support.
@@ -159,6 +159,12 @@ define(
         "click #btn-auto-commit": "on_auto_commit",
         "click #btn-auto-rollback": "on_auto_rollback",
         "click #btn-clear-history": "on_clear_history",
+        "click #btn-explain": "on_explain",
+        "click #btn-explain-analyze": "on_explain_analyze",
+        "click #btn-explain-verbose": "on_explain_verbose",
+        "click #btn-explain-costs": "on_explain_costs",
+        "click #btn-explain-buffers": "on_explain_buffers",
+        "click #btn-explain-timing": "on_explain_timing",
         "change .limit": "on_limit_change"
       },
 
@@ -217,10 +223,53 @@ define(
             '</button>',
             '<ul class="dropdown-menu dropdown-menu">',
               '<li>',
+                '<a id="btn-explain" href="#">',
+                  '<span>{{ _('Explain') }}</span>',
+                '</a>',
+              '</li>',
+              '<li>',
+                '<a id="btn-explain-analyze" href="#">',
+                    '<span>{{ _('Explain analyze') }}</span>',
+                '</a>',
+              '</li>',
+              '<li class="divider"></li>',
+              '<li class="dropdown-submenu dropdown-submenu">',
+                '<a href="#">{{ _('Explain Options') }}</a>',
+                '<ul class="dropdown-menu">',
+                  '<li>',
+                    '<a id="btn-explain-verbose" href="#" tabindex="-1">',
+                      '<i class="explain-verbose fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Verbose') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-costs" href="#">',
+                      '<i class="explain-costs fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Costs') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-buffers" href="#">',
+                      '<i class="explain-buffers fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Buffers') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-timing" href="#">',
+                      '<i class="explain-timing fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Timing') }} </span>',
+                    '</a>',
+                  '</li>',
+                '</ul>',
+              '</li>',
+              '<li class="divider"></li>',
+              '<li>',
                 '<a id="btn-auto-commit" href="#">',
                     '<i class="auto-commit fa fa-check" aria-hidden="true"></i>',
                     '<span> {{ _('Auto-Commit') }} </span>',
                 '</a>',
+              '</li>',
+              '<li>',
                 '<a id="btn-auto-rollback" href="#">',
                     '<i class="auto-rollback fa fa-check visibility-hidden" aria-hidden="true"></i>',
                     '<span> {{ _('Auto-Rollback') }} </span>',
@@ -377,7 +426,7 @@ define(
           height:'100%',
           isCloseable: false,
           isPrivate: true,
-          content: '<div class="sql-editor-explian"></div>'
+          content: '<div class="sql-editor-explain"></div>'
         })
 
         var messages = new pgAdmin.Browser.Panel({
@@ -768,6 +817,78 @@ define(
             self,
             self.handler
         );
+      },
+
+      // Callback function for explain button click.
+      on_explain: function() {
+        var self = this;
+
+        // Trigger the explain signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain analyze button click.
+      on_explain_analyze: function() {
+        var self = this;
+
+        // Trigger the explain analyze signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-analyze',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "verbose" button click
+      on_explain_verbose: function() {
+        var self = this;
+
+        // Trigger the explain "verbose" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-verbose',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "costs" button click
+      on_explain_costs: function() {
+        var self = this;
+
+        // Trigger the explain "costs" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-costs',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "buffers" button click
+      on_explain_buffers: function() {
+        var self = this;
+
+        // Trigger the explain "buffers" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-buffers',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "timing" button click
+      on_explain_timing: function() {
+        var self = this;
+
+        // Trigger the explain "timing" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-timing',
+            self,
+            self.handler
+        );
       }
     });
 
@@ -830,6 +951,12 @@ define(
           self.on('pgadmin-sqleditor:button:download', self._download, self);
           self.on('pgadmin-sqleditor:button:auto_rollback', self._auto_rollback, self);
           self.on('pgadmin-sqleditor:button:auto_commit', self._auto_commit, self);
+          self.on('pgadmin-sqleditor:button:explain', self._explain, self);
+          self.on('pgadmin-sqleditor:button:explain-analyze', self._explain_analyze, self);
+          self.on('pgadmin-sqleditor:button:explain-verbose', self._explain_verbose, self);
+          self.on('pgadmin-sqleditor:button:explain-costs', self._explain_costs, self);
+          self.on('pgadmin-sqleditor:button:explain-buffers', self._explain_buffers, self);
+          self.on('pgadmin-sqleditor:button:explain-timing', self._explain_timing, self);
 
           if (self.is_query_tool) {
             self.gridView.query_tool_obj.refresh();
@@ -1083,10 +1210,24 @@ define(
           var message = 'Total query runtime: ' + self.total_time + '\n' + self.rows_affected + ' rows retrieved.';
           $('.sql-editor-message').text(message);
 
-          // Add the data to the collection and render the grid.
-          self.collection.add(data.result, {parse: true});
-          self.gridView.render_grid(self.collection, self.columns);
-          self.gridView.data_output_panel.focus();
+          /* Add the data to the collection and render the grid.
+           * In case of Explain draw the graph on explain panel
+           * and add json formatted data to collection and render.
+           */
+          var explain_data_array = [];
+          if('QUERY PLAN' in data.result[0]) {
+            self.gridView.explain_panel.focus();
+            var explain_data = {'QUERY PLAN' : JSON.stringify(data.result[0]['QUERY PLAN'], null, 2)};
+            explain_data_array.push(explain_data);
+            pgExplain.DrawJSONPlan($('.sql-editor-explain'), data.result[0]['QUERY PLAN']);
+            self.collection.add(explain_data_array, {parse: true});
+            self.gridView.render_grid(self.collection, self.columns);
+          }
+          else {
+            self.collection.add(data.result, {parse: true});
+            self.gridView.render_grid(self.collection, self.columns);
+            self.gridView.data_output_panel.focus();
+          }
 
           // Hide the loading icon
           self.trigger('pgadmin-sqleditor:loading-icon:hide');
@@ -1812,7 +1953,7 @@ define(
 
         // This function will fetch the sql query from the text box
         // and execute the query.
-        _execute: function () {
+        _execute: function (explain_prefix) {
           var self = this,
               sql = '',
               history_msg = '';
@@ -1831,6 +1972,9 @@ define(
           else
             sql = self.gridView.query_tool_obj.getValue();
 
+          if (explain_prefix != undefined)
+            sql = explain_prefix + ' ' + sql;
+
           self.query_start_time = new Date();
           self.query = sql;
           self.rows_affected = 0;
@@ -2149,6 +2293,71 @@ define(
               alertify.alert('Auto Commit Error', msg);
             }
           });
+        },
+
+        // This function will
+        _explain: function() {
+          var self = this;
+          var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'off' : 'on';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'off' : 'on';
+          var buffers = $('.explain-buffers').hasClass('visibility-hidden') ? 'off' : 'on';
+          var timing = $('.explain-timing').hasClass('visibility-hidden') ? 'off' : 'on';
+
+          var explain_query = 'Explain (format json, ANALYZE off, VERBOSE %s, COSTS %s, BUFFERS %s, TIMING %s) ';
+          explain_query = S(explain_query).sprintf(verbose, costs, buffers, timing).value();
+          self._execute(explain_query);
+        },
+
+        // This function will
+        _explain_analyze: function() {
+          var self = this;var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'off' : 'on';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'off' : 'on';
+          var buffers = $('.explain-buffers').hasClass('visibility-hidden') ? 'off' : 'on';
+          var timing = $('.explain-timing').hasClass('visibility-hidden') ? 'off' : 'on';
+
+          var explain_query = 'Explain (format json, ANALYZE on, VERBOSE %s, COSTS %s, BUFFERS %s, TIMING %s) ';
+          explain_query = S(explain_query).sprintf(verbose, costs, buffers, timing).value();
+          self._execute(explain_query);
+        },
+
+        // This function will toggle "verbose" option in explain
+        _explain_verbose: function() {
+          var self = this;
+          if ($('.explain-verbose').hasClass('visibility-hidden') === true)
+            $('.explain-verbose').removeClass('visibility-hidden');
+          else {
+            $('.explain-verbose').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "costs" option in explain
+        _explain_costs: function() {
+          var self = this;
+          if ($('.explain-costs').hasClass('visibility-hidden') === true)
+            $('.explain-costs').removeClass('visibility-hidden');
+          else {
+            $('.explain-costs').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "buffers" option in explain
+        _explain_buffers: function() {
+          var self = this;
+          if ($('.explain-buffers').hasClass('visibility-hidden') === true)
+            $('.explain-buffers').removeClass('visibility-hidden');
+          else {
+            $('.explain-buffers').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "timing" option in explain
+        _explain_timing: function() {
+          var self = this;
+          if ($('.explain-timing').hasClass('visibility-hidden') === true)
+            $('.explain-timing').removeClass('visibility-hidden');
+          else {
+            $('.explain-timing').addClass('visibility-hidden');
+          }
         }
       }
     );


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
@ 2016-04-25 09:36 ` Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Sanket Mehta @ 2016-04-25 09:36 UTC (permalink / raw)
  To: pgadmin-hackers

Hi,

This patch includes the patch sent earlier for stand alone graphical
explain.

And also "horizontal lines are not proper" bug is fixed in the same which
was reported by Dave in previous patch.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <[email protected]
> wrote:

> Hi Team,
>
> PFA the first patch for graphical explain integrated in sql editor.
>
> Below are the few things which are different from previous patch which was
> sent for stand alone graphical explain.
>
>  -  Now user can select Explain/Explain Analyze with four optional
> properties (Verbose, costs, timing and buffers)
>
>  - Initially graph will be scale (according to only its width not height)
> to fit to screen so no blank space will be there in case of very large
> graph.
>
> - Along with zoom in/out button, "zoom to original" button is also
> provided, by clicking on which graph will be scale to its original size
> (not same as initial one which is according to screen size).
>
> Please do review this patch and let me know in case you have any comments.
>
>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
@ 2016-04-25 11:06   ` Ashesh Vashi <[email protected]>
  2016-05-09 15:19     ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Ashesh Vashi @ 2016-04-25 11:06 UTC (permalink / raw)
  To: Sanket Mehta <[email protected]>; +Cc: pgadmin-hackers

Hi Sanket,

Please find the review comments.
- Please add the missing 'explain.css'.
- The application should be smart enough to handle conflict in options.
   i.e.
   Buffer is not a valid options without EXPLAIN ANALYZE.
- A statement having EXPLAIN keywords with different format should at least
render the output in the data-grid.
  i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
- Please use the keywords used in the EXPLAIN statement in capital.
- Explain should not work with empty string.
- Font size in the tooltip is very small.
- Smoothing the zoom functionality.
- Arrow marker is hardly visible.


--

Thanks & Regards,

Ashesh Vashi
EnterpriseDB INDIA: Enterprise PostgreSQL Company
<http://www.enterprisedb.com;


*http://www.linkedin.com/in/asheshvashi*
<http://www.linkedin.com/in/asheshvashi;

On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <[email protected]
> wrote:

> Hi,
>
> This patch includes the patch sent earlier for stand alone graphical
> explain.
>
> And also "horizontal lines are not proper" bug is fixed in the same which
> was reported by Dave in previous patch.
>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
> [email protected]> wrote:
>
>> Hi Team,
>>
>> PFA the first patch for graphical explain integrated in sql editor.
>>
>> Below are the few things which are different from previous patch which
>> was sent for stand alone graphical explain.
>>
>>  -  Now user can select Explain/Explain Analyze with four optional
>> properties (Verbose, costs, timing and buffers)
>>
>>  - Initially graph will be scale (according to only its width not height)
>> to fit to screen so no blank space will be there in case of very large
>> graph.
>>
>> - Along with zoom in/out button, "zoom to original" button is also
>> provided, by clicking on which graph will be scale to its original size
>> (not same as initial one which is according to screen size).
>>
>> Please do review this patch and let me know in case you have any comments.
>>
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>
>


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
@ 2016-05-09 15:19     ` Sanket Mehta <[email protected]>
  2016-05-09 18:26       ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Sanket Mehta @ 2016-05-09 15:19 UTC (permalink / raw)
  To: Ashesh Vashi <[email protected]>; +Cc: pgadmin-hackers

Hi,

PFA revised patch according to Ashesh's comments.
Please find my response inline.

I am currently adding minimap feature in graphical explain.
I will send a new patch for the same.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Mon, Apr 25, 2016 at 4:36 PM, Ashesh Vashi <[email protected]
> wrote:

> Hi Sanket,
>
> Please find the review comments.
> - Please add the missing 'explain.css'.
>
Done

> - The application should be smart enough to handle conflict in options.
>    i.e.
>    Buffer is not a valid options without EXPLAIN ANALYZE.
>
Done

> - A statement having EXPLAIN keywords with different format should at
> least render the output in the data-grid.
>   i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
>
Done

> - Please use the keywords used in the EXPLAIN statement in capital.
>
Done

> - Explain should not work with empty string.
>
Done

> - Font size in the tooltip is very small.
>
Done

>
>
- Smoothing the zoom functionality.
>
Minimap will be added and zoom functionality will be removed. So it is
ignored.

- Arrow marker is hardly visible.
>
Done.

>
>
> --
>
> Thanks & Regards,
>
> Ashesh Vashi
> EnterpriseDB INDIA: Enterprise PostgreSQL Company
> <http://www.enterprisedb.com;
>
>
> *http://www.linkedin.com/in/asheshvashi*
> <http://www.linkedin.com/in/asheshvashi;
>
> On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <
> [email protected]> wrote:
>
>> Hi,
>>
>> This patch includes the patch sent earlier for stand alone graphical
>> explain.
>>
>> And also "horizontal lines are not proper" bug is fixed in the same which
>> was reported by Dave in previous patch.
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
>> [email protected]> wrote:
>>
>>> Hi Team,
>>>
>>> PFA the first patch for graphical explain integrated in sql editor.
>>>
>>> Below are the few things which are different from previous patch which
>>> was sent for stand alone graphical explain.
>>>
>>>  -  Now user can select Explain/Explain Analyze with four optional
>>> properties (Verbose, costs, timing and buffers)
>>>
>>>  - Initially graph will be scale (according to only its width not
>>> height) to fit to screen so no blank space will be there in case of very
>>> large graph.
>>>
>>> - Along with zoom in/out button, "zoom to original" button is also
>>> provided, by clicking on which graph will be scale to its original size
>>> (not same as initial one which is according to screen size).
>>>
>>> Please do review this patch and let me know in case you have any
>>> comments.
>>>
>>>
>>> Regards,
>>> Sanket Mehta
>>> Sr Software engineer
>>> Enterprisedb
>>>
>>
>>
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/x-patch] integrated_graphical_explainV4.patch (484.3K, 3-integrated_graphical_explainV4.patch)
  download | inline diff:
diff --git a/libraries.txt b/libraries.txt
index 9fcf755..ad1c004 100644
--- a/libraries.txt
+++ b/libraries.txt
@@ -27,3 +27,4 @@ backgrid-filter     01b2b21     MIT          https://github.com/wyuenho/backgrid
 backbone.paginator  2.0.3       MIT          http://github.com/backbone-paginator/backbone.paginator
 backgrid-paginator  03632df     MIT          https://github.com/wyuenho/backgrid-paginator
 backgrid-select-all 1a00053     MIT          https://github.com/wyuenho/backgrid-select-all
+Snap.svg            0.4.1       APACHE       http://snapsvg.io/
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index f461cbe..33c693e 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -6,28 +6,59 @@
 # This software is released under the PostgreSQL Licence
 #
 ##########################################################################
-
 """A blueprint module providing utility functions for the application."""
 
-import datetime
-from flask import session, current_app
+from flask import url_for, render_template
 from pgadmin.utils import PgAdminModule
 import pgadmin.utils.driver as driver
+import config
 
 MODULE_NAME = 'misc'
 
-# Initialise the module
-blueprint = PgAdminModule(MODULE_NAME, __name__,
-                          url_prefix='')
+class MiscModule(PgAdminModule):
 
-##########################################################################
-# A special URL used to "ping" the server
-##########################################################################
+    def get_own_javascripts(self):
+        scripts = [{
+                'name': 'pgadmin.misc.explain',
+                'path': url_for('misc.index') + 'explain/explain',
+                'preloaded': False
+                },{
+                'name': 'snap.svg',
+                'path': url_for(
+                    'misc.static', filename='explain/js/' + (
+                        'snap.svg' if  config.DEBUG else 'snap.svg-min'
+                        )),
+                'preloaded': False
+                 }]
+        return scripts
+
+    def get_own_stylesheets(self):
+        stylesheets = []
+        stylesheets.append(url_for('misc.static', filename='explain/css/explain.css'))
+        return stylesheets
 
+ # Initialise the module
+blueprint = MiscModule(MODULE_NAME, __name__, static_url_path="/static")
+
+ ##########################################################################
+ # A special URL used to "ping" the server
+ ##########################################################################
+
[email protected]("/")
+def index():
+    return ''
 
 @blueprint.route("/ping", methods=('get', 'post'))
 def ping():
-    """Generate a "PING" response to indicate that the server is alive."""
-    driver.ping()
+     driver.ping()
+
+def demo():
+    return render_template('demo_explain.html')
+
[email protected]("/explain/explain.js")
+def explain_js():
+    return render_template("explain/js/explain.js")
 
-    return "PING"
[email protected]("/sample")
+def sample():
+    return render_template('sample.html')
diff --git a/web/pgadmin/misc/static/explain/css/explain.css b/web/pgadmin/misc/static/explain/css/explain.css
new file mode 100644
index 0000000..49a4bc4
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/css/explain.css
@@ -0,0 +1,56 @@
+.pg-explain-zoom-area {
+    position: absolute;
+    top: 5px;
+    left: 5px;
+    opacity: 0.5;
+    cursor: pointer;
+}
+
+.pg-explain-zoom-btn {
+    top: 5px;
+    min-width: 25px;
+    cursor: pointer;
+    border: 1px solid transparent;
+}
+
+.pg-explain-zoom-area:hover {
+   opacity: 1;
+}
+
+.explain-tooltip {
+  display: table-cell;
+  text-align: left;
+  white-space: nowrap;
+  line-height: 10px !important;
+  padding: 2px !important;
+  font-size: small;
+}
+
+td.explain-tooltip-val {
+  display: table-cell;
+  text-align: left;
+  white-space: pre-wrap;
+  font-size: smaller;
+}
+
+.pgadmin-explain-tooltip {
+  position: absolute;
+  padding:5px;
+  border: 1px solid white;
+  opacity:0;
+  color: cornsilk;
+  background-color: #010125;
+}
+
+.pgadmin-tooltip-table {
+  border-collapse: collapse;
+  border-spacing: 1px;
+  top: auto;
+  left: auto;
+}
+
+.pgadmin-explain-container {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
\ No newline at end of file
diff --git a/web/pgadmin/misc/static/explain/img/ex_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bfe75d909f5092c4d3ba8978c75fbc57d7f606d
GIT binary patch
literal 574
zcmV-E0>S->P)<h;3K|Lk000e1NJLTq001%o001%w1^@s69zTe&00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10lP^=K~!jg?U_w#!!Qs=pNrR76nc`JAiEyfyPUu$F?5lg!9`tE
zrVdorjI6O#*B=N1Q4~GTk7uODImZ$7QhEcqbb{2T!+^AsNlnvqz}0v!OZCpVcg+u)
zSl03oH-ylcGy!)Fj09u=UN>$mMIX+&H|b<ajP!gzp{f<N2t38e1(}OYz(X)^Z9SDm
zaL$Pb&;cXx85twc3D+9}YYwWtX(n61tgLAZVhA&A0ZDox`m}f_o&;Lp=3^|TZAm4?
zB8D-uw2Hk&77xL~GD+H8Yh{L+-D~onRU64N$mC{z9Z`Z<4$%uyDn(tUuBBqiTE>@*
zne6>YDVVIT^|Y|u&2%+YKxQ4H!ZKN8+Uk0kwJKPjW&<(>@$PjAe4RCOnSn%Nr0(=P
zYi|fJ04V_hnL$cH0K3&%;sz_V=BgQD)cm$)2vvhs6@*_isdrBfc8kD{yg=7gktITF
z+PGFu2!0OeLWgu>5LFp3D9xourL!bQu%a?wd{rRqFIvi++{=Q!&>aaV%KTa{dS;2c
z$5o3IhEOTyT37x61pK30-JX4KbAS7Pk<5;R_SRus>jbGyCrEAj0!#X?PWurOy8r+H
M07*qoM6N<$f>VU-$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_append.png b/web/pgadmin/misc/static/explain/img/ex_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..017a2068b735f13cb89a3bcb2832e5880d17df56
GIT binary patch
literal 1162
zcmV;51a<p~P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(LcQrnzUg(&?|IPgRl@0p)bM7=>}tvEZ_4YI+3|bM?Sj$mrrz?7)b5hj
z@2}zW1*hRn!RZ^W<6Fh+V#n(^x8+d2=WEC4a+aaNk2du1$nfm9@9MSh>9q3g#qH&>
z?c}iR<FN7S!|>?B>));G-mL1~tMBB$>*k;D>$d6On&{t|?&h=S+m`0ol<eWI<I<1f
z(2eQZsp{jL=-rs*){^1Oi|N^?<kgYn(~#oMjOf><@94Ab;k?JHb@1lE=hUR;)1&O&
zx!}r)-o=I9!-U<zg67eo-^hpM&!OhgqTIiL<j$Y(=D_CCqwCqW<jtPr%%0=Rp4+{D
z+PZy)sbo@(8Kui)sL*Ju)oiZXakAicwdHz}mC<Hv(aX&7yuR$dzwOS>^3l=q!NTst
z#P8G8^VQY#$jI=?$??j|@!8t--ro1u*Y)Ay_q@IAr>W+ss^`DI?yaxsud(UH#qY<+
z@3pq;x47%Lx$M-`^vB2WiI(>{00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0zpYcK~zY`?U3tJ5>Xh&?SzGeqJ$LLD=fGO%SB*ek((>YO)i2W3vOk#CSYi4
znRfSIZ+9mfuyZIJXZldjhj-?kd4K0Q&oeUeU#e~(MZ}3i&`phI^cK3U)oRDk*qyt&
zaWp=m*H5C!DTCo2!Xg@@(Kw2xO(xTQ^uTO3>(InQtyYhwW@dB-G{|N8&s`p=e<n^G
z0<#v2W%goPDar;m`yB0nd8dnU0~WD(JRXZWy+HYV3x2Q%f<YFXp-`9`j6@<<8Ch7g
z109S;oxp^{vG^dw8;L|H@Gk}eG_cV`k^ych7US{Aj}#QOtfZjT6pWn09q0KJ7I_Sc
zh!gMP^;&1a=J(qj;9yzj3b;8go`O_5oyp=qCa3T%U!+JRLvo5(EXPASzgj5bk-lP+
zO0n@=u9Sw%YN1djA(xBgOQn1U)(VwM6_42b_BpvF*6CW8Q^eI2nT;%D%hhV_+8T4v
zEISV?48yr0#q(+T{k3Ab2DQz)fOi2p8cn$56iaG~e0~Fpl}e)u^=7jv;1M>FxKwPp
z(r9dgKt_How%TaO#{-achU4Ux__UIuSXNThl@v8W5U#DUt7}W#B5ow&NzYaPMkJm-
z`+3#B5v1H~KNqnZ(M8-Adt7=qvQOWu;_p1vqZcA^BYthzl84euNfB}45NYSt?ruwJ
zclP#POWpm0H;2+;tKB}5j=J6Bw-Oe4cXIOnRO+0aefTJS`uyeVj?_BsblTFl^ZkS4
zzljW=<qD1clll(R-{b2g;$(6F001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@z
zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUh
cIxsdXFf}?bFv^!ztN;K207*qoM6N<$g1#AbUjP6A

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_and.png b/web/pgadmin/misc/static/explain/img/ex_bmp_and.png
new file mode 100644
index 0000000000000000000000000000000000000000..64d5869dcfcf9d94d031fa068cff93ea929180b2
GIT binary patch
literal 1006
zcmV<K0}=d*P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004)P)t-smD2kF
z003rd(R6flb#--ic6N7ncY1nyHi*G{dwYC*e13j@fPjF4f`WsCgM@^HhlhuVh=_}e
zi;azqj*gCGmC%omkC2d%k&%&&j=z$UlEIHQm6er`lHGcqshOFXoSdAVo}QbW#Gjv^
zrlzK+r>D86a;T`NPQK>5s&lKWtGldpyRCMut*u(Y=dP}<ys&q#udltad8@4GzOs6W
zx!ba`vSh{Rx3{;mwCZuo?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?Zd;v-NJ*w!S2S!
z#*5VM-^PaD$cK{E@5aaP;L3^0%F2}3@Z-y#<IJAq&7R`WjpWXs%+2x5&hq8Zq2|$|
z)6>(Y-ty(vljhW<)z#JK)upZ9^VHPy=+~y`*Qe^(w&~ia>DsB<+S=*ds_NXT>)W{8
z+}z#W-Rs`0?A^KE-rnrst?b~g?cclJ-uCR`ui@e0?BlTF;^Ob*zvboS@aDkq=fUUa
z=jrL`@$1Cu>gw?D@bU5S^78WY^Yiuf_4fAm`1ttw`T70*{r~^}C9&%N00001bW%=J
z06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0q;pfK~zY`?UY$l6G0S(b8}-Di3kcP
z+Oo)^h$4bwltoqvL?DD{Mjf_nBqD~O2?A~ymixDzo*9a*Gm})MC?7Zv{c`K8Gv{>Q
zvDq$ko~o9(s?QF(wL!N4k*52GJqwax@WJEtiUu~Rr}eShD?&VOcaK*pj!Vugb=sg#
zfY{%@e)DeKes;iqrqk&yCPD_D!r-C^Nk4<GWMRlx_z0E==`xVDE_fnFj<OJC>PwuJ
zQ#!=9lF84TBBa*NRjVOi9LP1IA^nW2-@@fKw*1L<;8opaGaip`h_l>+MlB1G6SG9S
z=+u$;BX}4UBk&Ro<O=>E`h+(O1ZE*><<f@Dv{HsEI<ou#?nSH``|ZN(SURV-%r>Ht
zNXoo1qP+&hYdgl><kJ{stMHBkrzooyKsJ^Ng_cqlS=#YAjp3zO3@bPinuH0(v@xPG
z-th~(T!n2MS(<=x#ngpg%P%#>ef9pot6A9mcrN5H3(-xi$?R{pAeQfQB&8_Is%c|H
zG5v{QDc(Jx{2HTgO)iJ4;r-tV>{PR?%CelW*cUn`^~2;*7z$c$rLkbz!Q>%$6)bF#
zgDMhW1^r<X!9XBkv6Uy4*H)f(3HbeC^EY-H@%KXSjQLiI5MN;~GdYo*S;9V_FI=R?
cF7zMiA0?@qIju>9W&i*H07*qoM6N<$f<qfLO8@`>

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png b/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png
new file mode 100644
index 0000000000000000000000000000000000000000..2657d8c39328bfc80751c60db9d3ab4341c0de35
GIT binary patch
literal 1106
zcmV-Y1g-mtP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s00000
z003rd(Nc{WHi*Gol_FY@Wn`8)38~@|tKwvp&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPGu5^~NHbTAWN5AP#zUE86=}y7vTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q^_*?AyJGyuIvu(eH!N?S#?pz`^c>
z)9|d;ZHv_Ik=5>!)$gv_amL5*l-Tf>+3~XAcCFUw%+2wj+wtexE9lxP&Cc@b+bZna
zD(>4V@7yZ!+$!?iD)ihcx!dfw=Y00vDy`r1`Q0h{-6_=6^RD3Y``#)1-YNdxDZbwA
zx$A$x-|oWT?z`@S?c-qY<6p($@9^Ybyzqqb<X`jTU&rI{$m8(y<zMvWU&`a~_2pmp
z<zM*aU(4k2-rn~3=3n~eU(Mz6{N`W&=3mk0^3&+^^6H1w>GRa-^z`e8*6H;2>xcO3
zhu7-$`s|1O?1ujAhT-q`mEcQQ00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94
zoEQKA0&7V`K~zY`?Ud_N(?Ar(NfQ+@uBZsUQR*A(3k7^uiA_xlwb7<XX$T2Wl46zw
z3Z<omf1TZggd}Y;GR`=D;C$H3?B=&;&b@cGr{_ffM12%iU&sC(Yu~pzN7RRBuO_}z
z9SHSg=<4;C)SHp}k3Ldg#wI3zy8lp*uid=+whJ=U+k5r~HGFAc;3+jWJaTW6`U{t^
zEElbP-|8I2KEHnd^>^&(-vmu3&<E7Y<#G+wL{Y4Rx+F<;;36;16PJH@5PE-~#z=W;
ziYWt;VNw#1SeBJN2=S2cA-lUa!Z3^o#8j#a5_H+w@gP!)Wi2G_L4sKv#G9fn%W}g-
z`eRt9$y)*BIl%I*K9?(GoOF@xZQI4ZUy^0CDl3(yC(CW(0vV5!j_A!z8h-}~mT*$6
za2%)l5JX@-7#$sd_le%vpcj!ymO#PfbULko3dd#C2p5DnE;2^GRe;K6G8zcaf)Jd=
zPSH>*C`D7%v}T}UXUKCdvcCQ&74!AQMsP2b2D)EWk&C8PTM^wqM7$}qY%Zrq%-GtR
zg(zuUST%!@YA%<95iYB%7Gn28&1ADxp!<=IEX&Il;!V+l5Vj&_Y-#D(Gq$jB=z@FE
z)OFo<X@_?I6h_E^1__=pLT79oBQrC<=I0lWMDj$zh?ucbsf$a&>(Y@0AyyV$hfa#N
zVHoX*8Jn9s_Kewxb3cRzb}`Mpu<oOWZ+aR(egNk4c?ck1K*3ExD4Jn2MsLj~le1g2
zh42s<1wlYavFJkrE~R9$WV`UZ0SrM9%pelTlE5>D9%WyOJ=2@Tu2_G^GagZ~6a9ZW
Y0Mz-{B6#`Cr~m)}07*qoM6N<$f){{JGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_index.png b/web/pgadmin/misc/static/explain/img/ex_bmp_index.png
new file mode 100644
index 0000000000000000000000000000000000000000..23b9733b56792533e4db25ca9890a0a0140c6ff9
GIT binary patch
literal 1172
zcmV;F1Z(?=P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004@P)t-s000{R
z003rd(Nc{WHi*Gol_FY@Wn`8)45{LDla^$a&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPPu{MmZbjzYxL%irozv)iC=1#%sTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q|V#etORA+r5d~zJR>F?0eDg-NAx`
z(d~rM?%l(Kz`^c>)9|d;ZQjL&i`4Gl$A^*C?%>FXlGX37+HuCm@8HUbl-Tg$%!-%U
z@#4;m<IJA2;C8Ln>CDaX<<6kv(T?ZZE6vXG=Fy>}-SXtrk>=B+^4u!t)TFuF?6>E9
zt>5$1)bp<3^XA!==+~zI-YMzWr@r3px$A%D+?T-L?&;d7!r<=c-k9s!x9Z%g?c-qY
z<6p($@9N#Gyzqqc<X`FGo9x}X$K&wG<M7Jk@a*8N%jEIi-uC(CU(Mz6?BlTi=3nmO
zz3t_((dY8+=Caf1^Y7)q^6H1w>GRa-^w#P0@aMtr>9p7C_3`P${_KYG?Z)Bn_g>Mo
zbpQYW0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_00N9jL_t(Y$L*BsQxZ`a
zhh26nvhB^vO1oLo-JmFv5|N5XBA|_nRxB8D7XcMR7F@v#0{+$QVK1_a?Bq1l^uhPT
z*_pHZ%x|7^ezOAuC-YBckU%rovwx?vFI&Y|#D{0EroRzA2=QcObo3?hX8iu6kHpu>
z>6stWH^k%XH}AeZ0vXY2wKs^dOT)uYiOI3?do#per1Wz++u7&Wi~K6S(tLjX{>v}T
z;kSB{)N>E0r_<>=v>S~^8`><(wn0K(oX0MI??T9f0}>%=uh*M~Mn0c!0Gmiet6d28
z5R)PM`wD~wHX4nVRZ@0$Wk@2yLNyu+bs<U@5fNNE7R_?GyeA<;8Z@WzTMFbFpyAi&
z{3=ViitBI*+1Zh$RI5#B7K_EbE|=Tb1ze}Y#UZ!0Nc6mdd9k!$QS|wISsB6+XdX;V
zOuhR=Zf=sx+~8h})31g?q2dvUJcUEVlnj&w#N$ape-{qnT{4-vU{TAaQZ>biox#sZ
z$i~K>oS5uhcm(RXT&@m##cZ|)wNxxtQMr8q$pr#|oL~`iI2^P}$JW;+qy$+HkJ#<B
zO3K=rgfNiu%+AIjVz=9ZDji#0?I5jEsnmj63|UlYnl7kxY-Q!x9a~xwQW&NMl}LoH
zKp`0P7y91*DTMI1AI2EY!p2zyEfzD?w_{TXnV<i;jEAu>1GLR%4T9fnRv_|@#p7Km
zwAE@sh{;5$bc{nfE(~(vEeaGxB~?1MxOnW2@ran>r>FuX-EMcX-|cfhzPUN{^8+Rv
z=Ja_Bx6xp5_UjnAz2I^!Y?C5FnM@`(xD9edkrH>g;)f}e$!P3B6fSzyF}>u%TO^%M
mr}D&xdVb?7Cw4Ob-~0w&gyX$CR{j?N0000<MNUMnLSTYde~VB6

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_or.png b/web/pgadmin/misc/static/explain/img/ex_bmp_or.png
new file mode 100644
index 0000000000000000000000000000000000000000..c22fc31eee32e53abf634278f9d57751181f15c0
GIT binary patch
literal 685
zcmV;e0#f~nP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700030P)t-s00000
z003B6SY~U{Hi*GwmC%ijzrl|-kCNSbo~fFenwy-&xu<eYzUI5CbGxi`yRCLw!RNfN
zcfGNBtE}k0vU-WR+hoP)v$X2Ey1H@8?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?cKtI
zz`^c|)b8KLhTq7ClGX3V$M4|EiImvz<IA4o%%0@Up5oAr<j$YW&GF67^3l=J<<Oz#
z(W0i_^5xc(=G3I;)upZ9^VHPy=+~y`*Qe^(w&~ia>DsC3+^Xu_s_Wah>)x#F-MQ@G
zt?b~g?cclJ-uCR`uk7Qn@8rMa<mB+?!0_k6@$1Cw?CkIF@AUNa|NsAqxAFG?0004W
zQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkPM@d9MR7l6|)NN0KU=#*mj$o+0
zXGv;SRwxw`6%>^SK@x>8z5V~+lCW40^I>$hY<=+l#CCS=?A!_rGtXZp&xOfP4=T~1
zLLuDg&VhK#Q3h9{B+&*8S6f~eBpML~p(b&^vn6@U$0T2m#b{8Z5cd4&_~MEE7O~-9
zf*=_4G_tn|`*$&UE0u;Z3AUi@Ws_kpcNvpsxCSJ7EW-w!ByJ(e*z+DnG*V#06sAdo
z57R(x899zKpx?3pi_}}3HCVOj1h#=r;0$csmirZ0vT%(JY|HXz-k5KiT_1Ogc>-+%
z*I2g=Ed#gZrj<t0Z!rv`Kl8@=x~{vp_eDR1riLU<*hLa;LR4I1uBNJPc4P0=>MO1>
z^3%t=s-pBVfBn$JPrOoxdMEQgMkXTi54I4blS&e|kfbNeaxb$nGU<)Y^N;cg@O4qi
TF88uo00000NkvXXu0mjfLbGwY

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png b/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..e99f57478842f61cf0582433b659d37f0c532d5c
GIT binary patch
literal 334
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_@3?!2S6O@1yXMj(LE06{PXIHn!e64$cEXI-`
zzhDN3XE)M-9L@rd$YLPv0mg18v+aP48c!F;5RLO&CtC9zP~dS+JaMn}cL58V&ewnH
znS#1o7niJ>^>*TX*`QzgMdD7POzPaTo+w<9uwpLL)1F%W#!IqdhoMSxhwp~0@cf02
zhkMjmROfZc`EZ)w`S;|i<*e@Qr8gQ^@9R11|6Rc8|AQ9IRLjSmUOFeWtM)%%ZozPh
zRpl$&!*g?h?ocgpjVMV;EJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0
rRpn4L<mRVjrd2{T7+8WefK*!<m_an0njX3asDZ)L)z4*}Q$iB}@40B!

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_cte_scan.png b/web/pgadmin/misc/static/explain/img/ex_cte_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e1d779372f7fd212d2096e7701b0f1af05a5297
GIT binary patch
literal 1955
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_delete.png b/web/pgadmin/misc/static/explain/img/ex_delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca051cd5d01ee2f3ac17779b362999d82a9285b5
GIT binary patch
literal 1129
zcmV-v1eW`WP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003^P)t-sKLD^>
zTU+Qs2lh=8Hi*I5b0^MyED5RN(19;<mZ6f8lEIHQ*N;guwdHlD$Lf$-LA>b8qFCae
zX-dH9$EtPXqi$Wq>SM<0Ysctl$n0**>~+oT+Pi++y?@)iiQK+`+`oW((eK>Bf`rlT
z-NS=~)9~KJgx<x4-o}QE)b8KMhu_GEk=5?t$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc
z&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+mlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#
zr{>z0>DZ^~*{J8-m+9K6>e;sG+p6o@x9HxP>fNgB+_>rBn(N-I?A^NS->vN4y6oVs
z>f@a4-@EMLuI=Ev>g1j5;;-%DyzJw!?d7rW<i73ZvhL=y@8!Vo=D_dhv+wD(@aV$u
z>B8{rw(;x4^6ka*?Z)%)$Yy(c-~a#s0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkV
znw%H_00Om1L_t(Y$L*8bdl^v_#f8bG-PLL%nhMc~q#`mQ2qBD7)FomPgc62MQMdg6
zk3DnFFeXNthkjrCko~Zp{O0Vn_M*|e%s-g7lsHRO4WY~igJFl-+ccRDsDn>t^C@+@
zx4(Z*ogcZ~Uny0@u)e--pf-`3sE>NR-c0Qve4jen-Zs0bzx2v0yz^33L*QXJ96n67
z-|vrMEC|8~Mm*0Cfcyr)IFAhP?`wTY`?t5ZJrIhyTpRdgGF}BjOnw=S3Z#aS8bOKz
z2)SI2hh{Q9MZQla-3-V`zlMK|wO1@6q@DYg<e?sq#VQh#$r!5>kiFpjXD)tV7tH|-
zs+Ce#@?tf9p@%c(J&3b~b$3yv?^<i0Wd?NOY>az?FosdP&5vMEuU5Oz<v6Z0{jh=o
zomQ*WZOAT~qKnHCa6!L6FYiCcNQh7@l3<f{sKzcYIN<z#Z+<Q&%wTg<1P9CGU@I5F
zAO{7nm(GGl$rxFfd0BMP$!D`Im_IsL#iN4e&4xICNNma=3fVzdXx0akb}J=xi%ub*
z??5Y^PBlQ;M@VO0uV|$Gl`d%HB7Jh~V&PGT0ag%FkH=9lh;qT8zW{lfrqhr*b~zs-
z5-3Jml9U9SrB6;$kZ?Mkj5>n$=YC2mx@aL6sYC-BEW?yl5z?^_b4}Y?mYDns#}ztj
z_&TfPMvVl^-a$DU3E!wMhWB5?9atqzKrRj$h&b%_kUD~kp+H#Yp(6@myWO?~!I3Hu
z=Fs5?L&$Ek1=NvFHk)oKyp$u6@HJeA0)gu%65GN}d$-s()mK`n1iAvzJBEpt;U*Lc
zp;rPv-<A4eq!>gQ2X<i-UHB{(%b7ZoaEZkr8pXlNh({e^(G{YSM(iRIu-Pm=I9ra~
vF;TQX(RmkF9*^hhiNtoF^RIlF|7m^$H0#Fus?kFU00000NkvXXu0mjfhwW63

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png b/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..acba49c4fbfd9b871444c9e8d528deda3a37dda4
GIT binary patch
literal 1607
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
FV1m**ONRge

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_gather_motion.png b/web/pgadmin/misc/static/explain/img/ex_gather_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_group.png b/web/pgadmin/misc/static/explain/img/ex_group.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d5de31bc129a9abcf4a73e19a48739139f66271
GIT binary patch
literal 1228
zcmV;-1T*`IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=(EA5f2CVejTwfiWOA0Fgt11U#9qOVHuUhw@a(tm>9z9h#qH&>
z?c}iR<FN7S!|>?B?BK2I-mL1~tMBB$bEG!w=AY@{n&{q`?&h=R*_GtekmAse>DsC4
z<DBHxlHtsY>Di~^&Wz~SrlQAS@94Ab;k>5IWyh*@@aDkh)THLqqwL+ecBwj^wTIx!
ziQdJ9-ou35!Gh+|q2I`d<<Fq3)@|IrfaJ}di@s3r=D_ROwzcJZ<IJAp%bvUMgW9`&
zd$dE9%UpG!OlO!lj)kjcYthWj@w~q5zQ66y&+^gH^1{OI!^Q8@)brKV^vKBY$;t7`
z%kkOT_1@n1*Vpyo;rG0~?5C;bsH*3`zwW@o?yaxsud(UH#qY<+@3pq;x47%Lx$Mi#
z@y^cjz`^d+)bz*4@RF6$yGLoX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0)t6JK~zY`?UZR(+CUVB(*d`NQfXa6^~z!}1Oy^sF^V-vQ6PniB6gWpu~n<K
z*4lmj>zxTpNrZFE={Y_9;C#4z;o;8n<_!#Ueg5^if#~TvJ0ZwowOV^nZ(o1^02;Vy
zu?(WYUYl(Q4Gr7vw@^pKIx;e1MSTv3<2Jf8Iy!1WJp{L*;juA`9sTu|@d=i7GIzB*
zcbx`ea%zfYVA|z!O*g08?e>7jJHz>4miG$*&>@5fk}cqxo11erraKf0N5CtJA`kxg
zXjIZ8>{QT6K3YgD<|aru>;+DY$NeBgqlu&uVVO20Mv+K3EONld<MRSY3yFkmKo;vr
z%VLRIaMZ#t5Q{)kmQw~~SwrptJzZxz;S5eXn_Jw|;E6=MKJf88zXAbCN~R#CDCt$u
z5zCqTb(dHyv_vdKafVpTQ;S3*D}%DOp38&oS{ZE#LxT<JgH2|$1qIUUxtyxol6lbB
z@+Vln5v*J&tfe8J%N0vHeP`xj-Nh5}&d_Jg`!|S1QnN@Ci&a%El?{n)noX>UfRE$&
zSy&MSDF7*1R><$?^J*E&rLBr_%XS^%;`MqgXclXlMUhx+RjbvC5!ul?)~H8Yuxhbb
zEkWfnGVy48hc@|gu%#mXatCCbFywBqAxIKQNmeuqm0E03i*lu6bQyolvg{M)DW<3E
zG`wPq%cPn3+4J4qJzU%0fAJC@93H(o#>acL+6g{6I5>Tc4doBgcOSnwJ9~SM-(6h1
z|A0S!`uyc9{&xO-zlMMOJUluzB3ffiAkFe$9ch*S>MpJF|I(7_Iy;r$L}JbIn@VhR
z$7Es@G5cI-#kS2oefq~wwp=~2>+`>z-(ue+J2o$;LI3~&C3HntbYx+4WjbSWWnpw>
z05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppn
qF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTX@`Go)g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash.png b/web/pgadmin/misc/static/explain/img/ex_hash.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f35c76538c3a41920a7b93b94bdf97443df7aa7
GIT binary patch
literal 1169
zcmV;C1aA9@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001n
zsbo@(8Fi+|Hi*HY#9nXB?oYw#V#exs&h4h&@<F}n38~^Nv*by<=84npY=q5bYthWj
z@xa0E#>el?&hp;g_M^vPyuIzKtmw0}>eSTprp#qVzv+I_?y1pfi`DRv*YI-8?0nAc
zpxg0|)b5kj@2}wVOTX!?)@^0T>qEQdP`>AE$LX%xagvqMa+aa8;C8`}HuUky@a(s=
z<azV%$M5K~?&h-b>%{Eiu<YTk?BK2N=fUgdp10_I@9DMZ-I(RolH}8n>fNlm?Sban
zmF(iL<I#@k+^W3rgyPSQ>Ds94;H~fF!0zF_pG#z^7Z=B>b%2f~b#^4RQ$FKkW936b
z-9bUQ>wu4ZIPc`Y=+~#`)}`#;yM|jA(>_1o$cW#@hUV0yu-$XX5E0BgJmJiW-o%9F
z(xSc&4#hh=;K_*H!-U<zg67eo%w1jR)~4*;x#rQL<<Oz)+qdxO!sgSY<j$b#*|yug
zf7-fz<IJAizk%e<p5x1&-i$@K00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0!T?jK~zY`?UU<U5<wWn@pV8EQt5^jN;fSkZ!Z^txZea>#3ciXMG^ue5lb*`
zBLBL(BnC@M_{lzaKFl*S`~03W=gh1~bf??Z7>o&i8z-E`2MDn+LnITE(kU`Ph=my<
zli!=3;UOHsGB<N!4<_g`tKc-t-v&Vskw|g>!NW(76$6NJ2r~EN>D;qx<f=ddDj?lC
zzfvATB(nK=DOmtDJSQ(+N?*MuQ;UmBZ^#ftCYQ_Kk_phjJ0f{M{b7O3OfD}k^&$OP
zSQlBxWkuL!wbR&Yce&O%`5J+BAl-Rq6m~(aPN&nN7W801!-mmlLX-KU#frjNP-nB*
zbf^P6IMC>HI?ZTyx!j(ABM$Jw28;N7n`m*n{ee-CEij_l=W|)m<M#(C9G=C&reF~^
z#ik`>#q9t^g?o`5n$u2q89~HPuuHQD{TVq$evdr}9gW5IqV%<k!Qp^7d|{C&ZS{Bp
z2@Eq#a=+Jwj-`OFxEFRI^_BMcgKP@sAf3+iBOq2Pl`555t<kJ}Ti5ID_PBQove_IR
z2<Gz)Cew$9g+65OuP&+V_amCh=Zi_q93GcW`jBWYcl6^=7L(b`W$A2&nV)Qm)AC8>
zY=p)*2{p#QmtU5UXB!g~#e{J`Q!Sk0S*=#954-)4+s{4^DqlQEV)eLGs$i|rXr2#4
zu8J*k#g<F0Rt+y2&1QQT(pzk$)oRr6yvav+SrQb(rKlFlofH>!thd|kzAU`IGSb`9
zXpM~`UY7p|xp@{|V|TiBT>>)4pzcQhBc}iW03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfr*~9W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2a4e9369fa78c6fa19bf5c7814f8a586aed5565
GIT binary patch
literal 1571
zcmV+;2Hg3HP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70008nP)t-s0001)
zym(TL8Hu^uHi*HY#9nXB?oGhyVaDrr&FrS$@<6=l6071Yv*bv;=84npbc@wyYthWj
z@xa0E#>el?&hp;g_M^vPyuIwJtmw0}>b$+})YS8(%Vb!b%R{~CN5APyzv+9??}XFv
zsL*Ja+3}#;@rKgwjMVOs)$Xp~^8}~i38~@_s^U<>>RiR^tJQ3E&Fq2D?Owy^WyR=g
z$LN&S@2=W$rKse3o~g3nc4CU7!H+id@yYP)xAgGH^YF;E<$Ck&#`5gN?dGxV<go1H
zu<`1{>*k;9<(}{Bw(se+xaofG=CkePvFF;B=Gm3z*p%$yuJG)(>g1j3<DKv7weRV)
zy6u7G*OTSelI!2C>f@a0;+*d1v%K(x<kgYn(~#=jtG|XZw}gPOUOuu)6SQGJ#E^t=
zX1Me3$M5K~^6ka(>BI2n!Rp<r=g_IQU>nIz8rM-G;Z!G=R8sZm;ipq<sTUX9y@`O1
zC4YD<y<0}QY$D=RDF6Te+NO%`W@hL_MC3w4-$6p*Vq@;+vbpPkl6^Vv<iF|Ls_ELP
z>Dj2iYBa-eL+3+8i(D9Kc^QRT7SldI;mwQT%ZlL2iL*-)v`iG?LPDa(U!=)mr_E-n
z(rT^OZ?N5Sv*LKR=X|^GgU%8X#}E+AJUr>yr{Kwm-^ho)XD{18K!jQr#Saj^4i3gU
zJLuP@-^YgD#f8m}Tijt`&pkcLJUqoaJIq~O@aV$q;H~T6t&4j%?cclR)THLqq~_A2
z<;tVAk8q1xa-mN-ut*Z8oXPO!!0Ozp?&H4g;k@V8rRUY8?B2TU-MQw`qUF({<<Ft(
z+PC7*jpELX;mnKa*r(^#rr*bh-o}RB#D(V3q1?cM+`oX^zJTP;pXb!1-ou35!h_tt
zfZM%)+q{0{&7S7dqus-U-NJ(0z=GPledEiX=hda<(4pnepzGPT<IJAw*tWHqeqb;p
zI{*Lx0d!JMQvg8b*k%9#00Cl4M?`-}zj5UN000SaNLh0L002k;002k;M#*bF0007X
zNkl<ZNXKJf7zG0g7?~Jx$}zKm05gUTR@{16*w{HZxmYlCu;SLk!p+0W$B(83qywmj
zG+TfU!)c3vAP5Mc3p3%ggMmR%NLWNvOb|nhI36t$l2Xz#a4paPWMPtJVu7&bFoG0K
z3k#dPf}#?OGLs6EDvO%BhNc##XVJBAYwPIhvFI}yFd4EK8Jn1zVFoFhU*NWw<F>^D
z9AXwQt|i#zmMlzGOe_$#HAax4*<xdBXJ=<`<G|#|<YeRQ;_BvZgAt@?ezCFh@bvVu
z@n-U2^0o2v4+sphK~;c7OR#51XqZhnQv{Qrjay)3R5W2NULi5D;Wlwh@l0+uK~V{b
zNy#axY3Ui6c>I#(9iE+I;|$c2Ym*n9lwVL-R9sSAR9c46QeFW9<uGogjg3teQ*{MU
zOGOQkZBtuRS65$OSJZ&eQqkDd+|pVBx5X*0jj5ff1E{67qO+^Jr?<Ecq^GzKNlX6(
zCUnpq!8DO+(&UQnDO0CSFR2IVDVc$!WhN8rEb-a0b5!OU%rl>FwZL(qPkY27ro~H^
zRxF#ge8tM*dXOFzTUM>cX3Od|Yu8n*U$J51rXrA6Q2nxb%hqk%5q{aRa~IGryTO8c
z_U_w%fankd0dR=f96WUR$l*gr@x;rqq~l#DY)-B}b^46W*>mSFTqK;vj$gWb<*LoK
z>o;!hx4Cuu&fR;26AUO|*W29R_~79!n+uN~KY2=63n*ak+dO;z;^hUKdyk*IdW}U3
znqN*{d$Skli(7BsU9@@s;lt~Xn0`Uam6hO7LMA_<Dqz4_j^P7>7)~85T1fYc1xA33
zf&m5qP2%gmFInOH0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5vZ
zFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-W
VFfhuORjdF2002ovPDHLkV1kgxQ(pi8

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..0051f996783873a4a27db4b971135e95cac8d744
GIT binary patch
literal 1452
zcmY+CSx}P)6on&<ilC9wDWY}j1EW==VG&s^MyMc>VE_dIWsf2%OOQ>VP^3a>#ezbi
zQ$WfpN-;oW2^b&=k^l*WB?JiB_w`TyKVY?Ov`?Km=bn3K?)P@ef&wq>vfO70gTZ$B
z`}qW$(($7$%uVxaUUo4IW>yn~4)ryiwS?^8pZAsxthkhpJ|kUEnO0vS%3>*s%xPsS
z2k<7yY&!WTdW3!iaT!ycQNDmlo}HT35l7XO3C;8jz~MlDOvDm~m7Oo7J-u?muxfr@
zpW4Z+8|Svr5=g76*J7xhadTK|9F-Zn1z?W=#EU?ha)Ykg7?VO|88jh>7B!m<YlhB+
z+ihIKOFme=tgB-K^{c?37$i!dE}kC8*Y^qZlX7DhJFTT%m|NeG{<NMuQHSGZ&NB!^
z;v%k3I!Km}R=5~+lUn8L%?)9nD0kB+*n|W|{W4?{z}f(LBJsvL7vc+yV)2Gr{Z1in
zFoNuUS%J~8tXEIz)ik||2I-d8IN-v9etFrj#(`wAO|24dG_VI%MS2w#P*Oqdl0sB3
z7yho1^%ArX0Ocg0m;^P88U<b>@6$+o03~@7;%^vuMm@JJ<aWoNJuAayg^%p>Gmnsy
zuG1*Xb4;BYgOU392vO(P&!Kp6@p=_SEyk&Y-2;lk%6vCay<jj&HE-0dOV0b0N7*Ii
z>@A6~WuE;;C#7g56s?$SP%?QaR1+n-c`~kT8uNVa*2|@o?lo)=KN~N}8`2cd*w{>1
zTTgj<YDFZyg1O@D{UE}YyxV#N4ySl{7?kr`5m_T5X@ui?`P^!3EZxhC>gh@L@R+)G
zO(PhQvf3nzE#>#mKw1u{SyOTb#j{N!#xvnegJKyEY1bgl8X%hmqzq8Kx;j$0LMUJl
z<qK%_8X-v|7+3Q~)m)Q^fP}#wDqs)hYxrZTbs`|9t4#~%jZRFLv+$sL4Uo=)sui7h
z#-L;~yHiyh($Tl5Qd2dzWCRC>z`VsQjOo~s>UR}uO21G3b~5>_sl;f1pL3yp6Y(*2
zTg|QPYVFdf4n2i1ryt-~R&&EyEH~GIaAZa|e{p<ldYm><(Ym$Sy#1kbWFdS!sl4#8
zg+-&46R)?p=v<IrxKAkB`h2<TL4-|Z@SI~R;xX*W)A-68l_8JKw;U~tw3<);5q%L&
zO+WVhmV94&T?E^GFg?51+bpQUzhTmQhVhm~3Z0o+9h<ZCwR#@S#fJ;<<Zb)oT##J|
z)~QSg4M#(dpu=3@PP3E!chc@*v$C?VU<{A|;Ea$C)Mvphc3Q&ID5^bSMg}7&YYEw!
z+G=rs{cXF{PQvQnc;JJIsEn$r4gNJx`JpL<IafAY`AhBA`O0bGjYCz{Rlhy-wHk~D
ztSrl;%8Mf2s{-cqz(t1-!B^mBiO9Ppot??{A(!2P0&-5fl5P>dx>DZULg*TK_Nrt5
z-2iOB@sKY=Jc0s3a*{6G2s_h?5P1!wrHzP^j*5=&P&pEke^pz|yOz9TpDUboGQjBx
zMo!1otB@1eyV;Rc&(=*V+v==K^@N)awupf0xSm8APIR0a))g^~LD(V`iz%%B9lV<9
zNW9qx+^!S-`8sx=M=9LFn)pSWRENVZslD!B8wv}&N}w0~Q)Iq<_h*6eL%R>T<xbtB
zHVKX_vJbiD$PIhPc3|ksSjnS-lh?8Q_gnrdZaWp#?-x#cg*}W&rboNyF#et@q1F6p
z9T*>0alj*U0b~~`wx_Vx`_n~Y1o};*=weXmz#wPa!-WHU)K7MW;+k{KB^hrTOISzk
zQem*}7uv?l*3EgQg4t*IhG!(iW+b}Drze^M=7@AaA{-nMNQY2Iq`R||yR-9g<Y{*#
t(zVw9;QxR-DG5oKoc{o%+b7`Tw>#41KY&_u`GpC9`TGX?bfIFt{})E(L^%Ke

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png
new file mode 100644
index 0000000000000000000000000000000000000000..76c546a4dadd7fdae310dafcec2ecdef9649d4c5
GIT binary patch
literal 1377
zcmV-n1)lneP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006>P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZJydFPPaY
zjqWG6vC4&kzm|uj!nL^a^ZV4*@~N!lj*;EL!|c+nY?{_9i1sFWzH5(?-O$nT?A*cg
z?Z)orvhC!s?BcKR=fQ#6N$usa=Gm3y)|2GZkm}s3cd&fiz>e(VuH@B`<I<1f&W!2U
zr|sXy;?RxZ&5Pj5iRaa&`u6Vc<G$$Fr{~tC?A^KS-n!+`q2$h=>)E#4zJS}je%iZz
z<IA4fyMEfbeQRKz9{>OV0d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U00K`*
zL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U6fJ!G0)j%qd?aZR
z5fu}c7>rs-_lqPS2uKb<h(XgGuGEK33lp=Hw2Um1oV<df5)(cx$|@?VY7i|P>Kd9_
zOxilSdivP3!2P0PU|<N*L%<e9h$*WW0rh~yjKRJz!Ik=uLrmGoKm|mbnweWzTAAVs
zOBC;`SVOd!+t}LKo8n3rs9GE#S{$96U0jLO;s(*;?&0a>jav({i+z0kltEhjT>}Dx
zaQX!$jRpIKgaY-1nSww#aj8!^Gy>=})5xgkm{?PyQeSXrL>$l-)A)qMq-30aK}mc*
zDRE%Gq^6~3Wa89<QYfipK|(A$CpRx2w-)50Po)5&rLZW!xCEzPP;Ds%YatL~kR)gf
z39qtp6+&Uk08WDD=4PfK4Ju{j6{^H&F|VwwG&cpi3aCSkSS=P1TU3bB0n$=sQ*DYQ
zQd3)3UtibI*wozI)Y6L8FP3fY?MPZWI=i}idV2f%Crq3;Y4Vh*m|7qK8ivp@ZTgHE
zGiJ`3J!e`6kUM)8rXJI|^X4yD2)CuC6QpC&l-Udnb2@84dZu8w&2;gSrOTGXwbXV2
zb<9{X8N^;$3nC}CU}#yjdd=E(a4mJ+AT3RkK<xE(AaYU@hL*q$8#ir+YpL%6X=$Da
zVsEJjkrSISv~1nBJ$na9TF^p_D2MG#-nDxVY2M$pci;X4qy_TsgNF_wv=9}Q2M(cV
zAtJ$;mK->6poFAkiJr!&3jm~}-@X0I9(w=)03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfh#BXQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..ba24ed16aeb0d93bd133902b0b2218e674f84528
GIT binary patch
literal 1402
zcmV-=1%>*FP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006^P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YN)&~
znAt0h?kBgg%7uZymWQOmwYc*0`_$F)sjTIWk=?<=?9#1ln$|0b_9lA1YmbuM(9!Yi
z+`;qh#_r~_?c}iR;;-=M!GYRI?d7rN*_GwiljPHo>fEY#uzcLWj_l&D<kgYm(vRZK
zjOo~??cc`Y(2e2Ei{Q$M=hdb9_U`ZFzUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyHkc~n00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~
z0%b`=K~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4
zBxw;56%&^jj9OrRk(88VkYoTMLO_ZS1f)P}iPb`eEpWepZ2==#y2F+Fuz8<}Sz1O`
zj!9lYQAwEzpB5EWRW)^p77h(fEo~+pT|IpRY+B&4uWD#$1kppl7I=sOZ7~Mw0g0J_
zePN0#^&zJ*6=OqH5N&2|VQFP;hAZ`<cwf~9qQ%12&fdWcSGqve;t0{=<m}?=Mx+*Z
zh!zh|FK-{*T994r>*ucm(h}en7!-`tFNhEWf)M}EFrc1rGZ2VCmcb2F!XklAGmDCj
ziH$QOD!+z=MaBbdF-u5HN>0J)7nH>3n;H-HOImtHW)@B@D20-0HYCJya`W;FaBD#>
z`cw-cT8fGbN=kA11=W@^uoeO#21$Y@knk$6P$d+m4B#YaVPS3t(x6&iQK?3Z7K^H?
zDho5PtAIMxiPd5Wu|<_A9Uv{$wl!u*BDHn(4Gr~;P0cMW&8=-%{bJSb(Sf9;v#YzO
zx3{l<!o*3FCQq3<4O0swK*JF_rq7r;bLOnsbLLL(1ajxh#?)grZ~lUXi{Q4@c7b#(
zo;rttVQyC~NY7LZx0x+jx@`FhxR$zZppKa<r-0b2>Oka_Rtznx*Q{N)9<HUn2c)HW
zGKjsQ9z;%V#?TV9ant54a4ikJAT2GEK<upzAaYU*hL&yHcjW9ONefzt5#_L5DZBUV
zCC&T0_w7G$khDPFbLjA4gchQr^59_<Ekq<3v(keH50;XYEYZ^#bpZet8{@o}32r3-
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f<D*lWB>pF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb536b11b68fca6c2feda14a8011003678204d62
GIT binary patch
literal 1389
zcmV-z1(N!SP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006~P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZLlAj_u#Z
z`u6U(vC4OVr-z4&!nL^a^ZV4*@~N!lj*;EL!|c+nZ0y{@kCNSe%Uhb(E6~yL^X<m&
z=CbYNu<YWm@aMsZ_9pG+vF6#8<<^tr(~#=is&%M_sJt)i;;!V?k>k>j;?9ie*r$Ha
zR+!l<;?RxZ&5Pj5iRaa&h3hnp?kDf#zUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyg@M18hosTq4gLTC00DGTPE!Ct=GbNc000SaNLh0L002k;002k;
zM#*bF0006~Nkl<ZNXKJfAR90;fdC^ZT9}wwSlQT_NYcW=$;Hh>nikS+;RXAJmlQ30
z`~reP!h9rY5fK#=ml%v%NcW2*9|%YeK!`!p9j?@eO$!sVl(dX2lbpPQq7oB6Ey^k?
zs%j7|9O@dHT1?tHx_bK9w7~tMVqjnh(L=x%M2IP?7y<Qw#EikdFu|4jkV8z_$Up@|
zo0^$hSX!Cl3QH94t5`#{nA_Oe*_+}@7pPhsAX*%qoLyXr)Zzxw;_l(;<&9elvWtCu
z{ggpk{9OYAgK+u<C5;99g@gk2gqeasIB}^@IWz+3G}Fka=$KejqEcUQXha;)7Ss5I
z#H3`LenClmJ}Gfvzoe$6XJq2kf>J1{WI;kKJ0~|UAGa3dqEDp&qNT7XzqkaaUr=o+
z1#2M?Vvr<g3<<BYauq^h$^cG+=H_OmAPp*I<rS*LXfdy>tTZ<Ty9%g7jaV%f5L;A;
z(gD&^Wm9d6BvMmbS6^S((Ad=6+|<&F)i0K9?(IlgIy$?$dwP2N`X@}BIBD{fshC<I
z0UCzTF>N}KoH}FXtZ5xU#_SoGdQ9icoi~30+?JY7kdD?VGZ!wL)ma15GX=wKri&IY
zS-K3arM3%1wk%%=1S@Jm?8z+{T2`)Fy=E<3OI<gJY+47>vc3+)p45b)C2+&WO`G9b
z>U%(B^A?bnt@R-G#AXaF+qUn>-bs=cv=Af8VY`xd@7YV5_jm8xf8Zc#fxPF?;ll_m
zL`CJn!zfx{3FgRAoC(IX<lw=BB_t(F^fX3Y007N*+;lfqX*B=<03~!qSaf7zbY(hY
za%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0
vW_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfuI2JN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..0018157f64a5ae601a2db08fb1e4a553385c33b9
GIT binary patch
literal 1417
zcmV;41$O$0P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700072P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YTUq%
z?cc`w_U^Z_%6EXLhlh*8wYc*0`_$F)sjTIWk=?<=?9#1l?A*bRlHGmFTbkA@(9!Yp
z?Z)orvhC!s?BcKR=fQ~fChg_1=Gm3y)|2GZkm}s3b*P4@yf5tHuH@B`<I<1f&W!2U
zr+&{?nAt1h(2e2Ei{Q$M=hdZ!>okq-C-39F=-8*{)}`#-x$NG$<<Ozz&Y$bqw%ope
z+q{0-yM5!!p4z*9+PZy(fxniAqyuE0`2YX_0d!JMQvg8b*k%9#010qNS#tmY07w7;
z07w8v$!k6U00L`CL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U
z6fJ!G0)j%qd?aZR5fu}c7>rtAevy=vWRPS4Awock4+Nw@YKhfChAnWvfNcRISh~ZN
z`mlMQiCJ1kR*p$tK~YJW37-}fRaG^0h!zeFO)YIE9bG+r18iF0v9D@qXavziz!rFj
z0c|k`>H&$FfPG<#EA=6#F%@G&RS<1vZeeL<ZH6oLp?F`_2BO8n*3RC+3|G29)#3=z
z;^ge&>PDm%cZe1bPcLsD+**)b?Ca;R0@4!T78n$a(=Uh+1A-9$&@iB$a5E5yK$gJ`
zRKg;GPBV*&j){#kBPzd!ghj>!Z81woOiE6{=@*p5=bIW2_DfoNMrIaHEhvSOYBnUq
za&q(X3vg>eF8WjpAzF%x3rb3H`UTaNGO!i`AqGi;CXnzduTUivrVQXDXklS)2GXEf
zUQww=j24TkswxXJu&aPN)QQz%39&_$C><az)wVTeNFud$^$iX6jZMuhEzPZMSp8zv
z?$LpyrL(KMr?<DSf5OB`lO|7@It^0`BtXLvI;PJ6lGA3+nmxS}$e1$|Q;*r)dGi-6
zgxgZv1=7(rb=IOqv%6|RdZuEy&1~_KrOTGXwbXTk$kr8$fM8`Eh&`nhL(8hwYu2uV
zYpL%6k<IHtS~k>!*pr(vv;=M3w0R3$OG7V+Y}pFZvaJEcp45V&W&4huIlD;Gf)-*#
zIc#^zp1u39dcPZ&_xJ2SaPSamfxP$dks}B#L`CJHBPd#63FhcAoC(IP^w6P0r6eUw
z^fX3Y000U{;7!~3Lt6j<03~!qSaf7zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4Fh
zG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bN
XH99ab%9mBF00000NkvXXu0mjfCZzUl

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a78fa6a1d35d8b2666ee3ffa5583db9662a67e4
GIT binary patch
literal 1490
zcmV;@1ugoCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70007fP)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j_U_>5)wA5djnuJo)yKTSzu&2=<&>D;#mDaG==ksIwesx6?A^lL!H?z8tMcd6
z`}p$9v%Rr~oVtpQ(zUVm_Wa-A^}oUFs;%YG((&EGk@4lw_t?(XP%76v4Bcit{POSI
z-Sn%j=JV~w?&h-X<go1Gukh!=<GQ)mQY+$cP1;Eu-)lwf<+0}3mF3ox<kOJq+^Wd6
z(8ivy+*T~?;;!V?k>k>j;?9ie*r(rPJ=a7P;?RxZ&5Pj5iRaa&@3EfMdPDEyzUbJe
z=hmg{-MRG3zUrWd?4*wR<=^byy5-QJ<j$Y#*|yxifZM!&+Pi(@%bwc1e%iWy%Ued<
zo^8}&Lerv`vZEDM00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~0*y&T
zK~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4Bxw;5
z6%&^jj9N(dizFWiNDe@VLDL<s)Q3$A6SI`Gj4YF!yn><<6Fx1<Dk`dK5G@?)8k$;6
z+B&*=`q;F<{i0%EU<lDez!pS^DXSO(^?<~T!M-rTmHLoFOxegl1w@;gnOj&|nc@md
z6z{88L$sLN*xK2f;z}2&S{xu+9G#q9T#3};2GQc~;pyd#TMM#_eSH0tL0bG>0|J9^
z`UNG81^b1B0`-KMf<QQNsZTjH0_Zf;$f)R;SW}`>UvOwd9MBfi_=LoyWSo9MNqjyj
zabUlsrln_O;?#mtD5+#YLM%HcH!mNz7UZH&r2wL(uqeN{1gBq6Z7Bt7ArNAaBxno?
zud;F#LSf1PPJ-s<W~LwwDrMyrs>En9udJ*zHwC*2s6&ldEfx@4REW|6(o$tpZHgpP
zQ(ISGU)Rvs)ZE<E(u&nDmTm6sNLo5NySjUNdi(k(Oq@7r@|3BVS|9-$hR`u>Is?Ou
znX`ak_8bO=x$`jfn9g6YaM5D8Ej67$9SlomEnT*J)~ppk=G-Y5ZZloEYW146a4of6
zAadP$28KBsX3YYzC%0f|*|=%*maT9tb=@GcX%djwHfuYGJ*f#pOW=;3yLQ91)c1hM
z=7|gpduGkr3t~@f#?Z2F|AFj-7+MZ(n>FijJ$5Z<AqI2Uky*2jcGux>*s<i}Cr+Yy
zzhyE|3qw~e4(}g7b^6R%v_Nj1GM9m2dS?xeKt6Hq{CR{HNK`URy)bLm#SR7>QF-<}
ziWX>sxio9mWn2lywB+pBvn3=YOY}5GT>t<le%ij3CAg3P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f^-`z-2eap

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7764b74f5e5e5af1a205b2ecee9ed184727e1fe0
GIT binary patch
literal 498
zcmV<O0S*3%P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70001NP)t-s|NsAV
zbaXa|!Dg1x!H+h2o~cd1>4~}9VaDrk&F*&1?7h9crNQLey@{>X>89TD^V}-D-R=M0
zDZt<E!r<=3;qS-e@W|uv%H#0M<nj6DU(Mz6|K?xO=kn9&^VI3|*6H>3>xcgAhT-q`
z!N@d;00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0Ut?3K~zY`?bO>2
zf-npQ;7O;V0?Nb_&J(=<J35L^UDqjzCdSySWyu#<`)@-0>*s42VGTQwXf%l9@iclL
zNdOXeuiySypCM4!;O@y&X{wSOIUXq~XQs-}6QvYS4hKrA@L4=0l~^f9_IpA^Zz9B`
z$BhkkJMf8s@sL*VhLWpb4Q#i-2+rIQ+9-eP?noQ0lYBNH*ld8!i7ZVDly=E_-I=Ub
z0iwNRxdislWu;A#vn&>!J9Z<@Ox#J(okv2`=*=e-1+Z!K=krEr8ku~;d=B=YX>j2X
z3`^5Z>~y=h^w%!z!<XmH<A-BxricT_g}zMDF0MB}&$8Uyetc9c(E*Y`k!1ybMZHLj
oF&RsOM{v$!pYbjo``5SC8S!5pi4Z+;&j0`b07*qoM6N<$g5#I=_5c6?

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..d44eff429fa9a3409776ea88a11421582f6f4598
GIT binary patch
literal 1298
zcmV+t1?~EYP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001q
zx!X2~!Eep(M7`;C&hL59?^VL-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUOPl=ya2odY-AJ!Q^I^(ZP>4@#=@|<6!gf$nfm8
z@9DMj?Zxiqv+?W1?BlTT>B8af_vhLx>g1j3<DBW>n(yed=-!#<+?Va;vF6#7<<^tz
z;H}o__3YxW=GvC*;jZM>lI!2C?dG!U-mLKEz|`sV%c5A@y@~4Gs_*2!)9CZ$)RE)T
zkK)jc>DsB$=kny!kmAma@8e(T+p6Kris{*?_Uni3;k?b|^7G_h=+~#{*QV{@yYAa6
z^V}-n$%)^{h~LMD=G3Ii<nj6DU*5)s%H#0o)urasqsZg%{_KYD<-qLSy2s=2#o_P&
z=3ngIy5`cN=Fy_&(V^_zxc}ZM-o%96!-L$xg5}Vm+`fRq;O^wkpX=JUz~AoV&7SJn
zw!Gc#+Pi+^%$}{*>Ep|u5BdaA00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0?A24K~zY`?UdPD5>Xh3ZKAN;Q_C#*KwubLStTT}1_fLIkws7h8A(MzWmN>>
zU-!(C!g1n>o1QNGE)F-m&yV-_9Zp~0%lzwkMX>Y7Qw$+qOC*vv#9OIU+E4Vqdp|Hp
z3=Rzsj}RlHV`Co(QA9F6K0ZNAOioUIBtA_|O$`u3pQX}aVsv_XV2tRNGV%8*U%21Q
zpI@J!9p9ai$!5?8$Q25O9A=eD<t)sqR4O&7Ns`onhN37fbRe?1IT?#6=I7@Xpj=p3
zRDo(~X=xcqonCJM%4js1F=S<>16kw|9SNjfucyFbG<8Q*i!8FN(;>v5|7N7XYFb^j
zi6er`Z;S7og+&Wmv(4_nkhL`y`2p<k=g;S-7$T&omq}6$8iRq-fYxd@JHSEHv=d8N
zUvG1Hvaqlg8+M>wZnp<RHaFW4Qm5mKHCn;Ey0JwA?RIZ>L3V^<DXv)CmdizhcYE9C
z7f0lZot<3~3#Z5L4`9gNUeAs32*`uMAWESuRxLPecC-tp*W+<QAQTEmL{lKRzaI@^
z7CvMViX9xrG34k-K%&tED%OOq2kmmXz2Ncrd?5%Q942w(_?SncEW#FR7mD@!VzDr!
zlF1~4AtxssvYSXG(yRsji?awIi*PCekvPMgV#wK9&yAg*b1CUWCX;4&;YGXf`$GYU
zq~h@lIL+qrmmrE{uCA^!xWzRCxk9m6>W1*wBNU4rq#&NWE@UBJES7PkQfWgTi$%L&
zt_y`cl#1nYRUAoYSeFiqTrPhDH>GOz219Oddv1(Jgp@!yg?4elFxgYMER{+qrCO`q
zwWpwmuU=;#Kf#5+9!!C=D6<x|Mx$}xj>y*P&1N0_`voL*$X<^uSL_qg>P)FLo0X@O
zINC)Pazz%YmdlMA+~Wvh&>fq3bh%)%n8jVI)w=6!OnAn7Y3ybGuXzBr6b33x=w6}#
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f@j>KSpWb4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_insert.png b/web/pgadmin/misc/static/explain/img/ex_insert.png
new file mode 100644
index 0000000000000000000000000000000000000000..862d837277c99e17d2b66232de79fce010752e7c
GIT binary patch
literal 1065
zcmV+^1lIeBP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003yP)t-sBmgK|
zTU$1W!3nA2a+aZzl9IuXHmaF8ou4@}wdHlD$3eX4%c58k;}QztMoPfx$EtN*#Oh<l
z>TAd7XvplT#d&Va>__Y%vc!A9!GLwm?AyJ6+r5e0zJT1nfP2yJ+`)o`(eB;DgM`!Y
z-o%97#f9F+hK$ti-^YjF$cT~E?%>IZ;mnGc+417ejN;IZ<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp;FQ>)N;I-kIv%s_fjj>EN2{-mL81y6fMq?B2TU;H~Q8obBJc?BTBM;JfPNo$TVT
z?cu!a<FM`JvG3%*?dG!X=CkkR!0_h4@94Ad>9z3a!tm+B@a(qn>%{Wy#q;gP^YF+M
z!Lnrl0004WQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkQhe<?1R7l6|m0NSt
zP!vXK&`lBB2CJaOTA)a|q(Gri2tkO1TjWv@3IQV^xBmZM;7QKm5)32GIHM2U59@)+
zO7{1iGlRkKH2+{;P|ED2o<i9y7RwoRabmSzQkSo7wrlG8!sWW5Zmt4>Pn51=F&d2)
z>IBhBy)v84HtG!NF6!#^)E1!r(pR3TJMW~P0*}+_^l{1Ycsz%>EXy;PNs=@H{0soS
zsZ1XpT4Ta-wOSp4TqzX#AZ4@ZCSYUngQhh~OI2D}smLG~3Iz$e+4NHRx>yYJuvW$`
z^<kBxN(CwX!so06?Q|;DAWA+@Hy5zZqVB&+@l8$9oxr5os0~RL2mO(muAG;k%==v=
z?{&03tR0w-WO+_F>-VJ@Oxn%n5Qd^C4wf(0IAs5IimsC4t_ET>94@Z+-*8Z+6;vrE
zDmL4OX6o)%1Th>A6*p>99W@-O6jM@*ZQMqqB9uZQMgW7>p*i#m+5guRgQ6hxVDr$y
zp&Tmg*VxiQ7K(#Tq1&Di4jVOe7K2i;IDj6{=Q^OhkHF`j9~j8~+7t{*!3X_54z}(v
zzyVU)=`^|+m|`><E<urD7#?zdpLbg!qEcy%5Em;j!C($DUayzcSJ3|AcS0pa52eUu
zI?&-*wyvxEL1XK$h+=6#I1mKB2b~PZ-9tT@h=0*v4DY{{!oxzs?R3WU6?`!;`C!nW
z0nY1jCn4^1p6^g_rK@7mS+E|rI}S1D`FTWN!TW1cBogs5lw*-d`L~U|Gby8??pJ!b
z3_1msrXytzkCh?RRq(}7X*#mo?MOj9Ce&3jKILMl+*jeyDI_uXRqokjvW~fDmd(cA
j94hm6lTUy1|Lgn&xg@`Em^A9100000NkvXXu0mjfS$;eB

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_join.png b/web/pgadmin/misc/static/explain/img/ex_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c391233c449bdc5a0d52d50f522bd3e35c649c36
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_7<@W^2*R&GEgz?Z3h8!ou#;)brTc^v=)n(b4kM)b!xs_i~n@
zTZg#3z3iu{=c=sduCM9E#_!9_@xH(9#m4Z~*7U)THuUky@a(t1!S2S#@6FEg-rn}>
z=AWyq=(DuyyuIzz)bo;+(T1sHQjHmZrCYPXrlG`Mg|bG+s&$Tqt8$|?qQ_wH?6#)N
zW$Wdh>g1j8>9p_bw(sb(^6bTgsA6`iJD#<NuG(>Uu0Heb#_r~_?d7rT;;-=M!L{Ui
z=GvC!*OTPakm}s3d$U61)sf@UkK)gb>DZ@@!Bgwrtm4p);mwQS%8BRHr0(Lq=+~y~
z-MP8zf4c61<<X(!&Y$Yow(R4uyzqqFzJS}je%iWy<IA4v<DBW?o0iO7=--*_;H~M}
zsqpE-@8-bX#f9C$g5=GfwL|Xa00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0r^QpK~zY`?UUJC(?Ar(V^BbpRmDM2l-2b%E>$$61+gq;DJ?Apx>_)8HEF30
z1qv<yI!QY65i<h=Jn+JOn3sINdveZqt{_~`MdpTR4te<yvEk;e#wM}(cFUc+BHuEx
zwXMCQQ|!8T|3SCNw~VdAT=visCNG}~>Uq@L_ZU6tfBI|y*^ohN&5&~$8Xg&a{sO&x
z)%W_19RZglNiNj$_TBprh!c_AZnuPnKYkkfd})N~g^7GY=8#tqWQ@OlJIjk*1$#W>
zgqZ+yzW-as^djR)gE?nn@}vi!H!=L<1mfjN3{M>+(=)SYCw3BX`FwNp6fe$M7+;i6
zP0Ts|#ifABKyWz}Ug1&^<UqvyH3+S)tw$+doagEfL}HdoIG#wRD2Vfr7JgcU0O^g*
zUkVLzoGXry#4@R@lB0NWs^H)F^p4ffmArN24^hdfs+Omy80GW&f-oN{gjc}IE4D=t
zwfMKBQ@nt8X1hFR1{zO;+2q*P4r#JhD$x<&c6oOh3TyF16wJRei2A*N&O&C(yQ>A*
zTTdh_V86<|uIp4UMlA~aa1gDeGGHqKGqL&y^4bu#%6@n+eE^xPqU7u<Y%fNWHY$*+
zR%<FRl1?Nyv!GOKHH{aECXMoeS}PV0|3jEwtnxul(+=~OPV9QFW{v><WVgBso0Wk8
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f?exDGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_limit.png b/web/pgadmin/misc/static/explain/img/ex_limit.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc3efd59d70436349e5413f8c5d2673200cd74f0
GIT binary patch
literal 1237
zcmV;`1S<Q9P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(M7`;C&hL59?^MF-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUNiI=UKt$YscuhV`Hfo7w~d&?QwDGaB$~uaOH1r
z;cjl-ZEe|XY}0FNhN)yyjTym@HuUkx@a(tm>$dan$M5O1^X<m+?8WZpv+d@x?d7rY
z>%{Ehuk7Kj?BK2N=)$7LVC&_c>f@a6>b2?Mn&;e?=i8R-<FMt|ljYTt>))-W%w_1_
zndaG*<kXSm(~#=js_*Et=-rs+*p%bakE+va@8-bo<iD=jaL1~3vEFs<<gn}Btm)gT
z>Ds97;k~rudA8?#xaxo6&Wz#Ai|N>>x$A)A(2n5BiRjm+=hmj@)urv<yUl57?A^NN
z(xc|lqU_wb-^hpG#)jt9q}{@U-NAz8(4pSMh1|e_-NS_2zJTP;pYP?s>)N;G)1&6m
zq2$e;<IJAw*|yrdedEiX37wJX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*pySK~zY`?UU_O5>XV!X``@E)R3}Nf|O*B$m)u`ByS4@xQIz9FPadbCWy#_
z5QxIR-d(t0;9^epp=NwO+}WKuzd83g&$%MegZ^zjB6tlvg%IMgSS)@*Je5c!L&VUt
z=h9(fctj=}B}T`_$6pYFh*&O{i-`$^Lh+J#H90vcB}QJqnUWD>)6>#%;<hdmlBqj%
znNi+NGuMdnZJ$&sm0G1%znh(%otvA}Xf*GU=I537>Y^pJA`vX;bh-upT-1Y}qNpWU
zrdJHBuxd1!Er6>!B0rD_(XFkm>CkDl+JLg#?KIF1r;`Dr%kB06uIeHmuaQNo)k-16
z>!rcq^!bcncKg>^z*Rzst%U@<-VFmVK3~uTX8-!;P2>~V_R@9{;9VHHF1A>f4FRs|
zBA?OE7vMGU#8sVnk=rDx1^uFp(!)}Krk7!LhhZ#WF?+&W5Q;>jF~C)w9^1QnUrFAD
zodO!W2)fMJMPxr7PXMm!blKhehPqHzyFCE(hQql7Owi>vLwGZpj6gIVPo)7@b#KOh
z^I4$5t~i`N223uuKMZVge?JO|R4R1<xT-gO?1Jfc5lbH&9`&dHC!2h8X_3C)(ET>K
zCEkTm$i;CclRW{Uhm5oEtZyb+ez5U09OO<<^N=|!6gXkT=yIVYXK@xOl*Qp`v6z8u
zp;Q`xnA|;C<PITUES_h9E0xL>VZ`DIvp*md{nbSlxu|lLN>vc~?IsI!LGL0Gf><J*
zK7ylszIF^J7aUiGYNdX8C48}77w26U^+u!76h_)hp3ddaU~A`Rg$v-yWrSP~M%qh`
zvMAR0EYM&p<#MBb8`9%~Yq_ZNF0Pu*=GEVt*n|Fa{RGa817HRj*Pj3Y03~!qSaf7z
zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6W
zZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjf-+GHC

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_lock_rows.png b/web/pgadmin/misc/static/explain/img/ex_lock_rows.png
new file mode 100644
index 0000000000000000000000000000000000000000..41c1148bb185c87898b3fddaa76be36a15703336
GIT binary patch
literal 1520
zcmV<M1rPd(P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700078P)t-s1prKL
zYin<9ZE<dHb#ijIWKo23T)1UXxMxwgX;FK4c)Ds)x@%E3h{3vUQGR@Ux^Yo}etx`j
zQG<Yhyme89f`YwxQHX|yzI;)Nh={*{QH+X;38~`0f>Di(jE;<qz=Tnbj*fDcp^%S{
zz=%=8h*6S~l9Q8@!H+hYm3+dEQI(aIm6n#4mzS5An8cD%#F$YswdI<go|~SYb*9Ih
zp`pT_QO2H8o};6lq@>5AQ9-=u%c5Axr%}nMQA)t+%BxY=s7}hQQOB=S%&$?Wv9Zju
zQmC`DUBv4BtpTdFwW_wZW5(*uwpDA#=xE67&bnBwy1LG~S+BgjZp-YjzP@$M?Ap72
zw8FyMy@}(!bKJgw|GhAK(eJjz#N5GxgwgKZ!-Is=@ZQ9P-o}QE)b8KMhu_GEk=5?t
z$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+m
zlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#r{>z0>Dj2~+?VOvsp{Fb>D#L7+PCQ5nd;rD
z?A*BN;F{~+tn%Ha?A^NS->vfAr|jOk^xmoL;H~Q8ob=zR?BTBM;JfPNo$TVT?cu!i
z;i~N8u=L}t?d7rb<gf4KzU}6+?&h=a<-qpkvhe1>`Q@?i=(G0cv-an-@9DMq=d<wX
z!uaU6@a(qs>bm&qxbf@6{pz;(?7H&p#rf^N`R>2@?#1))$p7!Z`SHN{@xlA?!~gNV
z|MAlO^v(bC)c^F+|MtiK_R0VD+W+?3|M<<SyU{8D0004WQchC<K<3zH00009a7bBm
z000XU000XU0RWnu7ytkRE=fc|R7l6|lj&CyQ5eRxeH+_N%WSus*<!m=*`Bu8B8iHI
zC~mlcpojwm1gMN_hN04AG^HkyMp;_fzWiC;xpx#ij-Jk`bLxZdhi7KancsQe=eh3<
z3Yw{ZPzMOMrc^$J5DEl>R3a-OF)@e8xgr!66UABC*%d@ZUSZ)af)^1)L_`RP1gMF`
zrP$b5A(0C4Y$7i?Sy)K?L6_Y-qqanb<M+*I<|&mUcAZbVnRe~SO7sCn1_lO3#?<5S
z41pnrVSK<x)3g_0A|~ZR%)0Q%xVS|K8R+b6_i*ZIY;5cUjNNMO2DH(rb8yJniyPR-
z(tX=Gq#Ysc7_l>ev0AM(=rZcYkf@Xxj4t0ln?*eB=x3yTb~{A6t+$OdaO!GmS~0Te
z24{TE!C>SC%|Gtq5pvPx1ztyMQx8I}?^v_?^qCXK;t$OVo);YybP}+Yfi!)xvdGo<
zqo2Nf{PF$s@L+%cR@fzzEilx_FhjuC-A(&|*XeNdfF6pX+^Eatcca5>4G#A9K3UHq
zNl8hW0WP{&7u8LGQYjQRgq(YWkWm(S7&eYDb~_H%=md_M>PiYw3PmYKPQCUc4?_J2
z3YJE}wpOZC6sRjLm0J*U{FNWMzhd0WWEXCm#o|Ip{0l#FH{`bxJN%qQMiFvnG3?@_
zyPRH5ds>^|S-9(LHaBpY&8802W$)8xFF$<!`R#4*z2@cx5W(|LbRmZ>%tZzHdW7tW
z-nMaVSm>%{Axjp{p9>MEP6rnQE2UB?>VQoymz#m9AYY~jd{+!b+Qcai)*>$|G6923
zCL{R~I6w86Q`UtGx-c0!K!=_rTX>P90f}WyPLIC}Mb){v)4|$d(Cgbki&j(Bz&{wC
zAMXHG=pvT{8mUC0=11^i1R{D_u2c)EBogry2#z!mA#<hDDxj8##Y%p}ZLyeL6TGx&
zG*$JWUaeHt|Fy(iCIdVLtZgKzZ2=8xwHkI&mY3J^4~7>5D=9@T#H@>Qkw{d=j~MdV
z#lWgX!TOOBenbyXLCdK|!XiqsSX2(mrbgOGEu0@~po`j)l9JkK5=)=%R?gJ_Sib;}
WuicyymX%Qe0000<MNUMnLSTYGrAb2o

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_materialize.png b/web/pgadmin/misc/static/explain/img/ex_materialize.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd0bb90dd2ef1cc1baf7f932817d1b6bdf463b
GIT binary patch
literal 1221
zcmV;$1UmbPP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001^
zyUc^E$aSX2Hi*HV&EAa1-IK}Ps?y-H)ZnGh-mTK#zS!b$%<V(H=}o`rQ^M+n)bNzp
z@OsYdhtuw@-}7s{*>k?y5Ub*3#pqna>5|p&pRKHao1$`-p~$a;;^52R-owF<HuUhw
z@a(tV+qd1?w&2~u>*k;8<eu;8wd&)Y-PyJ2;hXR1v*6sp^6kaUy`#sfb>G{;;oZva
z=CbkX#N5}k+|{$*+Q8@Bm*&}&?BlTA*tOf#vFYHN=--*_;H}=+zU0-B;M>TCsbo@(
z8Sv)7tJG}W*1h1`$FJLRv*CB}=)&sVtLfaT?&7`L(XZLhuG!A5qQzgO%Ve<JbGPSw
zxa)u8(U0QJjOp2^+S0Jx)VsRuf#J-G=+~#%%&gqgy5P!*=hmj**v0JKy4%va+tIk@
z)THLqqu0r)-^hpF#f9C$g67eo*vzZg%d6JMsM^oB<j$bnzJT7<!|U0$+RnGy&9~&u
zp5x4(<IA4a#irW2ecQc%&=gc%00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0(?nCK~zY`?b6p*8bK5XU_ww#jGAI1rVXt0A|?yUN>Pg78bgs7LkqpPC|yAQ
z^~}hc;|y@}z&Sa2@IKs^*>CRt?k<n_Q2(eN@lkOdJ9#WX#r69K{Ds9u;S&GJ)1~ES
ze2fTIR@a`t5WIY~zW!Q(k;OL~8$t+$3(Evq6^TTff|a$mo9_g3PI><U71uHH@iqn5
z?;ilML?RJ$9V671R4U!#Iz}WinM{JZTtN`2QmK@pu2N4Wa``NxQfX!ot#$^{Xmr~+
z;j$<c`Wac&>g^qjd;+Mrj*-t4Dy|b#6bd*+Pt%5pDQfi=wPQ3IcZn=8g&xp*CKE$u
z(db|b<Nm(cf)j<-YNY{fGTBI?)*T!gAu*fn1hHE8Xuz0kHU~)#4k59a%}#=dX_Ugi
zI2^7!a^yZjWSnBxZnt~>x-s`LGP5^kU>L>)TrA6;0C@jxeL01O+O=3LUn!5*>+|C;
z#@J8_HgFbX0nh#I{QUU%`{~6GH;+r<4TZxIl0>5{V9$cV*q<pEKhYGs)9Lh5zHmIA
zz=<Oo2tX1{CR2Cu-AEu33i+r=Je|(qWSk-!OQo{J#ynmhOyN&tGP$|`u@jb!WkEKd
zFJy^N>4`)VekxZimGCKCihQ|TE)WE!sAQ;OwN|U+B$oVjnFob(`Km#XM7mNfQuSJ`
z*2GEb@)F)(xVoAknM$=<f~3_V2ujfg?M9=~CJ0PX&rzLbv)P$@W7$Fh-rw$adlOUY
zb$Ea0x<A0Da4EXO;jl-LdJCoK-`v~`aMFOJ3wl#Xvo#uBQ-i4lK0K9-M)2V&WSpYi
z>rG_o^ihh=U@(|_V-NMe)o+jt(ePu5AK?H103~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfVJ3t*

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge.png b/web/pgadmin/misc/static/explain/img/ex_merge.png
new file mode 100644
index 0000000000000000000000000000000000000000..3fd8299fdcb72604d0c97816d38ee25ec51699ff
GIT binary patch
literal 1127
zcmV-t1ep7YP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001z
zmC<Hv(ap~CyuIwd!0pk|^3&Aw$H?!;$neU_@!Q+=*4Opn;rFJf=Bcaa!NTsv#qYJY
z>$$q@!^H2!#_)!zWKxY8p~PNwrpGpj!Eep(LA>Zn!0CI@?}XFvW5()e$n0**?3dZ`
zpxg0;(e8}Y?vd5*uHf?tsp4J4>N2(Eb<ON+$LOQSVRDwC!H+ic@W}A&w(se*^X<m&
z=CbYOvGMD~>g1j2;F{;$m*?A-?BlSe%w_81oao+}=Gm3(;;-o4nC97(<=2z!;H{?6
zXYAmv@aV$Fs&%W?Z0p~x>fEaD<-p_8kK@pe>DsAbl`meHCBewL<kgaJokg0;U3jiO
zp0$U%?SbUekm1dYi>qviyiJ|ZV|uVayzqqY<iF|Ir|#mtzsI+$-f_6=fAHqO=+~#`
z)}`&=yWz}=;K+#I$A{+Bq~67a=F_9#$%)>?h26u0=Fy_;-MZ$|qU_wb=Fy?$&!Owu
zx7@#g+r59}&Y<1Fg5%7dI?;o_00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0v<_3K~zY`?bGX5(r_3DaGDn~EzQc+f-=ht?ef4F_ZT1uwF%R(Lb^=WWfKN0
zFq@$K>;1-ob697`-_$p~@OkmP`<&<Z{qD@+xE#k7!Wh%W5n^n7Vq%hTUY)u&O-wtT
zGuH_$8NV?*J4f7{pT9LmOy0gTHS@oavu8O+V;7hOa^b9myh~n)+@p*!Z5*NQyWH*v
z)WgL`OOL51%THIHQCQ;gcsy=ub#2Y{oLXL8e6fNj_O84fA+P%HH*FjNug~Z6f|sUg
zKlm9Y5Ckmog+d`3=x{j9027HuW0vIgKO}AtmSrFiO(c>wNIZ@R!?LLW#1iZ2j3wDH
zWf`OhpcKJmHj~4WH(-ouTamY;SNt@?_#qgJC3BGDIDQkbgke(=lwv)NQgHcvL9ilQ
z8{6+3!+!vnR0I{9Os9(+@P&dk*?IrrBQj*kFsRrlDmGgz?(&F~%a-KR=PzHkNs=sy
z$c&`egFQj0NLJ(<GT(ocN}?!|HXB1`XA4~s`&V+=4EGZ@c|njsl4WJz>dLS<v7aT=
zPa%}c)e6WpRjr>w290sM`GP^JRT&hu(P*AR2H%Cx8^sD$O;e!W_|<CT$uNb2QXDjY
zw-2$zW@Dx+l2DN)*ss@{2XOdD*N^ZIt7uwnm_qM#y2p4TYc*7Cv(-A$5$Se&c%rB(
zDz@D^>F99WwM7OgbR)%4uh+w8IY(ocV>y2TWnrZACmaDB0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1n&OPyYY_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9aa51fce9d775f169f70d115761e9817541c48
GIT binary patch
literal 1599
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_append.png b/web/pgadmin/misc/static/explain/img/ex_merge_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..12fc55d76f504140935d5fa422f9a5075431bbc3
GIT binary patch
literal 980
zcmV;_11tQAP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005JP)t-s00000
z003rd(Nc{WHi*FosNxW-;$)T3a-%gGuH(UvHg>8zdY-9xu0FMyetNM%gs???v_por
zNkF{mMZW2fwrozm=1joprKsemspnI|>RQ3)jlolwyLFkoc&e=DiMiWd#p`6n=&rBn
zp~POI$6%JsU2)6oq{?G&&F-hoW_r%-+rEF>y@|ZN?A*VB+`xgozwN%i?cKqGgVF8X
z!h^rT?cT(M!ou!_)bQTLg~G${i`4Gl$A*&C@5RRNy3wP>#_+D%ag^Bb;mnHS&yCB>
z@#M{(<j$YW&GF^VpySbw<<Fqw(vQy1^0ec5<kOJl(V^tjk>=8)<kgbV(emchr03M6
zw&#51*OTYfrLEud)710S)b!}rrs&tF=Gv9%*r(Rk^ttPQ>Dj2~+?VOvsp{Fb*xB^D
z?t<yus_NXT=--*^+qmiAn!NCY>)x#C;hXH;y6fMq?B2TS;+*W@t?c2h>g1i+<;(2i
zukGQy?BlQO<FM`Hu;AeL?&7`e<gxDKzU}3)?&h-Z<-hOd!0+g@@9DJg=)v&l!td&~
z@9Vbl>cjBtw(#t?^6bU)?Z)%($Mo^Z1Vq=200001bW%=J06^y0W&i*H32;bRa{vGf
z6951U69E94oEQKA0kKI$K~zY`?UPkg!$1^;v)KUULUEU1#ogWAouV!7?ykih{xC_%
z08P8uzzo9+=i$EWd~@#Ev&%65`jNspQS#J=aPdC=LHS%|Vph=rrxEdXaCDB~V*P?6
zOSoTiagtKVQ(H*3K0T*!kezPn9y(&R*|RBuD5{jL$;c}oWwnJQBj0x+hE!(ZG4|b2
z56J|uqU6ai*kJH@XNdN;E;@msASoyeeLdlI2K-Hg!O>Vfiyb6RLfk-<JSpwt1POuH
z$A{Y&cse`Wy#}=#6BO#@!i&hmHd?uav*Et3WuQFNGM5Uyz`)%Gnzz7yUq|yS4PkhF
ztFRheEux77*lcU89;dqKTir#MXUoaK4P4Akj_d*D?}vtngvyQCK3LpK>BaV2;4y5!
zFcp^>3gRlIkf(HCo=X%&Q8JG`mtuU?vnWc&GXk+=|5<z>)+K-bO1R(MTt0_mbAQjO
zbuYw{VGA{*NiY=`WVb62QpvD}s*xFtx!GwA)=08cuRN`gIaG!Ep))fzsaD}aS{Y79
zOvZIsXsWB7QXr(2iS{Zh6GdK`j1X5QioEi_{zPxhzn!H%4w>%&0000<MNUMnLSTYP
CgEgxF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ce4839e02fd3f17b5fd8e358ff204046c38d26f
GIT binary patch
literal 1344
zcmY+CSx{347==TqEn;1Yb)kzRwvO5s8xTS1=pdpEBQS_s6{;)(R?#BJSj4Jq3RJBX
zQDkYPh!_@AR0IrxkdTEC5=cT4vWF~}EH}AX?oB|$Ort*a;rwUL%=w=FQ<a$T<6{5M
z{81>>;#h1<lGj7u%Fow(H%%9op-|{MiE#(_c;jQdE<1-e#<7zI!9lWxOtDPx9D+$F
za|9Y;+1PBmM2g7e2w&iwoP>oUczW7R8US1I0KR>uy>o^_wbJQO_e)bxulW@b<ZvB4
zo@1B>(HS=8h)pa;>PUr2w*vp69j|&N3$LV{>k!lrt7&peF2@=_WK3t6FL1zCrK1;c
zH7nuAD!5GzQ;lwx*-Zivior!Ux<t@i6v-ISq51BG4od>61lETNxJ+QH5ZZ2v>;w(c
zr$;)~&Sx6uh{@f|NG-y(<=?}pne`Kl{^u_ZM7rq_Go5lf9JaG;VDH?l&W*_Qz^qE`
z(rDcPFl)8GZeeAZfrp4=T%kmSLZ?ImE0ivyaqclYvz3#LAOEF|e_A$BCFnXQ9V7Dv
z_73408`uMxdz=u{jY#L*+Na`NlkRVmw$Z4rw*oI<I~TTdU}((ckeqDT`iPf(toCb@
zT??6djMEJURo#rD7SwjRVJU3mIH578m5tcNm+*=8qsIyfJBhqUufSr+@WP;cFeAGU
zOx1wur*5ajX&H5xSqLO*8Y-zE#x-)&i@KtYKUhz+mi9wsb~6oB6U@p^NJpMUrV*>q
zZCA}es>*=`951s&nb&8&N(4(twsIRVWR$lXrFet*iAB}#cBo*>Bn%3iW`PS*7^Q7$
z_8+Q|3MKs-z-xxVNk~tFw8K^n)nS-0h+9>R>&oGB1*KF!-sCj%Aq~|!O|hzn95ds<
zWQ&4YCLb!14P4c6a1JBSs-{>}Ll!034sgw~K0UWdJBBk&^&l3R0~ojKxv*KPW#gp8
z%b<J^8ZM=HN9j|LlyCqQ1x#go!@_geqvySjeW$&woSohR5F4}WAeMO|ZS{hWZ^o@H
zjt<~cX=Bt;2A4)*(K*93HuL+nBQc3L{_3j4ufE$?-cl2G_hx4!iJHcXCZ>+~h4>dY
zSFdYJuBo}Vk>A|xk3MZ$+vF3p@YH_cBwxfkBosto!jdAwL$+=?ya7+A@<*k6g(9w!
zvR@G^meB8Yt<;>!+s)T3aowoc;8%FDtaxd7@E|tX<MBnKJ@e?Y(o*z}vfa_c)PZNG
zs-i>z%K`%f)vG>Tv0{ZT@XO_&ueyC`AKI845G$^ZA*NQ}>28@ViTY+6jXxB0|Ihe+
zAGQbA9hF3-<mczxr>ZM7wVqPyI&zbynjb2^vgu!ZXGXE!his~s5{t;!8Lhxdd2+H$
z`a^M1P2{d350QAO=#A%f!-lxV@)Wji(}5@Phm(@(Lzd&2xj)T=+t1GfFXQUTsNU3-
z3CFt&S^TUm*_%@dF`1P<NcVl;vpy!2^m&H9U6|t$5mLzMU;Ubr=F|HHSR3Eadqx5D
zUS@KS_2<1=;NKL~a}pYy|H!jsP2}&ojk|&;3b$YI&{zkqJQ;7?@f+?)@T$!?Vu1u2
z6*|R)<?x~pmIY>P?D=^xPs_+6o5h4r&fZYnYKX{H+WIe;MNe~RudnS)#<~{;28H`~
zp;7y0eV6*_*%98?uP@khq#!fBAS?1jewNozp_mX%a7bt{Cgfl!CNgYGWLVg@n5~f*
t%(lAqYyJnE&CNWOeepej+5QfA+m6J9zXwKZ4|RG0RP3IF7(#Sf>3<opDS-e0

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested.png b/web/pgadmin/misc/static/explain/img/ex_nested.png
new file mode 100644
index 0000000000000000000000000000000000000000..15c47316d5d4163d97b95cda99b2e3a2049341c5
GIT binary patch
literal 1108
zcmV-a1grarP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002N
z!qAqP(SL){b9d2ZYtf>m<h8fzyT0th#P7ht?ZCnA!NTss!|uez@5RRN#>el+$nVI>
z@W{&W%FFS~%<;_4^3Bfj&d%}9&+^dF^3v1u)YS9U)%4fb^{cMtuCeH+s^-7I?YX<_
zv9sxtmEUxE)|i{&!NTpPsL;d2(7e9v+1d5IzwF1wx2vUWos>nDi!Y6V8N<SK)!C8X
z=A7p9r{wXW<M5#4@SfuCp5pDD;q08@?3>`~o8Rb}-shO$=$_f!mC@3Tzr1kL*o)26
zebU;8+TxSc+KR}^cFoU%&Cc<&uw0mqEU~Ov&d-F$$aBxniISDkwXlcC#=6<xjNRgy
z+TW7e-jUhekLvTc$jR@iqGZ|JkFBog*xQf2zwOuBjJLS!xVh`s*o@`wtmEsW*yMoL
z*Nf}&z3K0|)z^yM=aJRciPhDL-sP6m)QFdjE7;qU%*}nTs8!L=iO9rx!oF?G$b{0>
zhS%DTytZrH;g{Fej@H)nL!K(600001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0t-n*K~zY`?bYj3+CUHha0E^Sm0C-!f`UTD2bF{cA89RMMd}k&#45_8X`@6D
zMDcCwzpmmq!zFho^0nPBncU28_IB=WSEKnK57DI6=@B#ZI&Bj!nhh;RQ)^pWyU}cZ
z)A6?RoyGFL>qB>s)oQibdVBlq-DU@7w9Wkk{RS54bh=CqZ4J>G20jkDS^IJi4ZF;`
z8lrbO21iC&#g2{)nR@g!3&`nlGsS~(TRx#$;_)EO3xVtkGWPWA<#-<`!pC#oDzTG@
zm-7e4Cnl!^LHPV-`fD&4425UD%|;##g>e44==XU{gcqWVOA$^@mY1u)d>}fv62tcu
zi?6P&^D2@+DDn7(31MX;iI<(+h;RM?6?qPr6k<u`VluY5#Uwyj-DZ&~GD!iwmYp3E
zNvTN!?e11h8X!#~86`=e_H5Spl!p|M$tel4d9vA`ebie9IZ>Sj{rZg<Sws=27lu-c
zC{alo_Vx${Bq~Y6em=jiHXz9~0tdl-K6I!$8ITL~EO2y0s0Io}dif9vlTN=_Vvz7L
ziImF>GJZ_B{3m6ZPJE}8xcZ5m`Oke+!p|<Q#uX3w<m}?|il?`W$kJNndTTqC$=!&O
zB+G?jsp7x9xaAm4DY72;gNf4L^Q&7%Q+ieFtE0i*;iR56euwqsx`u@l8x{?Wqy`3G
z1LL)UA^xw%J#C$lG-cRqEC2uiC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$c
zGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLe
aHY+eSIxsNGmsP9)0000<MNUMnLSTY)!c1@g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1c0763337617f9ffe8cf13ba0f47f4cbc228c5e
GIT binary patch
literal 1741
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0e8a17d409343ea937e43ebb76e8ebce3cbee49
GIT binary patch
literal 1679
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
ZD=;-WFfhuORjdF2002ovPDHLkV1g+E(G&mx

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_recursive_union.png b/web/pgadmin/misc/static/explain/img/ex_recursive_union.png
new file mode 100644
index 0000000000000000000000000000000000000000..66952ea454e3703dcb08251bd2273193aacaeb28
GIT binary patch
literal 1224
zcmV;(1ULJMP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005?P)t-s0001q
zx!X2~!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_`zG-s;qsyLGzJqh@Q-%+2w=zwN)l?ZU(F)710W+4RoO^3l=q
z)YSCg;P-*9LUN@xnY?(sz3iu{=c=sduCM9E#_!9_@xH(9!ou#w#_-nG^mC;*r_X10
zsycS7JFwk!QjHmRt30ycc6zZujKETQu|cJ%<dC*(*yYQi#9oB3MRKDxqQ_vQ%44U^
zW`?#&cB(tB+HrWUKD6U`wV8gwk2a0LQ}pr4@a(tn?6&Xfw)5}D^6bU!<+1JKu<YWm
z@aVy|=X~nqo$BM9>EWC2=(Fb9mF3ry<kgbv-mJOnfA8zI>f)T}-<j^_vh3rp<kXSl
z(vRufs(PNOy6%GS>9pzKn&{q{=iHa<<FM@Dt>n{?<I#@d&yDHXsC=|Tyzqqc?Z)lo
zvGC}^@8-bj+^Xr?sp;6K?cuzZ%w5~PiSg>g@8!Sl;=SnCr{~qB=G3I@-MZ}IuH@B`
z;?R!h*QVdchTg@6-NJ+A(V^?#t>e*;;mwQZ)TG|TgxtV^<<6ku&Wz#Air~qK=F+3x
z!Ghesf!n@+<jtP$<G$?Py6fAx<<Fqx&Y$Ypwpcc+1poj50d!JMQvg8b*k%9#010qN
zS#tmY07w7;07w8v$!k6U00Ih0L_t(Y$75g^1>^xnCJ=y<#A#t-W?^MxXX4=G;^tu@
zQVTC1zknc<kg$lT7_nMNw?!Q63n)p*ixQGjKvG&pR!&|)T2V<^MOB)R<&tXZ8bB?Y
zTG~3g(t7#^hDOL*uzFF-*u+!=rxxs9)G#x*z@-Jd7o{w%tTk}?1-r1MjV;hGcG4gK
zB^fj<EbJY?TCiG<EyOGw9i6~>aJZKN(>M(aXP_REv;f1z+0g=bbYa!PVBrGNg6c&y
zE$GHsxMJD@@iq)#7-!*Tfnhl$q2L6P?j9KF2$E260x1tqFN|~qPAIs6hL^VmnqMFZ
z1!u;P^700z3Ljq(fRfOJf-}iV`hlc<{R0Anf_+0m!@?tc(LxL(V^~CjT<see6&(}n
z8yXj%km!r%atvE6lE7M$Q({uneADAIGPBUNWP^PHCBa&9z*=(i@(T)mi;7E1%h0u;
zIvN;W<rQEpm1$Ll)xI^gb@dGhEf7DUI$Fb`yrL15lAFL`1tk&D3{C~8j<(1F>Hy^z
z-{$b5#1`Myw)T!rge{O%fa+*?uJrBd&g`l4?d|KIFcHZw;8cL!(G0$mCYMfW^PM_v
z!t@zPwt!Osc1JV#*3O(YyWMxr+<EgCAhbYI0d_|-_%5vPow~?(@sgzrmSKb#c1Po|
z9FL>%0_={)uLY~4@dA8~9tHFV07F>Y9b_N@c>n+aC3HntbYx+4WjbSWWnpw>05UK!
zFfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GK
mIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTYtrHx7e

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png b/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_result.png b/web/pgadmin/misc/static/explain/img/ex_result.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfd7b5904f9b304709ac6aab37e24dcc06d233c7
GIT binary patch
literal 1320
zcmV+@1=sqCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002L
z$<UU#(67SKeXh~4!_bAX(Osq0Zn@x(wb8-K&}gpOjmqed%IIXO*vQS$YP8;l)bMe>
z<bu!aVX4@+#?YR;&}^sCf579B&FEmJ)}GYufxh8%tI?>y&|Rq4YRc_yz2s%c>~px_
zSfSIA(d>o8<yxZCm(uK6qSJ1{=25}wjkVEryyK13?q#joj?L+~#?YVI@LHwSTcFW`
z#^!Uh-Idtykjm$8yW%;y=Uv3<cDmqo&Fy`{<dN0xWTeqv!RRu#=5@;IZp-X#u-cN-
z?P09iK)mRBz~p+$>VwejipJ$*z2zga<UG3PRl(?g&g_)c?q;&xVZi4cujDnj=ViCx
zTEpph#prd#=v=(zEVboDyXJq$=zz%RWX9`=(d|CD<!s68Q=QRq$?2xQ&}6XOaJJrj
zx!-lf=SjZld&TESz2|<->y*ywR-w~O!0LI->S@2`Yq#KKzve-^=xegvb++DV$m?jj
z<BZ7Wg3arCyWyqU@K3knEo+Lw00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0^dnQK~zY`?N#Yh+CUT!A%sLMsaGh-LM_OZl!8#apdwKLmBRyy7FwkURz=05
z^{DN?-f#qVLv*HJI{o0AVTQN6zxUqnUBR$t`S+s2aTTPh2@R$Ze@_Uhr6?^eX#=!w
zkidu+dYpM2@p6b79vK~LYpmgw(Ik4&kDGzz5roD1dV(B&W5e4B`=q1wly#1~+^jML
zcsL#Z*85J~R5W&<&+Tu6-BT{7A|X|P;{qx^*y6$v?}zk~X*x7LLn!g#XXoaFl0t_g
zAIP46<ARx1fJ7H#9?<#k*9uEIb&ut;cg5uICm&DDQMi=5<os$BHaEGpPRal*X-K6r
zalBmum3AqcgrAV>8=EE-ir5<7j^*+nDazi&@X2h!2ImzwkBpdc1Oj$;KNWK&&*v{K
zHc_Fp2T>6_XL(<6MG=Sv0DC5#0-#mAvC+Nq*9v^kIK2l8vdFM%Et8knvxnJo1c6|c
z&4c}KvKHW}Vy0H@L~i3sM*z8wP6~1|m|;789O!rkC-wRX3IT({=^1*&oHufd5<hTM
z>Vx<$E{y1rNF{wq{1pVhD#p6C2Rn5oh%7L0oleB1N|4Fp`jMQ=#pw)z990@QuA5do
z%zYPv$i2ZQ&zL%%$z6BtR(}-AOZ|1ZsiIV3#aX_aKKpI?Rtwj<S$$bw@^{6#TF;1K
zr{3RVX9kpAD%Y_0MwLA*6vA-iVkfW3ieG7@w;>~pUC1Wk{1EpWn@(i<$E=y+6>NkK
z&qpAySa4-uHvH^XzT03OH><a{o{ehAyMj8w$h-WttQ&NZpV96kV4My?L0ABbMGMDq
z*$yScYY)=&)1fH`v<WoU-7VJ2U7}9J&`8I$z8yYm`gKnZK)LWFQpDw+wQo`T$%m=d
zzQj%A4ia8s<V50kDpFI}g$%88-LN9{Um<<~F?K%Tp#n<Vv$^~qaIuPR*#L_H0A7VD
z3nOkq=7|r|v-~&m2P92<JOAsn6#xJLC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@u
zGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<
eG&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTXtOrt>n

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_scan.png b/web/pgadmin/misc/static/explain/img/ex_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..396dfb4feabdd85e081bf8336304129058633c5c
GIT binary patch
literal 1320
zcmY+CX;4!K6or#E43U(&OiX7G0Tmtd5DXYmgR&F@acWZxiv&wwL>3hgrBoq~lr@it
zLJ^duAZWq5fw&ZkC=h`X77>I3W&snjFJvdI(x|^WbI&<*@7(X#z04qTpskIa4Gade
zJw!T4v82aGA-}XZR*;$vgTXU`4u=peEwhE|na4_cJ>&OMPHdC^GN?%#(geLygmo&i
zN3_g;wJ~hf>0VV*pQ>a+SI*I098_lwt84kXyOa8Y(V1&wnnwbI)t>@VyL>~#^s#Q`
z-ez%7n>4gTN@YnI!|M1RSrS`*eOyDod9RKoioG$+D)iJTAoVnDW}2o~Kn4Y*nFci?
z(4d5jN=PdLbt2HDg!CeCRtX6-W+}%-_OSp37f^A587`pV0tPWSt}w|tfRYQSEfXG~
z<pD-9I3_n5EVvjnE1@|h#C^#el^bWp;E2qi;{kdeVBi5}F*q#M8+pK-7&KX20?sR;
z1r;<X(GQCC^Ad1T#TXFlOnku12j=+L1qs+M(#`ULMG4q9t)1s%*%ntpODaaswDupN
zW=R4rs~Ft^&Eh0>X%f3E0nHYQkNqG4U%j7Mp2U7oF*>GZTDYnYlh_prI4U=^O{(k1
z<RH8(z;Xk43WXdD^Oa1*TAEd7Nz|V$N&2LK-gs{q?2D;G2mL}wJ?au}Fvk}SCEl-Z
zn!Q~=7IE=$y}e(yGh*kDXdI~~U{jVK(F*Yxuqnx~>1&_l=Y&sWTE`Nqyi*-+eA9Md
zK3zI6YurED-VlgxD2VgQq4T)o<{Uc52dBM4_ns4qT@<Dvt#*S#&6df$?z_JA^7Nd&
zsc>|3T=BIfA3pq|mf@Ygu#{_8Ub(bzb>X|nwg*!@UBbMkgoVpZr*Tf=$$yMQS7cHv
zJl|yI9uG%_?<;ufhl(nz-b3s9yRngm7!eBB**@c+iOV>**WUgtxir4qH!Q58sBY^X
z+t^lqCrqiOuN6j!FCm^Z-Pw}A4cEdyv#acY!)jFJxKh;d#_(duq1<!#u|_inUhRb7
zZuWs4?RmPM3Lb48C_72VNCv(-0Y~+p3u~M&y#z-hvq<OPO#~HPL|HeT-JBnDa|-Q?
z%<Wv4nUHu7scz!F?nr$`t8(+1oBKW|Pe5|!z>mbS2==EN8$unCzu)=|NpXJS+8k(y
zsf-o0Hs=vz(jedS7h1^)Nk6Egj#I}j#|fGRi^DszZaa6=0t<S~OgC$5;PxXZf91sE
zOWU(6NzTED<doV?9Qs3{X(;P@)9MkAuHxQkhHpV(D8foPn!Wy&AP<pDsLJyP%3oh&
z_cAYs?`!v|IYy!w<2r@-;@--2?03|0W>xir$OP}|H5oNdm)-MvQ!!rm(9c)!#r=_M
zBHea|)}|e>!%!27`lYDuS>)H4O$vGq?bkY|V%O_yDBIEKFR-P%ozXV=^u|^833oyO
zaZUL45Q3B6k&=}B<!$*W;*kcVXyLiC3y+ARgakaz>j~!U7+UU*(%Cey-DHB+7FHqH
zPj|}S-*ndrRToh|R<X8I9r-f-LcEN{x??i2OZ4-b(JqNaSpSxvc22t`6N*Rz>U)~O
z3XiIz)B{0V)^^ow^e^-{bxVdh`d7qN(nH;q3&j)u%b;lk?cGMHsP*lX<>$Ai5W`cV
zkEg^CP9(=z0_K5p$GN(DxZ>PHJa7d3E&?9!g4<2N;r5nqb@(5Um=t{~HtjQj+xH3h
axJ|%$eg^tVLmyfI*dZeMV8gextp5Q0yRj$$

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_seek.png b/web/pgadmin/misc/static/explain/img/ex_seek.png
new file mode 100644
index 0000000000000000000000000000000000000000..130fbd8f53ff5c2c757c8173eb62a1b604555f34
GIT binary patch
literal 1326
zcmV+}1=0G6P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=yH~!hN)yyjTym@HuUhw@a(tm>$dan$M5O1^6ka$=CkqY#O&j+
zp~PP6<(}%}obT$j>EN30=(FhFndjV>?d7rN*p%$xt?c5j<kgbw;jZi7t?lNr@aV$t
z=D^3Qb@1lE>fEaD<G$n4kK)jc>Dj2{(~#oMjN#0S=-8+2;k;g#B*DnK#F|j0%w%+@
zIG(kKuGw+%>WA&)VD95x^XP-`;92wMfbif}@ZVJQ=6#RAQ?cH30002z+A8STDCyWG
z>eeFb)Es@XL#xzmv*LH^)gSEB81mgwaGgbAl`r+=dg#}t=hUR^+_~n_qTtDi-^YgD
z#D(V5q}{`W+`xk0$cWs(faK1gsoHLy(PX^vg!0`^^V>}K<977gNQ<j%pw(yY&=l^^
z5bw?o^V&-A%?rQBx1!f+zBxuV00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0_90WK~zY`?b6v(+E5e+U>k6&Tea1i1BihN$R<*($RcX1MKFRGK$JxVf+$KW
zhy}F%>%F-wiB95-Go4N!ybt#&-#z)Ab9#DS&%Zx!2<}MUVuX;%<?>#luYX`*kQjU`
zlMN9=eZ#{e#K`E_*gHa$$j8UW<wXC)#Ke2z!{p?ojOaytm>8X!l8q66sme$E&ysih
zt7&qYC!kO&l?v`wDPTseR?osLMNt~iXti1mw7()hkpiNeo10UDQm51DL2WP?=7BPr
zOlHtpEEel?M1_c&BSs1+lgXro1&hV@3{im|lX;^N5wpo;(ZZt5X0wYEg;J%O;ZuCZ
zDHa^yu-ffQqT~y3NAec0qSvcYm3fLnRm^78CC#GM>VRdZ)43x4V<?4zPhsh%aJt-X
zk2q25T8%Zf8@mWro6YM4r`x?MMbIodjcv`s?sd5kS@roO$Sh8w!6_Vq6e}Lj+B#1*
zzIONK8}5wc-65M>-;p8xoPq5?Ah6xNgTWoxp=o*tc0-}?Ubo6-WNU*YN&h}F5MZKA
z00PlyGyuU^EFJ{<;NXCUP$H2C^W-qHdF1!+CzB*1Q6`E>EC|7PJWeB$N`)YtNT)MA
zA&*Z`6(sm7u~>_s_vus$y&p~s$r0`tnG|LTK25Qg$z)ESkQa5#FPw|RYPB!JQhOF_
z*>zaY<#Kt*vn-njQ6gLqkFQY3Lhh_sJO}ooRAReiUr6j;FJ6yTv>w?);W7ugV)23n
zwp6+*OAwdK?L_bUd~1-sJUh$5dGP|1D*;IgiByVwxm@mMIr;J89XtMch<EH7&xM=C
zF6@TGtyxq~;j~(<-oQ<*R=ep`xsCif<aX?uGe9#bCe)sFre3dCpxS6Q@1S;ne_!j6
z-?x#6<TXBM$LJK(U5`q=-l)P|v)QaYCdl9&YgGv`2__7gbiIy9qk#$c@nh0%Tq{c_
zMWvPEu9M<XmiBo`$6n9>cm4nf0rwvzb1L2d001R)MObuXVRU6WV{&C-bY%cCFflMK
zF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGP
kFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f_kQ{fB*mh

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_setop.png b/web/pgadmin/misc/static/explain/img/ex_setop.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3a9b1983b5b47c96ae910e73ab4260ae9c28b73
GIT binary patch
literal 1143
zcmV--1c>{IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002E
zxzL`V(1?rCc6!ljZ_&-ct;w>B#-wM%nNGrzJd>5te}mbYo#C;w>b<`0$jI=)!tS)S
z>Yk$FfrZ(}s&>qySJ}LM<k6by-Kg#6v*XQ`qNLELspq@A?YOz?x47%Bu<78)j_l>D
z>*J&8;hW6M%aoVjwzuoBvgxw3>bkq^zQOKonbT;Cw%F6E=-!y;+?VRzrRLP5*SC1B
zujqQE*JQ2NWs|?=+Lq|snys$rs;uaQsoGkq)?dWwX2$7un78KHmFC!#<=2zt){@q>
zb)U22NtVh}z2-Hy=1RThb;#&boz14Or{vX=<kXSo)|jNH<fy9WTeRLOv*bFs<#o5*
zUyi-x(~#rRkKMwEAh6_t&FbUOj<dAt7_H;t&Wz#Ai>j^WVVTb!u;hfp;!=vh;meBO
z#*ekO>Wt0lK!(EL%8BLApgM`e53A#l)9m2Mh~LPG-^YjE#f9R_n%=~P-olRE!h_qr
zgxtV_;K!5Guy4}DpWelaCqTqQ00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0xn5JK~zY`?bK^i+CUTrVD7hwTp9u~3K&4efQ155Dpjm4f~c)k&`L{%-n1>)
z8Vm%m{&hD%k|mfKr!(y*&NpUw=DfQ(dlZUEP3k|EQl-{twHmcbIry#98;mBC(V*AK
z0c6TNZLwNy_D3iJkj{ZQUHHrlPB<~gy=WGlv$#E8ug7AuTIbPOJx2O`et#ek@cVEQ
z(~p4#WO2zm9bC3Ac_=qPq43IMANT)2Boc{6p2Xsg1qngSR4_5mlTc_inS9EFXf&02
zhC3aSREyMFdQBwH*EcfBO%4E&da?EL)fS!|$)-f@i8MsEbNQXZ?%w{O1t(s=IdEa{
z9UyO)4`C8M+9{SY$0rd1ygNNTC_~YdQ=T+TwsVE|#Zvym-aY_BQK?j7P#cx`;~Y#@
z*NbPxQepRaGbMm(wdRV8r%``OjF8VqUpSKa`fZqr1GRJF!XOaM_w)$K<<I)$$4{|E
z?Yv%V7zLtrAHsyXEd1ig#_vn9Mg5AVV<tJ`7D*pe`=HruLPQdb$`$xve5NY^u4%^j
zfaCL-0Jv_EZNmdDS!~f_Sppgv`A=CNwrBy-V&I993`~e0m^z<{1x0q?S(Y2i<{%R%
zy5-);Sjr4PEwt!%S>{IaFeOjS2A<dne{2A{WhN_mVq*{45?=X3(8Ek!_M&$)_K+p(
zBEhYTPJ<wC!A;%vn9e=xqQ8_-b8vJi#oJ7y!L+k7OG)t;t8Y@1`X_Y<dEniZp!V7a
z0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFU
zC3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ov
JPDHLkV1j^qN`L?W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_sort.png b/web/pgadmin/misc/static/explain/img/ex_sort.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d46fd34beee098f997529bf0b7714c52232a2df
GIT binary patch
literal 1157
zcmY+Ce>l?#9LKkGD0JGb!<|bOQ+Me`PIBs@X131ubm*x$mvXaab;*yACb5J^<yUCU
z=?G6fnfz!u<i}*hkYy7xW3|nXjiOR_@wspHSATpy@8|t_KCjR7c|Ol4eIL!oZ1p#*
zQ7Dudg-rBA)cHdg8z6b}L>w1|LZ92`8{m!DQ{|nmX`=An**#5DzO7UKZPUMtXP6Il
z5fWW|pN=clXUJv+$_1mFs&#^Aj(62w4Qg7mCQzhhh$nO8bCLPQz+!<`3u9yCLG%bb
z?15U4$&kq)mBQ#~6BITAuO9LqLIsSB)IkpP_6i{rRI10&2s?LDwdh7Z8fJ@NCJm;!
zFd4t3ITfD?Q+k-tfKm+#6{x2Ho*jVkUqDF&&9OlR$ex0982Sc5B7<QifWi&bjP3!r
zaEsx0$?8cB`nK#iITTstVily{c^X+e5er>i<q%g4k%ho6fRJ1W5&*YE99$-0RE<W}
zX*u<}b08K&QxnwJgHU)0{BOZo5Q%sYYAS}-7f?P0MUzmdg?qD*qJqS6;3y$x4DvM)
zseqCx$W=qd3>dXTM?bXpLR$}rBv8e3)XBr99y8x4qpF)h&;TP34lIsGOB-m{TFz%8
zmqJJT(fm=Knjt1)R-Gi%qYx#33{uyzT`1HF4+_yMfZVAcm^py)vTU8m)18%`sKnhj
z*<uksYUi1eVOfJKPs>_|AHuUzvW>}&>XtFnD#12=5jSD-l}O2Xy6AEAsv_w~<-a~=
zR<a9*e9KC+;FgGmLH*CMbBgT*1FGqRFG(blkIk<5_s=(bzrrV0ONLT{JvvNXj&wR)
z>7XZDI#BoKl9GL|_MWroq;CqTDQRU3U&c7L*{b^OT#I7a=4*Z|__`WB7ft(h<#lmL
zwVU<juuHW=)EJqm7n3feC-+#%HCxw=uD|m{yYOyl`E(|KL();vVdHgcQY}`jeA$Kz
zd^GU|aTDfZM~Hc9Sy{U?g_H0ClY?C)4vFx^B<8H|$3?8%P+fzuPdEAthoRteGj^qv
zp2y-%Cb5#6EEi(d{<;x1@Ul9Xj(^`H-CwfeY>d+eFuzNvy3((g8#?Rm4)+uXRZ{0S
zek&Lj2&|vqTX>h8z4p+Yu5=*Wb*%X-%fX;6ZCeB97F{VJdvHeYIL!geO(qzY#fd~4
zgP|L%u1gYzNTmVY-#^*ix*}V#)w7h0IfJh~v<cg-uAtgBZSQdJ-+WSx^D$TOOB4TV
z&ougHZ<*gtEH4SK60NlsIlMIuW@DxNaj!)u>tp7<>2HN#d3gq@q^0Jug@L!_)33u3
zSD40j=<&?;yenlDmRu9KMFjKSyr<uxY0vOCm{D>(4e9y6VRvwCmgKfwx9qwK@bjUR
zis9q0OCHVF-XIovFSc|R*2(*Lx0j!tMXN^#U$e&f2a$)ekM=$o%{USr>i$P$C?Y6l
zf)l~f$=Q+M6yQv7ciHCd;_?IGCwBtDjc339GvLo~Mi}$-CxEcyBk-Zyo#6Tj=q}h_
PfdD9qH;q`i`*8CAb@5|F

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_subplan.png b/web/pgadmin/misc/static/explain/img/ex_subplan.png
new file mode 100644
index 0000000000000000000000000000000000000000..e13d7794e91fc0842702b0b34c1a9b61607dcf92
GIT binary patch
literal 1283
zcmV+e1^oJnP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002O
z!_bzQ(SL){ba>NdYtf>m<hHo#y}#_l#qYwz?#9UP$jI=?%J9p~@y*Wi&(QMG(el&N
z^Viq)tgh&<vFNF*=D4}*zQ66j!R@lN>5`V;n496cyz8c@(AnDc$Hlj+rEHy)MVE~(
zje!}GmC?n;b=BCB+u@bv?y2JJpyBGA(b0>*y>Y_8Zq(O`&Cz|**oW5HjmgV)&CP_q
zyl}CsSnBe)wXlcC#=6+uj;W$#*yDlM;eY7uxXQ|X(bI*zwrqx}WKxY8b*9HQh{34O
zXl={vL%r!uzvx%P>V?$sl-Tfk&g_TN?ycYR5Ub*n)$gj(YI2sLtk-VAk2du2$?)v9
z@9DJj?Z)lovFzcl@ae+p;+*H(mgLov>)x!v$huvZB(B<V=--*+(T?ies-Crnc&$CS
z>3-?jshY`Moz7yq?t<^;!0Fhh?&7_NyG?JLL&vIhi>qwE$G7O#rsmV5>)g5C#f9C%
zgW=4J+`xh5&Y;`Af8@=c4PV1400001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0=h{=K~zY`&6Md=+CUh_IT8h`QXz`eqXw2kLy;n)g4XJmdbG8avTB3Kp(v<$
z)p}e1_3p-?5Zu7nnNFV%o0)f?-@M1}Mx*)X((3dKs}!TxX;p}pR)f)GG8$jBwRd!w
z%`ZDUEmpIMqcggeuI?_I0%_~9**P5z(YJQL>Qz?8^x17DJq=;{x!wU~4cdV|%WIZ~
zu$^Xx5QDtK35c8yeo&)jELJMwVp*3&xINyX;bB4W`rcrMwbBt;yniI{)+-1?FfbY#
z<5dwki^CHb36sj4;gCDzP(xg7TRUOE_;`3?5(g$H!Vxb}L0qhf<BXVz0ua3qO*$hH
zHH5VoIL=<X3)B(ZiOt5=WWh)fsJ71<dhlKp3=LHn5NrmCg5jxYL1vmmz_>6q?R^LC
z+aGBz3y6${r7U>JZlAM>UNjEhXh=TF8Nd2bRuFzH;GTIO2?j%Mzk8N%fEdW$AV22w
zL@?s<g=QOEOV-}=<mI>0-wW-D+06_Mp*`e&BlRIa<9G3lpVBjaeSfJrI7i?75F?V;
zhba6A>5ka^!s61W*yk_H%U@$pB6W_~LOdQ{ip?)B&3~&x5><%OLCKO($?{#QQC=UB
z<Ren&Kr5@OYb)r7D5loYlSrKdt)|oIH6(6iGFem^Z!W)?&s9dPbVz|M5s8^hVH*(_
zo>gwAm@Mw(G_XV%g#!oHwzf7zl+6?hyND182=m3g{rzGd!crMCwGM*d8pU;Vu)TYD
zgz$MJ63OJ|-to!teyLO{m&;J$!1{@Z<P~R0Bq4++r@u~5@V88|)H+C(^;9Z*fR4`2
z&M%~1s%8btQZKa*97stu9$sEvoj**h=5i_>8k8**uFuf<<<-sY<GEC;&VjbCuSwwQ
z=Jy>1Q997kA*u1=_V(_Af+!tz^Qco}$N!-}nbr1E9_U2o0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1oA>oooOA

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_tid_scan.png b/web/pgadmin/misc/static/explain/img/ex_tid_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a6063a1af031c626802911fdca598983aff7d52
GIT binary patch
literal 1074
zcmV-21kL-2P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002d
z;PI2T-@Mf8{{H{L*6o?Q;ibal>hbyT^!nN5^vBrl+3WSI#^#K%-OAhV<Lvi^t=n~>
z)~3be%jEIR<?+4P?Qx#e)#LN7$>)Ek+0f?l*6Q@d;qbxV@5$rvYnjrc!{ox+?t7-#
zwb1FX%;=%P<Hg|a#oq4t`u(HD<awmm>Ezqh$F9h;kiMjKx|(3PkxRstJB*5mfq{XC
zhlh@ij+T~|prD|vtgO7eyv@zc+}zyo@bKZ`;laPVtDlO=oI}p1PuR6w-oa?$%4zD;
zYVFo~;L^J5>E!0-=GocV($dn%$jHFJz_hfqs;a7)nVF-bqn?|W>)wU&>3;X;Y4PG!
z_2EYK-Z;soXy4!8*4Eb0(9p`t%EiUS?d|R1)xgxbiOHZ?)2mYZ=33Can9k15#>U3O
z!^66|x{;BQ>gwv<&a=<QxxT)>xVX5tx3}Kg*UYkn%e$bxy}jh*<eZ$G=;-L1o13t(
zu%DlwudlDArKN?1g_M+(;^{T(00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0qIFZK~zY`?UY$l+E5sVm1q!Z0s&d05EXH$OJzw&f>9JvV-~<xtzpr&mJ)3#
zt^4}dJ;CwH5a3Q{crP>OnfJ;0@};Mzn|G656V%rsV(N#@1DyaC>j%>yg4)_VZtpi^
z4$R~na=AT7>*IZ1AL@sFZhUwc9|;5piB`Y>L|~&3j^krablejP42`#Fv4k9O$mI$;
zF%N;*>=WT2Y&Irr9@rh6L@)yJTD0r+VpD$OYqp`G5qH=Twobo1f&7qh2|N*)`RMCt
z5>^5=+dBb3rrVuclg!S|FML{Dj6`FxSe&F1G{YvB49;`QE2-7B^m-<<5!u}0xots|
z<ZO=2OPoHy%R7<P^ye?Td;4GaGY3ktP$(AJk|>G{P073tp-|<+t)p+>BR|ra<Kx}6
zp9P^(`c;(}RaK?3e4@NYvh3lhmf7D8Y$NrD70Xgh{acr1xh^r;=Ey0}bN}Z4ADn5#
z`7_Z35rLQn@fV!3T@cu3sYXRZOw?(q(U+=l1rYG!LIqTS4wb1%)v`)c*EjlN#riFI
zORLpVMG%CdCdSn7?sHM{I%^=7WY6#Nxs&Jv80AAeN<QWi_cvX!_a&yZ5Yzei!HOA(
z>8=<EC*^g;nl5r9OG%cY6BHS5^LNqB+sG4UmhG=%BJ%?P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$g8msoqyPW_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unique.png b/web/pgadmin/misc/static/explain/img/ex_unique.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb70480be8a4f49369d9a974ce9461284d1a4654
GIT binary patch
literal 1238
zcmV;{1S$K8P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002Z
z#KgUKcgI3Ps~Q@~u&}o&Ddc5l(9X`ss&&DSHnvqK%tl7dN=mayB!;PEQjHmZrCYPX
zru6a2@a(tm>b0~uF5<tRp~YT>vPSFWp6cS9>EWB(wt%)SEA8a5a-%iq-I&LARP5lb
z-DPF(<-exPW%BF9vLz+pz?JIUs&=Y7>DZ_2+_~o1l-8qo)LdNR&yAk7hpyUjc&<L=
z)RExHh~LMC=Fy_RTtmV?KjF-Z=+~yT<a*t~g5%7duOT6OvqIz1kK4U}xG^!fML3PY
zQ|HvA<j$bIJ3G^6X1VKs(35S+RaLw1gT_uymdstaH8rt4A-G~H%wuG_M@zb8Ws;TA
zW^2)iL34~eXwA;?y}#|h!R^Ar?#0IN$jR`_%<--<Hq^DqyuItEspqP!=dQ2mv9s#7
zx9hx}zpN}W)z$R0W_!S=%C9#);NbVg#O%qp*3r@OwTrE~yX?Nd?W<8{vvZEQtk|zV
zN|H5LgGPX=ey69T;D1YtlFETR00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*y&TK~zY`&6N9F(oh)3hs+U!xs^aDm&vUZae{%Mz>t6d6NpNgVwgoYFIh(3
zy7;emwsT-WPoD8Z&*S?eXP>?A`{nFI5QC)~(-9-qn4TPw8K(^79c_;qX;>yRGhrkM
zmgDZ;!yxpe#VVK0K;5Sag0tJFa13pkb~v0)7iDnswA^I|Fc`!t6CSVG?Df&|5A2Mc
z!yy3hc-(%<1rabC4w>YwJny3XHeUcC4=@N!Y=Y67XxiA1bfc8ZIN0SO&+|UgKRXvh
zUD&C47Dj2YfB}5L&;mV(@PZ&Ly2G|eBm_^E<{w2_CCX`sM~Fq1<B1`}C&Xea%&Iq;
zOraAtn&uS5sSu)=8Af8U2<cyQC6^Jh6isJYB}$L*I2jkk+%OUd1{d?;LMoL)BePGk
zEL;+-QB0IFnW0^bN?v7?8Vr}lB^C%K$n~|kVljbU#y!RTm7(NDzETiDp<uWw4oSsV
zYGtpCl;9o9;t`QtCtYGLmn$hZIa_^(yU7b1^-3X8DwULAxGEqp-;0rcUaP<81*7Gw
zBy{A<kv%cHxiuOAn5}W8+b#20LxQz!W_B9d5|f&{uUdPr_um+(w~{uGN$q#<KYaXj
zKp75?ByAo;$g$acEF)SxhLA%)^%){~kHK9IDF4aP7lhzE1{2$1^xKlu=tsIy%GLP0
z%U9sB({2~!v2K*jZ{34|T{`aYNox=7f;@(n{C?Iu7(mXM)eH3mdJKaAk6o%=pvP`N
zn!Bw|K76H)j6C-5UF2YX)XG2AV35adO6;r+Ja($S4C^s4@~?d5s&8bh#{hC(D_^Jx
z-eXexH}KeH7t!J|X}^=NZ1#fD;;{zEtA5=J<`#PF|I{BM#JXLtE~^v(001R)MObuX
zVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=
zI&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f(A@n
A@Bjb+

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unknown.png b/web/pgadmin/misc/static/explain/img/ex_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5d54f5c7b253978b96993ea260828a144395095
GIT binary patch
literal 1088
zcmV-G1i$-<P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-s00025
z!qAqu(2ljybgR*YvC*c#(8$fuxW>>^ozY{g+H$$zaJ}Pq#prd!=Xk~Eea7c~!sL0n
z;BT|sS)tRx%FtS*)o#A!TgB^2zUV@{=s>&YKf2~ayXQu|=uW=pS;FaL#_4Uz>V?ql
zgTmx#uG*ix&}^sCZn)r0!09iw<{+@-AFtyivE(VU<S(@3G`8hBxaB~(<wd*ZOTFe$
zzUOPl=~<%FSE18r!{{2W;}xvqRlw(3!RL9*>ypmuTB6fvyyGgg<`JvnU&H8?(CcKb
z+6}7WW5noy&+LiA<Yc7LR>0^9sp2xV<VL*aXU6Do$?BHZ?`E*vFt_Gl#_Mpx=3d3>
zQNZYfzTthY(P6#i1*qX$pwV}|;(pHSdCBU($<S58=^e1-60GB4s@Sl@&}p>ZUZ&P@
z%j|o{>5S9ub++Dj%<5;W*ml6=ipuD1!|15M&|t;sdd}^2y5K>(=xE66YQW}z$LM~@
z=wip~XUObp%Itj4?KV3{{{R300d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U
z00H_*L_t(Y$L-VoSJF@z2k>#Zm$&&66HySyHefJJgw$z(fRdI186mP%OejOc)GU0-
z>Thp&cTUGnWA_LBjqi^;_jC5#=Xowg_45ER0W(yCfW=C5-iGWBdSGy9_=%GlaUqi-
z7)9=}agTSxH_7@rUI+w3XtcKw!x1Su^>jKm6Hh#wotx+6KyX3qS=8e5Xfl=jOVUex
zCY$SJ(DF(?f1kYIGpjlM+Q3>O|MFk*N?zYUx};D{l{E6&w>hhkH|4iEy=IG*tr8}&
ziR$)Hxu#$u2i^f4va_4qyCc)=fD6L<KCfW%UbnKkRKg@awqsLAR5&PNlF7pVi-x=T
zA@#A_G0)+r?gu!`8IsoWZc$>r-g5o!YDdRuOx8EdI)ya=f(84cqtfc00Its*zP`{t
zvGm*-tI5)xw)yaY^JDU5HB1@DiXyUq6VF9xpNhu9hUnU)HhsCxm`TK0GodYv9AJ>K
zpwO8V$2+zpw@(TFp)QUR#cP=s&KF1o75*B>n>;TB;RT6Mw;+beHz%@@2+C4n@q3xe
zxX1(rB0fx-PLf_>Qk9e@SL44kfRHrYjx{x*Q0S;ZNGK6#A=nd5v?4T3cRC&Bgw()6
z&n4?oZ*z4HNy+sL?wE4ZCuHQu?RfR}=da`6SyFNV?OMnlOFxg0KS=w20#>Z+^Z)<=
zC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!q
zSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMn
GLSTY1nJ@bQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_update.png b/web/pgadmin/misc/static/explain/img/ex_update.png
new file mode 100644
index 0000000000000000000000000000000000000000..a45c53f83a0593704855bb8488871e8e8c9be701
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003#P)t-s00016
zTU$1W!3nA2a+aZzl9IuXHZry4b*9Hbyy(lKSW3X@*Qid%s&!q&>iw+&W5()h$LMIt
z>~72Kb<OPByMEiff7`u@<Gpj-zJT1nfd9QPd(rRQ!GeU*?%l(Kgwyce#Dw0(h2F-7
zjMVPm$A{m@h>_Lq;K_;M%!-%U@#D*$;?9iY%%0@Up5oAr<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp{Fb>D#L7+PCQ5nd;rD?A*BN;F{~+tnA&o>));H-n#7It?J{P?cclX;jZoAyXxef
z?BcKO;k@kQu<hls@8rJi=CbbQv+w1=@aDkp=(F$XweaY|@ae+v?6&di#PaRM^X<m-
z@W|M;qjdlP00DGTPE!Ct=GbNc000SaNLh0L01FcU01FcV0GgZ_0007=Nkl<ZNXPAy
z-B#LA5QQ<eL#ah;5tS;UMbwA_5<v)|fIxsm0YgCuG)2mv@BaclIp+XDLzfq>)m}Ik
z`zl|u_nwKx@;3iqJ}~+$R5OHe*le~9W_M+Eb)VV);&7ZYr@MQ57tF=s@$q-Y6tOKY
zFWZ<Eq^rzltJUgYHW0qY9ImfBj+s~b$~)|Np_(D^I2a5bC)(@vMljMeZ3shERr^4m
z0k9j9!To)$N40l*d;0*Il+U+8O{EeF5Mun>Zl6PH7^x9N(m>1S^C~n`i3##!Jnm*;
zhV*LqVXP05gphXrI;BEA5sz0XlFizcG0d`H|I-vdaf)Ui`bxFjrCB!Z-`K&F`2_lG
zW8HeL^u3$4uQLXA^nuZXrj20OZmUD+*A=A;U0IeplNZl1u(P(dwqr=q)KgrQK@JCl
z;>^E+p@=3)q}Ws)l=#)94014dCK_tO3=TIzaIq>bwt5*3%TPQ!V{>4cXY2w@vpi4H
z5p%f~7?%#4`cmO#jZGLtp*Uy@&3d0|w^~NK=oCe<1FcLZ-GKRfgiQARl7;%8Pr;%T
znUf=*08efhU<o1hL;@WKQ8^qA7C~fLHUsG+pZ6(3LA}y4RZ?t@Jvm83((Co|<_PjH
z|Dvj=XrUD8WCI!k&sR*5kA}v!pSNfJ6<IEJ=yr%Ul7estl~^=#V{VN2CpSP8r8wXr
z>T$a{bA%j*5fQuxo)|>jZr1{YRBA-{1CJ*HoZICJnIoNCF4LOJQi(<*B`9&BQ0dhg
zYo(KD3q<dDK30Jnj^of*LV-ZR+!#3wqPz#EaOo)mPN(zS97+20!yp>N#m*4FIU=Af
z#HdEyIudfZoB=prjNI`t<e#W9MZxd)7yi=N)=U1%xA~vu4`}JQAOw$Lx&QzG07*qo
IM6N<$g4z^C1ONa4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_values_scan.png b/web/pgadmin/misc/static/explain/img/ex_values_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..15b5ab4079cb8e2c015b4f5f10a3dbf8aa6410d3
GIT binary patch
literal 913
zcmV;C18)3@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-siMiW0
zh{12o?m@ljMZW2E&hL59?@+?(R>SIp)9{Ja@MXyCYRT+x%IuWb@tNB3d(Q2G(e0$&
z@u=SNj@0gw*6*+3^985jNWbYDuH#$9>o~XNP`>9?z~@=P=Z4YkYscts$mx~W?|Po8
zrNQK8meIkFHu36*?c-qX<6!dYhx6)(@8e(Z<X`jahxF@*@Z?|d<X`pchw|iK_Unh?
z@Av20E9lxQ=-Mjk+bZnaD(>4V@7pT#<zLt8_37Fw>)R^r+bZweD)8JY^yOdH>GbpD
zU-ji*_v?rF>xbLDiPY)z_T^vG==1U1D)QVa^V}-;<zLa~^7Py)_~l>q+$#9yU-|2Y
z`s|0!=JL+v^7-ap`|O6y<?;31D)-$g_}wY`=3mR?@%G&+`{rND<M8?2DahmS`Rs@M
z?1snV@W$fr{N`W%?1shR@B7{<{N5@3=3n>SDf-<h{^no8;O_n2DgNw+z~An<+wA_{
zDgWkQ)amoS-tDc{=`>ap0ssI20d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_
z00FH@L_t(Y$L-b0a)Lk*24Ek?nFomRW_gGUK~X^z7hFIQqoPPyTuOQNU}g{;#Yj~u
zhm?Jq%hywM|1~5M&-$-bf~P9QA@C)YD!#&4B$dhJ^6-HRlK><UDZb@$)Hi|e6h+aI
z7lL#eAd=5jtC&#LT8)Xk5M;BM1g#-eV_6OzX@=ukmluLU0gw}e6wC6MaC~zhM3C?#
zXpU=qVA33nA0Xiih4FYO5~N%P_eS3qM6pMZN(IEs1gO_R%)p!pq$!4J!B~W0dA{*T
zU_hti6P|CPal)1$2<_;=bi0@Y8wgGI=I8hOm;|k%FdTl$=-8HJWkw8nG`bFqIFuv>
z5m{ALg&>p3bzpku)=*JRlO)sW-M}cOX=((S&+i6irfAxdAd5xpz^qoW1LG6e7Dc)D
zP+)8u6H)Rf`%_@fM3(#wc;Boj%jm#tw?0u-yn)kXbuBtDXA-pbh^`yxxHJr711}%G
z-3~o|;r(g)dX0&(b{s2Az~Sh+#{;pMQ)_F3i9?ViuwB>2PwdUWSk8WbK|JGSEO{p|
n8_Tjb@b)wQYk!?*{c(K(wSJimSdJ6(00000NkvXXu0mjfih%ky

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f858be1cee3f5d8f2b67f55d730e6d3f43e9b42
GIT binary patch
literal 2021
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png b/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..b51e0b3f4c61ba166b69bd7f32cb23dc8428429e
GIT binary patch
literal 1965
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exclude.png b/web/pgadmin/misc/static/explain/img/exclude.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd62eef6410d9315857d2e6d246770e8b65b8f47
GIT binary patch
literal 725
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47y|-)LR^8|wi}<ymTvVw_QrY1
zLC3?d^*UDhFWLA1|9`)wM`KREbvXQ5A-=(6%9fR9ZhU_6^4^pwS28lLWMw^=HS5>=
z_isLZP1<@%wQ`Qjk=K%*nNgG09JqAnY+~Zm($d$dsZT2^E~KQ~e)?Lsd6{J1L`ko#
z8QYH^XzhAZSorVPuP+-md|tBT-`~HFYif>6nkJPuNuqp?YIOD56Ib^8`rV6+d^K<0
zzkmPUtXQ!pGFqZ!hGg3swe*g4XK!wIaz5qe_HfFSrwbQu^AC`0S|K@Qk7Uk-*~hPM
zEhsq<82I=1?;BI6UhV1m_vg>S@bIm*jgk|0NG?2Of9R!T^@7Cpr%znEwc5~Vm%sly
zUEQ5NKC5l*c3rqFx8Q{2nrn7@9?A4<w%>5&&c`3`Z{FOOoV;9Hdxfs<!JM4Ox9?6m
zenWfBW$A4XY?hzYUUH^<`Bv54PXhWDq@H_cwegx-ao6F7A05CjWh@Eu3ubV5b|VeM
zN%D4g;b^-zwF=1LEbxdd2GSm2>~=ES4#-&V>Eak7aXC5R07H*Y2Gbdx45l?XZ+LiQ
zWMmY~)Ws(}c=qt=V{riyAu&Nw;pq&V9$ucOPn<e=Qd>hyv$&ZhB;@K9Q<JS*N=v?e
zImpD;=5|bN*|M}_&%~xBFluK@M_UI6S4XqEt8Zx7+`W4C?)K%=xA(7?_c&m$V4z{4
zVq&6Wqh!RTA|NX)Ek1w3j45*_&6>t1bmBw`i*--4qPDWM%7n?><t2V{=CjN{U1V)w
zV3-jtUHUai{xs0(swJ)wB`Jv|saDBFsX&Us$iUE0*8qr2Lkumf3@ojTjkFEStPBi}
hcbK)IXvob^$xN%ntzp~MJ}aOG22WQ%mvv4FO#q89Jv#sZ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension-sm.png b/web/pgadmin/misc/static/explain/img/extension-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..432d2f44231c1f88e787091060efc5125d80ef64
GIT binary patch
literal 423
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}QGic~E0BJDv+cp5$Qv6qzrVk7
zW3&9@vzBik`G5Ho|K-)uTicc2-1GkNGyU0x%oo?3fBedPak1gnHlr`^;(z|izO~Kb
z%lm|1zjI$+bN~H2|Mm{mcMpR9{3*J*S>f3^`;Si}e|^pQ`={jETDdE$#Qyy)dv><$
z|KAGswyocQwlbCk`2{mLJiCzw<Zu>vL>2>S4={E+nQaGT<aoL`hDcoQ?f2wsP!M2o
z7l{eDQ+~(c|9@BE84^1Kx361bY|Bu`qQb-J62DBxZ_<p5BE`pwna}6wxpa1%>e!Vz
zahdU=*X&ESFOpJE*&;e)`qd3*DpaT5u9JDLS%3P((k)@<ZbaLzV+h!Of4=?`KK92q
z=3fE@l4^--L`h0wNvc(HQ7VvPFfuSS&^0vDH82b@GO#i+wlXo*HZZj^FqrpFZxxD$
o-29Zxv`UBu152<5plTB<12c$*Q`1A&05vdpy85}Sb4q9e01}k2!2kdN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension.png b/web/pgadmin/misc/static/explain/img/extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c533347883fc1dec73bcb21b32fc54e3c31732
GIT binary patch
literal 996
zcmV<A0~`E_P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%`cO<%MQ2zLf_6I6&9|zdd99>-@a*2m!Junh7N)|bRf|-bx|zVboSKha_w(lG
z;mc-L4XVVcQjb)R!IoN>UTm6cdTlP+)48aeY_F(+!qvh)fIq$I$ak@QM1Demb2p-y
zZNIpg%*U?Z+|0eUmR?Q)@#@yLtA3u9XFhv8mdv0tb~C!Ok)4xckc3Wsa4?*aUD?pH
z@afX-<;s|gP-<HecxWLITMn+{ye)Dq{QLL&`11Mo?Dg>A^X%E{;lk+My5`xk<JGC)
z(V?J^Pa0qr9%URUZYXJ}aH`|EXsd7=V;MkvKHbcisFznYY9*e@qsi^kq0p+c;k~!#
z!?5MPqv5li->#6?p?1NAale3n)0NuDkgb|nMQ$vEwur;)&X(P%klLe(*qwsbn0(Ze
z&%lqBg+*3S1ZY?dnutTImr{GNfpyZ2etkM9XCNqTCtr9wihVOAV;iN_u6D(TK#xcf
zTn`v!7(bOtw4`b=XCQ>Lht=`it<|(#mtSF?W<QQcm$;T>m1BZ;G0*SUx8%WGt7&4B
zV3D+tb8RIwgF3Luu#bN^7G4v==*yVRp-_@kjmeoSdoaG!zSzWv!K`UHUKC!MVuZSj
zaIAJVdpAyF9vxyClh&g+jY8GGe$u;klYuouT@*iVD?w)>Y;7EsekVC?DRR1gZ_tQZ
zyKv99ZOyY~z@}84hBK{_JHn(!l6fLVbTnhFZfMSfV$6Lul0qhGC2*Z?NP<TvhBL~q
zUB|0c#ivccpg)Ro98G#WT*`P>$a6}(W;2>aI;l}Wz+t|fIeljlScFSfreaXWZ%f2#
zMZ#t~zg?k^OOAUphI1*4cr3V}Qns8&igY1@Y#D@Y7H?b$T8UCtsbn_2S%i2!rk!)h
z!>EaMCcvamyR?(W!KK8$qF<3+9BCaAUJ)8<9X6LmEss1DV->ZTL3e2=8EP9)hfaum
zLy>wXERsEdsDEo(3zmE$ex`jsyImG&7=LOOfol~TcO*%INEmJ%7i}B|YRn1%0004W
zQchC<K<3zH00001VoOIv0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW
z0~-xG4!&HBF8}}lR!KxbR2b7^U?3j2xS;VvLXbtwTz~>0W=QhVA)yH_FkL{}1;huk
zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<<OZkC{A-h<6}A<CjbDaP8qDe
SR_4e60000<MNUMnLSTaX-^L37

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extensions.png b/web/pgadmin/misc/static/explain/img/extensions.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed7ca97a33ef595f448b8621168d531f86d51d5
GIT binary patch
literal 1017
zcmV<V0|xwwP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%_fSk!MS^xZ(#^N3p?R&Od+_Yu$ibj%T^6RorB#bmn!1_5x}2JiUH9|m=i$p{
zRt>7esZx(rkHMB&m|kp}YkF-i+S9qHoNTYCfx^|oK7c>H>Bx7neMEjjfO9vZnQgzg
znaszo-`vc-ww7K_0rBeAwyS=gm1jPCJeJI$Gj=n&vyq*XV~~VSeQ+?GkzLu)v+(KC
z?&Zpui%@D?5_o7K5L*te<Gd|$E&TiU`}p$t_U!fW;PdR+>*2!a-MZ%4vg6gM;L)L=
zk53w479M3BDQ+lfsBo&|xoE3!8)F$jd_LXGn5dUmHEJcE$)m~b(xK3*v*EqB=fkk&
zzN6u@o!_pI*P(X7g>k=tf76xP$dIj?SVe9ugSLpn>&}+lsF2#DiP)Wj)|h<Mlh449
zm4!uCPy}dL4Vs8UtCv!Hv4M5cjedPPC}$ujZ6{xNJBocXBx4(;)vk8Mhd_@=5nK-#
zWf(t|O0=YEF=rrzvxn93+^yBLT$f*Ao@PIeN0+#kWR+urcQMcJ*SF-sT&rnflVFjw
zkaKM%G=n;@$*_-qITl_M!syGG&7n||RE^1*D|;}$)4tfmhrz6AIbIZAnqq{yi*T%V
zHhVWtV;&u18I#tdIE_NpzkbrYc9VfMLtPX<ZYx1&B5Z9Om3}8VZ7FiPes9o-TDx%1
zw{6X{Wx%FXorW{5k~_krMv{3VMsze|t!`+}gJR5mHj+XnY9(-;Zb*VhCx$c1uU*Hh
zRmG=Gz@R^havV*1JY33nR>*Toyk;|+MLMZbK)_+XojHAH5m<yvR;FT5#&1i+YDL0k
zJHK6_k4uhwGlp|1i+C)!pi;J+M~ZYIf@~RtZ5D4_30jF#R;gq*y;+2KJ*J&=$it|K
zbtb^1PrI~}#=)h;zM@}|T^wm05?&D+Y8^J0MJ<m!6k`>&nL&4HC>d%SP=`*4d_$3X
zCoGaZfT(|KS__tZB7UZQKD%8OXc&KL7J+LO8+Rm0f=C!{9T#mJ7UDFE00001bW%=J
z06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru+XEXB
z6Cp(Rbcp}}0B%V{K~xyiU68R2z#t3+nS(tt!3~hY^vGg42FL=`EENNm0{M!6-y1;)
zQxFazvL_cK*b<_okFg0d8Hc#V=Eh`)h+yZmplehnlNZEdtgQ@hX0CNeIWEpxw!oyN
ndc~tsp9TlidOzOC&-)*|KH)3pD2ngr00000NkvXXu0mjf<KNUz

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable-sm.png b/web/pgadmin/misc/static/explain/img/exttable-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..acd43cb8abab6ef38daf34ff6d7544153a11dacf
GIT binary patch
literal 612
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47`X#{LR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)^XY$8$A0G@_?f)(TiB-0o~u9DE`4h_
z@3s2Om(8dDdI7mh-fGNz8JxJ$(RuCl>$mUSyLbQo{YQ@;J%0Rn>9T9)mKA9e*Bv-;
z!q9k*y7s}c^63vAJOIkOd8|>@*q@f(clYkyWy_XDM0Trbo^<nAscUdhS#3sp`^5F@
z*S~u8DlvJNy7pxi^{qR09)J1r<?Gk4&!4|-WV%sNW#!y?Ya^o8sA?W7EL!yO<Hxse
z-}X<~s-}6=(71d4{Dn`SK5cATs;+%VU9<bdiL;%Z6I)tVJ$drv&6_t59=vw<+ErTC
zQw{Rh4~>~Gc%JJ_00swRNswPKgTu2MX+REVfk$L9koEv$x0Bg+K*j`57sn8Z%gG4}
zOa@Af9Sp+8+}hIC?Ck2|<}MBG)BEEc0z4vIL{>Gq^Qb83DJpS_PM9=p;?&9E0U<$c
zTq{<rTA7`ZmHE2Bfwko87hay;9$%lc3z#+)6+IK17IrPoEP&B6+Pa$ET|Heqd_}|T
z-R;Y#Z{6O%UOwc2y~2g=!)h89Dk9JL?rkX8V67z4`B96(W2)e}i60vRfNoPQag8WR
zNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTd
cHGouG8JIydoSGiG2B?9-)78&qol`;+0LVQH?EnA(

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable.png b/web/pgadmin/misc/static/explain/img/exttable.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d84035feea62d53d457481178b1bcbe9622f856
GIT binary patch
literal 793
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47;6K3LR^7d#i`GGFZ`c${`aEG
zUw7Sjd-49u`;VVLefIp>vuC#+K3#L}dGf{=db3}2pa0)_=3m{3Kc$C%<?Q>BxZ_*c
zrqAAMJ~%9YXTIQ#?wnU@GhViy`J27(hvTw$W((iw&U_Y~)1HyF{qEhn_wL=hfB*i2
z2M-=TeE8_mqsNaQ8yXiV>iW%}zd>1TV^GL$BjYL^{S(WU9oVt+prXnmSNFZkm#=*C
z<jL~o%k>TF^o@>PyZ-vst5*{zPLflcU|_QU>a|B#uU=JF?bJ2cziipY_3PKae*OB&
zm8%-sEqaEB)ikG?m>;=w=lPpAZ{EIr`|{<>zP<^HDjPJkuS}Y>_5J(zA3l8e^y$;P
zckfoNT)BGn>N#`fDXPqIbUAEeo3EjBs<&_JrcIlkKYzY=?-6CSwcft_U%!6i?2@ga
zbAH+K$Dcoc{`&RnzI`WD)Hj5LAKkcd)7Gt9lT#YiwN7;R-um|K+wv9LmDM(Tcy0Rl
z@#D5_+u{=nRW-JDc3)AL{8UY|HzVudq)BroPg$ri@$mwIO;dmoz*rLG7tG-B>_!@p
z!&%@FSq!8-z}W3%wjGdh+0(@_MB;LCLV^n;4-Zd|&l#OHId5d<%!#>Uvqyo^u8z@B
zF;Otku#nSp1DBm9qhscRhMqMtE)H>yfu51Bp}w)6&cZqimabjQ@@VyoS1+4cMR<97
zd#?KUoIRtfbCAhvS=cqRZDrs1`uxr%FfOPQ4>vF8_xIM%-_X$2-@kPI{CbCkfC7#P
z2U8Or8zmzxD>XAS4xIy%;`1lWcrs_sq*>GW#5zu&ICF}R^VG?+r}Y`QMZ`qYkF!mE
zGG*G-@CFvG)vNY8H+eN(zQmoCbx`Y7z#e{vFBY0Te&TE7f!<Rsag8WRNi0dVN-jzT
zQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTdHGouG8JIyd
UoSGiG2B?9-)78&qol`;+0Q{<O$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttables.png b/web/pgadmin/misc/static/explain/img/exttables.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5dfbd454ed33727bd1deeb87c62f8671f6a5a03
GIT binary patch
literal 662
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47^MPyLR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)(1uT&ufDx|_ipp)e^tkR=O6f)yz^Vw
zrq7<MKiDpPYdG(<`plR2?%iuX{nra9vgEDdjF%cSU*5le-z{*Hjoqv(SFS&L^yu;9
z$BCh54YV)SY`ps5!GohmFPT|vP}AIAUOws6@q1>vXAHE?^>wU&`SRt%hYy!6TNV*D
zT}|t>v;9UL?W?Amt7goYwSN8jSFc`8IQ7}h=Aef5HBHT(=Pz7){rdI(-PetE&Kqf+
zTC?J?x9>_dt@Dk|J3fB=`1bAF+~nQb+SeRSH>_B(;_1_;Rn;?8HFg`BEWUQ_zJ=bg
zri$H9o;-Q;=FOu=PwX9+YwPW<2KnxXa>IqjgMEvDp~F}b<QL4~@a#q!ki%Kv5m^kR
zJ;2!QWVRiUvDwqbF+}2W?0HwQLk<G27xhdW8XmB4zY~jTVks}b`QPk?8Z+O@ht}mE
zxDUR+@ZDiy$V$_wu1ha@gBJAuXUmw$IQdLNWLm+2wv7?FW$l-rva#qG$_eeUt7l;6
zYfsQPQ#3I{XWjwDPG7CLC)J97I!!#j(8XtG(AulgXTzi?C<q+hI5%(o`R%TodS@Ig
zFq!9*{+X5YhEDkFv(xUs<#&JSY%y0q+H23fcl??hCZB62%C0l7Vc_x-dugW}eiG<>
z)e_f;l9a@fRIB8oR3OD*WMF8ZYiOivU>IU#U}a)#Wn!XjU}|MxU@=ow4n;$5eoAIq
iB}9XPC0GMUwUvPxM8m1+p=*E|7(8A5T-G@yGywoZHZsuw

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/js/snap.svg-min.js b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
new file mode 100644
index 0000000..6567d19
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
@@ -0,0 +1,21 @@
+// Snap.svg 0.4.1
+//
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// build: 2015-04-13
+
+!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g=/\s*,\s*/,h="*",i=function(a,b){return a-b},j={n:{}},k=function(){for(var a=0,b=this.length;b>a;a++)if("undefined"!=typeof this[a])return this[a]},l=function(){for(var a=this.length;--a;)if("undefined"!=typeof this[a])return this[a]},m=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=m.listeners(a),j=0,n=[],o={},p=[],q=b;p.firstDefined=k,p.lastDefined=l,b=a,c=0;for(var r=0,s=h.length;s>r;r++)"zIndex"in h[r]&&(n.push(h[r].zIndex),h[r].zIndex<0&&(o[h[r].zIndex]=h[r]));for(n.sort(i);n[j]<0;)if(e=o[n[j++]],p.push(e.apply(d,g)),c)return c=f,p;for(r=0;s>r;r++)if(e=h[r],"zIndex"in e)if(e.zIndex==n[j]){if(p.push(e.apply(d,g)),c)break;do if(j++,e=o[n[j]],e&&p.push(e.apply(d,g)),c)break;while(e)}else o[e.zIndex]=e;else if(p.push(e.apply(d,g)),c)break;return c=f,b=q,p};m._events=j,m.listeners=function(a){var b,c,d,e,g,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,g=m.length;g>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[h]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},m.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(g),d=0,e=c.length;e>d;d++)!function(a){for(var c,d=a.split(f),e=j,g=0,h=d.length;h>g;g++)e=e.n,e=e.hasOwnProperty(d[g])&&e[d[g]]||(e[d[g]]={n:{}});for(e.f=e.f||[],g=0,h=e.f.length;h>g;g++)if(e.f[g]==b){c=!0;break}!c&&e.f.push(b)}(c[d]);return function(a){+a==+a&&(b.zIndex=+a)}},m.f=function(a){var b=[].slice.call(arguments,1);return function(){m.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},m.stop=function(){c=1},m.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},m.nts=function(){return b.split(f)},m.off=m.unbind=function(a,b){if(!a)return void(m._events=j={n:{}});var c=a.split(g);if(c.length>1)for(var d=0,i=c.length;i>d;d++)m.off(c[d],b);else{c=a.split(f);var k,l,n,d,i,o,p,q=[j];for(d=0,i=c.length;i>d;d++)for(o=0;o<q.length;o+=n.length-2){if(n=[o,1],k=q[o].n,c[d]!=h)k[c[d]]&&n.push(k[c[d]]);else for(l in k)k[e](l)&&n.push(k[l]);q.splice.apply(q,n)}for(d=0,i=q.length;i>d;d++)for(k=q[d];k.n;){if(b){if(k.f){for(o=0,p=k.f.length;p>o;o++)if(k.f[o]==b){k.f.splice(o,1);break}!k.f.length&&delete k.f}for(l in k.n)if(k.n[e](l)&&k.n[l].f){var r=k.n[l].f;for(o=0,p=r.length;p>o;o++)if(r[o]==b){r.splice(o,1);break}!r.length&&delete k.n[l].f}}else{delete k.f;for(l in k.n)k.n[e](l)&&k.n[l].f&&delete k.n[l].f}k=k.n}}},m.once=function(a,b){var c=function(){return m.unbind(a,c),b.apply(this,arguments)};return m.on(a,c)},m.version=d,m.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=m:"function"==typeof define&&define.amd?define("eve",[],function(){return m}):a.eve=m}(this),function(a,b){if("function"==typeof define&&define.amd)define(["eve"],function(c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("eve");module.exports=b(a,c)}else b(a,a.eve)}(window||this,function(a,b){var c=function(b){var c={},d=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},e=Array.isArray||function(a){return a instanceof Array||"[object Array]"==Object.prototype.toString.call(a)},f=0,g="M"+(+new Date).toString(36),h=function(){return g+(f++).toString(36)},i=Date.now||function(){return+new Date},j=function(a){var b=this;if(null==a)return b.s;var c=b.s-a;b.b+=b.dur*c,b.B+=b.dur*c,b.s=a},k=function(a){var b=this;return null==a?b.spd:void(b.spd=a)},l=function(a){var b=this;return null==a?b.dur:(b.s=b.s*a/b.dur,void(b.dur=a))},m=function(){var a=this;delete c[a.id],a.update(),b("mina.stop."+a.id,a)},n=function(){var a=this;a.pdif||(delete c[a.id],a.update(),a.pdif=a.get()-a.b)},o=function(){var a=this;a.pdif&&(a.b=a.get()-a.pdif,delete a.pdif,c[a.id]=a)},p=function(){var a,b=this;if(e(b.start)){a=[];for(var c=0,d=b.start.length;d>c;c++)a[c]=+b.start[c]+(b.end[c]-b.start[c])*b.easing(b.s)}else a=+b.start+(b.end-b.start)*b.easing(b.s);b.set(a)},q=function(){var a=0;for(var e in c)if(c.hasOwnProperty(e)){var f=c[e],g=f.get();a++,f.s=(g-f.b)/(f.dur/f.spd),f.s>=1&&(delete c[e],f.s=1,a--,function(a){setTimeout(function(){b("mina.finish."+a.id,a)})}(f)),f.update()}a&&d(q)},r=function(a,b,e,f,g,i,s){var t={id:h(),start:a,end:b,b:e,s:0,dur:f-e,spd:1,get:g,set:i,easing:s||r.linear,status:j,speed:k,duration:l,stop:m,pause:n,resume:o,update:p};c[t.id]=t;var u,v=0;for(u in c)if(c.hasOwnProperty(u)&&(v++,2==v))break;return 1==v&&d(q),t};return r.time=i,r.getById=function(a){return c[a]||null},r.linear=function(a){return a},r.easeout=function(a){return Math.pow(a,1.7)},r.easein=function(a){return Math.pow(a,.48)},r.easeinout=function(a){if(1==a)return 1;if(0==a)return 0;var b=.48-a/1.04,c=Math.sqrt(.1734+b*b),d=c-b,e=Math.pow(Math.abs(d),1/3)*(0>d?-1:1),f=-c-b,g=Math.pow(Math.abs(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},r.backin=function(a){if(1==a)return 1;var b=1.70158;return a*a*((b+1)*a-b)},r.backout=function(a){if(0==a)return 0;a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},r.elastic=function(a){return a==!!a?a:Math.pow(2,-10*a)*Math.sin(2*(a-.075)*Math.PI/.3)+1},r.bounce=function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b},a.mina=r,r}("undefined"==typeof b?function(){}:b),d=function(a){function c(a,b){if(a){if(a.nodeType)return w(a);if(e(a,"array")&&c.set)return c.set.apply(c,a);if(a instanceof s)return a;if(null==b)return a=y.doc.querySelector(String(a)),w(a)}return a=null==a?"100%":a,b=null==b?"100%":b,new v(a,b)}function d(a,b){if(b){if("#text"==a&&(a=y.doc.createTextNode(b.text||b["#text"]||"")),"#comment"==a&&(a=y.doc.createComment(b.text||b["#text"]||"")),"string"==typeof a&&(a=d(a)),"string"==typeof b)return 1==a.nodeType?"xlink:"==b.substring(0,6)?a.getAttributeNS(T,b.substring(6)):"xml:"==b.substring(0,4)?a.getAttributeNS(U,b.substring(4)):a.getAttribute(b):"text"==b?a.nodeValue:null;if(1==a.nodeType){for(var c in b)if(b[z](c)){var e=A(b[c]);e?"xlink:"==c.substring(0,6)?a.setAttributeNS(T,c.substring(6),e):"xml:"==c.substring(0,4)?a.setAttributeNS(U,c.substring(4),e):a.setAttribute(c,e):a.removeAttribute(c)}}else"text"in b&&(a.nodeValue=b.text)}else a=y.doc.createElementNS(U,a);return a}function e(a,b){return b=A.prototype.toLowerCase.call(b),"finite"==b?isFinite(a):"array"==b&&(a instanceof Array||Array.isArray&&Array.isArray(a))?!0:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||J.call(a).slice(8,-1).toLowerCase()==b}function f(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=f(a[c]));return b}function h(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function i(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),g=d.cache=d.cache||{},i=d.count=d.count||[];return g[z](f)?(h(i,f),c?c(g[f]):g[f]):(i.length>=1e3&&delete g[i.shift()],i.push(f),g[f]=a.apply(b,e),c?c(g[f]):g[f])}return d}function j(a,b,c,d,e,f){if(null==e){var g=a-c,h=b-d;return g||h?(180+180*D.atan2(-h,-g)/H+360)%360:0}return j(a,b,e,f)-j(c,d,e,f)}function k(a){return a%360*H/180}function l(a){return 180*a/H%360}function m(a){var b=[];return a=a.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g,function(a,c,d){return d=d.split(/\s*,\s*|\s+/),"rotate"==c&&1==d.length&&d.push(0,0),"scale"==c&&(d.length>2?d=d.slice(0,2):2==d.length&&d.push(0,0),1==d.length&&d.push(d[0],0,0)),b.push("skewX"==c?["m",1,0,D.tan(k(d[0])),1,0,0]:"skewY"==c?["m",1,D.tan(k(d[0])),0,1,0,0]:[c.charAt(0)].concat(d)),a}),b}function n(a,b){var d=ab(a),e=new c.Matrix;if(d)for(var f=0,g=d.length;g>f;f++){var h,i,j,k,l,m=d[f],n=m.length,o=A(m[0]).toLowerCase(),p=m[0]!=o,q=p?e.invert():0;"t"==o&&2==n?e.translate(m[1],0):"t"==o&&3==n?p?(h=q.x(0,0),i=q.y(0,0),j=q.x(m[1],m[2]),k=q.y(m[1],m[2]),e.translate(j-h,k-i)):e.translate(m[1],m[2]):"r"==o?2==n?(l=l||b,e.rotate(m[1],l.x+l.width/2,l.y+l.height/2)):4==n&&(p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.rotate(m[1],j,k)):e.rotate(m[1],m[2],m[3])):"s"==o?2==n||3==n?(l=l||b,e.scale(m[1],m[n-1],l.x+l.width/2,l.y+l.height/2)):4==n?p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.scale(m[1],m[1],j,k)):e.scale(m[1],m[1],m[2],m[3]):5==n&&(p?(j=q.x(m[3],m[4]),k=q.y(m[3],m[4]),e.scale(m[1],m[2],j,k)):e.scale(m[1],m[2],m[3],m[4])):"m"==o&&7==n&&e.add(m[1],m[2],m[3],m[4],m[5],m[6])}return e}function o(a){var b=a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||a.node.parentNode&&w(a.node.parentNode)||c.select("svg")||c(0,0),d=b.select("defs"),e=null==d?!1:d.node;return e||(e=u("defs",b.node).node),e}function p(a){return a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||c.select("svg")}function q(a,b,c){function e(a){if(null==a)return I;if(a==+a)return a;d(j,{width:a});try{return j.getBBox().width}catch(b){return 0}}function f(a){if(null==a)return I;if(a==+a)return a;d(j,{height:a});try{return j.getBBox().height}catch(b){return 0}}function g(d,e){null==b?i[d]=e(a.attr(d)||0):d==b&&(i=e(null==c?a.attr(d)||0:c))}var h=p(a).node,i={},j=h.querySelector(".svg---mgr");switch(j||(j=d("rect"),d(j,{x:-9e9,y:-9e9,width:10,height:10,"class":"svg---mgr",fill:"none"}),h.appendChild(j)),a.type){case"rect":g("rx",e),g("ry",f);case"image":g("width",e),g("height",f);case"text":g("x",e),g("y",f);break;case"circle":g("cx",e),g("cy",f),g("r",e);break;case"ellipse":g("cx",e),g("cy",f),g("rx",e),g("ry",f);break;case"line":g("x1",e),g("x2",e),g("y1",f),g("y2",f);break;case"marker":g("refX",e),g("markerWidth",e),g("refY",f),g("markerHeight",f);break;case"radialGradient":g("fx",e),g("fy",f);break;case"tspan":g("dx",e),g("dy",f);break;default:g(b,e)}return h.removeChild(j),i}function r(a){e(a,"array")||(a=Array.prototype.slice.call(arguments,0));for(var b=0,c=0,d=this.node;this[b];)delete this[b++];for(b=0;b<a.length;b++)"set"==a[b].type?a[b].forEach(function(a){d.appendChild(a.node)}):d.appendChild(a[b].node);var f=d.childNodes;for(b=0;b<f.length;b++)this[c++]=w(f[b]);return this}function s(a){if(a.snap in V)return V[a.snap];var b;try{b=a.ownerSVGElement}catch(c){}this.node=a,b&&(this.paper=new v(b)),this.type=a.tagName||a.nodeName;var d=this.id=S(this);if(this.anims={},this._={transform:[]},a.snap=d,V[d]=this,"g"==this.type&&(this.add=r),this.type in{g:1,mask:1,pattern:1,symbol:1})for(var e in v.prototype)v.prototype[z](e)&&(this[e]=v.prototype[e])}function t(a){this.node=a}function u(a,b){var c=d(a);b.appendChild(c);var e=w(c);return e}function v(a,b){var c,e,f,g=v.prototype;if(a&&"svg"==a.tagName){if(a.snap in V)return V[a.snap];var h=a.ownerDocument;c=new s(a),e=a.getElementsByTagName("desc")[0],f=a.getElementsByTagName("defs")[0],e||(e=d("desc"),e.appendChild(h.createTextNode("Created with Snap")),c.node.appendChild(e)),f||(f=d("defs"),c.node.appendChild(f)),c.defs=f;for(var i in g)g[z](i)&&(c[i]=g[i]);c.paper=c.root=c}else c=u("svg",y.doc.body),d(c.node,{height:b,version:1.1,width:a,xmlns:U});return c}function w(a){return a?a instanceof s||a instanceof t?a:a.tagName&&"svg"==a.tagName.toLowerCase()?new v(a):a.tagName&&"object"==a.tagName.toLowerCase()&&"image/svg+xml"==a.type?new v(a.contentDocument.getElementsByTagName("svg")[0]):new s(a):a}function x(a,b){for(var c=0,d=a.length;d>c;c++){var e={type:a[c].type,attr:a[c].attr()},f=a[c].children();b.push(e),f.length&&x(f,e.childNodes=[])}}c.version="0.4.0",c.toString=function(){return"Snap v"+this.version},c._={};var y={win:a.window,doc:a.window.document};c._.glob=y;{var z="hasOwnProperty",A=String,B=parseFloat,C=parseInt,D=Math,E=D.max,F=D.min,G=D.abs,H=(D.pow,D.PI),I=(D.round,""),J=Object.prototype.toString,K=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,L=(c._.separator=/[,\s]+/,/[\s]*,[\s]*/),M={hs:1,rg:1},N=/([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,O=/([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,P=/(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/gi,Q=0,R="S"+(+new Date).toString(36),S=function(a){return(a&&a.type?a.type:I)+R+(Q++).toString(36)},T="http://www.w3.org/1999/xlink",U="http://www.w3.org/2000/svg",V={};c.url=function(a){return"url('#"+a+"')"}}c._.$=d,c._.id=S,c.format=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return A(b).replace(a,function(a,b){return c(a,b,d)})}}(),c._.clone=f,c._.cacher=i,c.rad=k,c.deg=l,c.sin=function(a){return D.sin(c.rad(a))},c.tan=function(a){return D.tan(c.rad(a))},c.cos=function(a){return D.cos(c.rad(a))},c.asin=function(a){return c.deg(D.asin(a))},c.acos=function(a){return c.deg(D.acos(a))},c.atan=function(a){return c.deg(D.atan(a))},c.atan2=function(a){return c.deg(D.atan2(a))},c.angle=j,c.len=function(a,b,d,e){return Math.sqrt(c.len2(a,b,d,e))},c.len2=function(a,b,c,d){return(a-c)*(a-c)+(b-d)*(b-d)},c.closestPoint=function(a,b,c){function d(a){var d=a.x-b,e=a.y-c;return d*d+e*e}for(var e,f,g,h,i=a.node,j=i.getTotalLength(),k=j/i.pathSegList.numberOfItems*.125,l=1/0,m=0;j>=m;m+=k)(h=d(g=i.getPointAtLength(m)))<l&&(e=g,f=m,l=h);for(k*=.5;k>.5;){var n,o,p,q,r,s;(p=f-k)>=0&&(r=d(n=i.getPointAtLength(p)))<l?(e=n,f=p,l=r):(q=f+k)<=j&&(s=d(o=i.getPointAtLength(q)))<l?(e=o,f=q,l=s):k*=.5}return e={x:e.x,y:e.y,length:f,distance:Math.sqrt(l)}},c.is=e,c.snapTo=function(a,b,c){if(c=e(c,"finite")?c:10,e(a,"array")){for(var d=a.length;d--;)if(G(a[d]-b)<=c)return a[d]}else{a=+a;var f=b%a;if(c>f)return b-f;if(f>a-c)return b-f+a}return b},c.getRGB=i(function(a){if(!a||(a=A(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:Z};if(!(M[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=W(a)),!a)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};var b,d,f,g,h,i,j=a.match(K);return j?(j[2]&&(f=C(j[2].substring(5),16),d=C(j[2].substring(3,5),16),b=C(j[2].substring(1,3),16)),j[3]&&(f=C((h=j[3].charAt(3))+h,16),d=C((h=j[3].charAt(2))+h,16),b=C((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=B(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),f=B(i[2]),"%"==i[2].slice(-1)&&(f*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsb2rgb(b,d,f,g)):j[6]?(i=j[6].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsl2rgb(b,d,f,g)):(b=F(D.round(b),255),d=F(D.round(d),255),f=F(D.round(f),255),g=F(E(g,0),1),j={r:b,g:d,b:f,toString:Z},j.hex="#"+(16777216|f|d<<8|b<<16).toString(16).slice(1),j.opacity=e(g,"finite")?g:1,j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z}},c),c.hsb=i(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=i(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=i(function(a,b,c,d){if(e(d,"finite")){var f=D.round;return"rgba("+[f(a),f(b),f(c),+d.toFixed(2)]+")"}return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)});var W=function(a){var b=y.doc.getElementsByTagName("head")[0]||y.doc.getElementsByTagName("svg")[0],c="rgb(255, 0, 0)";return(W=i(function(a){if("red"==a.toLowerCase())return c;b.style.color=c,b.style.color=a;var d=y.doc.defaultView.getComputedStyle(b,I).getPropertyValue("color");return d==c?null:d}))(a)},X=function(){return"hsb("+[this.h,this.s,this.b]+")"},Y=function(){return"hsl("+[this.h,this.s,this.l]+")"},Z=function(){return 1==this.opacity||null==this.opacity?this.hex:"rgba("+[this.r,this.g,this.b,this.opacity]+")"},$=function(a,b,d){if(null==b&&e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&e(a,string)){var f=c.getRGB(a);a=f.r,b=f.g,d=f.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},_=function(a,b,d,f){a=D.round(255*a),b=D.round(255*b),d=D.round(255*d);var g={r:a,g:b,b:d,opacity:e(f,"finite")?f:1,hex:c.rgb(a,b,d),toString:Z};return e(f,"finite")&&(g.opacity=f),g};c.color=function(a){var b;return e(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):e(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):(e(a,"string")&&(a=c.getRGB(a)),e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&!("error"in a)?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1,a.error=1)),a.toString=Z,a},c.hsb2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var f,g,h,i,j;return a=a%360/60,j=c*b,i=j*(1-G(a%2-1)),f=g=h=c-j,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.hsl2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var f,g,h,i,j;return a=a%360/60,j=2*b*(.5>c?c:1-c),i=j*(1-G(a%2-1)),f=g=h=c-j/2,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.rgb2hsb=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=E(a,b,c),g=f-F(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:X}},c.rgb2hsl=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=E(a,b,c),h=F(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:Y}},c.parsePathString=function(a){if(!a)return null;var b=c.path(a);if(b.arr)return c.path.clone(b.arr);var d={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},f=[];return e(a,"array")&&e(a[0],"array")&&(f=c.path.clone(a)),f.length||A(a).replace(N,function(a,b,c){var e=[],g=b.toLowerCase();if(c.replace(P,function(a,b){b&&e.push(+b)}),"m"==g&&e.length>2&&(f.push([b].concat(e.splice(0,2))),g="l",b="m"==b?"l":"L"),"o"==g&&1==e.length&&f.push([b,e[0]]),"r"==g)f.push([b].concat(e));else for(;e.length>=d[g]&&(f.push([b].concat(e.splice(0,d[g]))),d[g]););}),f.toString=c.path.toString,b.arr=c.path.clone(f),f};var ab=c.parseTransformString=function(a){if(!a)return null;var b=[];return e(a,"array")&&e(a[0],"array")&&(b=c.path.clone(a)),b.length||A(a).replace(O,function(a,c,d){{var e=[];c.toLowerCase()}d.replace(P,function(a,b){b&&e.push(+b)}),b.push([c].concat(e))}),b.toString=c.path.toString,b};c._.svgTransform2string=m,c._.rgTransform=/^[a-z][\s]*-?\.?\d/i,c._.transform2matrix=n,c._unit2px=q;y.doc.contains||y.doc.compareDocumentPosition?function(a,b){var c=9==a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a==d||!(!d||1!=d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)for(;b;)if(b=b.parentNode,b==a)return!0;return!1};c._.getSomeDefs=o,c._.getSomeSVG=p,c.select=function(a){return a=A(a).replace(/([^\\]):/g,"$1\\:"),w(y.doc.querySelector(a))},c.selectAll=function(a){for(var b=y.doc.querySelectorAll(a),d=(c.set||Array)(),e=0;e<b.length;e++)d.push(w(b[e]));return d},setInterval(function(){for(var a in V)if(V[z](a)){var b=V[a],c=b.node;("svg"!=b.type&&!c.ownerSVGElement||"svg"==b.type&&(!c.parentNode||"ownerSVGElement"in c.parentNode&&!c.ownerSVGElement))&&delete V[a]}},1e4),s.prototype.attr=function(a,c){var d=this,f=d.node;if(!a){if(1!=f.nodeType)return{text:f.nodeValue};for(var g=f.attributes,h={},i=0,j=g.length;j>i;i++)h[g[i].nodeName]=g[i].nodeValue;return h}if(e(a,"string")){if(!(arguments.length>1))return b("snap.util.getattr."+a,d).firstDefined();var k={};k[a]=c,a=k}for(var l in a)a[z](l)&&b("snap.util.attr."+l,d,a[l]);return d},c.parse=function(a){var b=y.doc.createDocumentFragment(),c=!0,d=y.doc.createElement("div");if(a=A(a),a.match(/^\s*<\s*svg(?:\s|>)/)||(a="<svg>"+a+"</svg>",c=!1),d.innerHTML=a,a=d.getElementsByTagName("svg")[0])if(c)b=a;else for(;a.firstChild;)b.appendChild(a.firstChild);return new t(b)},c.fragment=function(){for(var a=Array.prototype.slice.call(arguments,0),b=y.doc.createDocumentFragment(),d=0,e=a.length;e>d;d++){var f=a[d];f.node&&f.node.nodeType&&b.appendChild(f.node),f.nodeType&&b.appendChild(f),"string"==typeof f&&b.appendChild(c.parse(f).node)}return new t(b)},c._.make=u,c._.wrap=w,v.prototype.el=function(a,b){var c=u(a,this.node);return b&&c.attr(b),c},s.prototype.children=function(){for(var a=[],b=this.node.childNodes,d=0,e=b.length;e>d;d++)a[d]=c(b[d]);return a},s.prototype.toJSON=function(){var a=[];return x([this],a),a[0]},b.on("snap.util.getattr",function(){var a=b.nt();a=a.substring(a.lastIndexOf(".")+1);var c=a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});return bb[z](c)?this.node.ownerDocument.defaultView.getComputedStyle(this.node,null).getPropertyValue(c):d(this.node,a)});var bb={"alignment-baseline":0,"baseline-shift":0,clip:0,"clip-path":0,"clip-rule":0,color:0,"color-interpolation":0,"color-interpolation-filters":0,"color-profile":0,"color-rendering":0,cursor:0,direction:0,display:0,"dominant-baseline":0,"enable-background":0,fill:0,"fill-opacity":0,"fill-rule":0,filter:0,"flood-color":0,"flood-opacity":0,font:0,"font-family":0,"font-size":0,"font-size-adjust":0,"font-stretch":0,"font-style":0,"font-variant":0,"font-weight":0,"glyph-orientation-horizontal":0,"glyph-orientation-vertical":0,"image-rendering":0,kerning:0,"letter-spacing":0,"lighting-color":0,marker:0,"marker-end":0,"marker-mid":0,"marker-start":0,mask:0,opacity:0,overflow:0,"pointer-events":0,"shape-rendering":0,"stop-color":0,"stop-opacity":0,stroke:0,"stroke-dasharray":0,"stroke-dashoffset":0,"stroke-linecap":0,"stroke-linejoin":0,"stroke-miterlimit":0,"stroke-opacity":0,"stroke-width":0,"text-anchor":0,"text-decoration":0,"text-rendering":0,"unicode-bidi":0,visibility:0,"word-spacing":0,"writing-mode":0};b.on("snap.util.attr",function(a){var c=b.nt(),e={};c=c.substring(c.lastIndexOf(".")+1),e[c]=a;var f=c.replace(/-(\w)/gi,function(a,b){return b.toUpperCase()}),g=c.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});bb[z](g)?this.node.style[f]=null==a?I:a:d(this.node,e)}),function(){}(v.prototype),c.ajax=function(a,c,d,f){var g=new XMLHttpRequest,h=S();if(g){if(e(c,"function"))f=d,d=c,c=null;else if(e(c,"object")){var i=[];for(var j in c)c.hasOwnProperty(j)&&i.push(encodeURIComponent(j)+"="+encodeURIComponent(c[j]));c=i.join("&")}return g.open(c?"POST":"GET",a,!0),c&&(g.setRequestHeader("X-Requested-With","XMLHttpRequest"),g.setRequestHeader("Content-type","application/x-www-form-urlencoded")),d&&(b.once("snap.ajax."+h+".0",d),b.once("snap.ajax."+h+".200",d),b.once("snap.ajax."+h+".304",d)),g.onreadystatechange=function(){4==g.readyState&&b("snap.ajax."+h+"."+g.status,f,g)},4==g.readyState?g:(g.send(c),g)}},c.load=function(a,b,d){c.ajax(a,function(a){var e=c.parse(a.responseText);d?b.call(d,e):b(e)})};var cb=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,h=e.clientLeft||d.clientLeft||0,i=b.top+(g.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(g.win.pageXOffset||e.scrollLeft||d.scrollLeft)-h;return{y:i,x:j}};return c.getElementByPoint=function(a,b){var c=this,d=(c.canvas,y.doc.elementFromPoint(a,b));if(y.win.opera&&"svg"==d.tagName){var e=cb(d),f=d.createSVGRect();f.x=a-e.x,f.y=b-e.y,f.width=f.height=1;var g=d.getIntersectionList(f,null);g.length&&(d=g[g.length-1])}return d?w(d):null},c.plugin=function(a){a(c,s,v,y,t)},y.win.Snap=c,c}(a||this);return d.plugin(function(d,e,f,g,h){function i(a,b){if(null==b){var c=!0;if(b=a.node.getAttribute("linearGradient"==a.type||"radialGradient"==a.type?"gradientTransform":"pattern"==a.type?"patternTransform":"transform"),!b)return new d.Matrix;b=d._.svgTransform2string(b)}else b=d._.rgTransform.test(b)?o(b).replace(/\.{3}|\u2026/g,a._.transform||""):d._.svgTransform2string(b),n(b,"array")&&(b=d.path?d.path.toString.call(b):o(b)),a._.transform=b;var e=d._.transform2matrix(b,a.getBBox(1));return c?e:void(a.matrix=e)}function j(a){function b(a,b){var c=q(a.node,b);c=c&&c.match(f),c=c&&c[2],c&&"#"==c.charAt()&&(c=c.substring(1),c&&(h[c]=(h[c]||[]).concat(function(c){var d={};d[b]=URL(c),q(a.node,d)})))}function c(a){var b=q(a.node,"xlink:href");b&&"#"==b.charAt()&&(b=b.substring(1),b&&(h[b]=(h[b]||[]).concat(function(b){a.attr("xlink:href","#"+b)})))}for(var d,e=a.selectAll("*"),f=/^\s*url\(("|'|)(.*)\1\)\s*$/,g=[],h={},i=0,j=e.length;j>i;i++){d=e[i],b(d,"fill"),b(d,"stroke"),b(d,"filter"),b(d,"mask"),b(d,"clip-path"),c(d);var k=q(d.node,"id");k&&(q(d.node,{id:d.id}),g.push({old:k,id:d.id}))}for(i=0,j=g.length;j>i;i++){var l=h[g[i].old];if(l)for(var m=0,n=l.length;n>m;m++)l[m](g[i].id)}}function k(a,b,c){return function(d){var e=d.slice(a,b);return 1==e.length&&(e=e[0]),c?c(e):e}}function l(a){return function(){var b=a?"<"+this.type:"",c=this.node.attributes,d=this.node.childNodes;if(a)for(var e=0,f=c.length;f>e;e++)b+=" "+c[e].name+'="'+c[e].value.replace(/"/g,'\\"')+'"';if(d.length){for(a&&(b+=">"),e=0,f=d.length;f>e;e++)3==d[e].nodeType?b+=d[e].nodeValue:1==d[e].nodeType&&(b+=u(d[e]).toString());a&&(b+="</"+this.type+">")}else a&&(b+="/>");return b}}var m=e.prototype,n=d.is,o=String,p=d._unit2px,q=d._.$,r=d._.make,s=d._.getSomeDefs,t="hasOwnProperty",u=d._.wrap;m.getBBox=function(a){if(!d.Matrix||!d.path)return this.node.getBBox();var b=this,c=new d.Matrix;if(b.removed)return d._.box();for(;"use"==b.type;)if(a||(c=c.add(b.transform().localMatrix.translate(b.attr("x")||0,b.attr("y")||0))),b.original)b=b.original;else{var e=b.attr("xlink:href");b=b.original=b.node.ownerDocument.getElementById(e.substring(e.indexOf("#")+1))}var f=b._,g=d.path.get[b.type]||d.path.get.deflt;try{return a?(f.bboxwt=g?d.path.getBBox(b.realPath=g(b)):d._.box(b.node.getBBox()),d._.box(f.bboxwt)):(b.realPath=g(b),b.matrix=b.transform().localMatrix,f.bbox=d.path.getBBox(d.path.map(b.realPath,c.add(b.matrix))),d._.box(f.bbox))}catch(h){return d._.box()}};var v=function(){return this.string};m.transform=function(a){var b=this._;if(null==a){for(var c,e=this,f=new d.Matrix(this.node.getCTM()),g=i(this),h=[g],j=new d.Matrix,k=g.toTransformString(),l=o(g)==o(this.matrix)?o(b.transform):k;"svg"!=e.type&&(e=e.parent());)h.push(i(e));for(c=h.length;c--;)j.add(h[c]);return{string:l,globalMatrix:f,totalMatrix:j,localMatrix:g,diffMatrix:f.clone().add(g.invert()),global:f.toTransformString(),total:j.toTransformString(),local:k,toString:v}}return a instanceof d.Matrix?(this.matrix=a,this._.transform=a.toTransformString()):i(this,a),this.node&&("linearGradient"==this.type||"radialGradient"==this.type?q(this.node,{gradientTransform:this.matrix}):"pattern"==this.type?q(this.node,{patternTransform:this.matrix}):q(this.node,{transform:this.matrix})),this},m.parent=function(){return u(this.node.parentNode)},m.append=m.add=function(a){if(a){if("set"==a.type){var b=this;return a.forEach(function(a){b.add(a)}),this}a=u(a),this.node.appendChild(a.node),a.paper=this.paper}return this},m.appendTo=function(a){return a&&(a=u(a),a.append(this)),this},m.prepend=function(a){if(a){if("set"==a.type){var b,c=this;return a.forEach(function(a){b?b.after(a):c.prepend(a),b=a}),this}a=u(a);var d=a.parent();this.node.insertBefore(a.node,this.node.firstChild),this.add&&this.add(),a.paper=this.paper,this.parent()&&this.parent().add(),d&&d.add()}return this},m.prependTo=function(a){return a=u(a),a.prepend(this),this},m.before=function(a){if("set"==a.type){var b=this;return a.forEach(function(a){var c=a.parent();b.node.parentNode.insertBefore(a.node,b.node),c&&c.add()}),this.parent().add(),this}a=u(a);var c=a.parent();return this.node.parentNode.insertBefore(a.node,this.node),this.parent()&&this.parent().add(),c&&c.add(),a.paper=this.paper,this},m.after=function(a){a=u(a);var b=a.parent();return this.node.nextSibling?this.node.parentNode.insertBefore(a.node,this.node.nextSibling):this.node.parentNode.appendChild(a.node),this.parent()&&this.parent().add(),b&&b.add(),a.paper=this.paper,this},m.insertBefore=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.insertAfter=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node.nextSibling),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.remove=function(){var a=this.parent();return this.node.parentNode&&this.node.parentNode.removeChild(this.node),delete this.paper,this.removed=!0,a&&a.add(),this},m.select=function(a){return u(this.node.querySelector(a))},m.selectAll=function(a){for(var b=this.node.querySelectorAll(a),c=(d.set||Array)(),e=0;e<b.length;e++)c.push(u(b[e]));return c},m.asPX=function(a,b){return null==b&&(b=this.attr(a)),+p(this,a,b)},m.use=function(){var a,b=this.node.id;return b||(b=this.id,q(this.node,{id:b})),a="linearGradient"==this.type||"radialGradient"==this.type||"pattern"==this.type?r(this.type,this.node.parentNode):r("use",this.node.parentNode),q(a.node,{"xlink:href":"#"+b}),a.original=this,a},m.clone=function(){var a=u(this.node.cloneNode(!0));return q(a.node,"id")&&q(a.node,{id:a.id}),j(a),a.insertAfter(this),a},m.toDefs=function(){var a=s(this);return a.appendChild(this.node),this},m.pattern=m.toPattern=function(a,b,c,d){var e=r("pattern",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,a=a.x),q(e.node,{x:a,y:b,width:c,height:d,patternUnits:"userSpaceOnUse",id:e.id,viewBox:[a,b,c,d].join(" ")}),e.node.appendChild(this.node),e},m.marker=function(a,b,c,d,e,f){var g=r("marker",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,e=a.refX||a.cx,f=a.refY||a.cy,a=a.x),q(g.node,{viewBox:[a,b,c,d].join(" "),markerWidth:c,markerHeight:d,orient:"auto",refX:e||0,refY:f||0,id:g.id}),g.node.appendChild(this.node),g};var w=function(a,b,d,e){"function"!=typeof d||d.length||(e=d,d=c.linear),this.attr=a,this.dur=b,d&&(this.easing=d),e&&(this.callback=e)};d._.Animation=w,d.animation=function(a,b,c,d){return new w(a,b,c,d)},m.inAnim=function(){var a=this,b=[];for(var c in a.anims)a.anims[t](c)&&!function(a){b.push({anim:new w(a._attrs,a.dur,a.easing,a._callback),mina:a,curStatus:a.status(),status:function(b){return a.status(b)},stop:function(){a.stop()}})}(a.anims[c]);return b},d.animate=function(a,d,e,f,g,h){"function"!=typeof g||g.length||(h=g,g=c.linear);var i=c.time(),j=c(a,d,i,i+f,c.time,e,g);return h&&b.once("mina.finish."+j.id,h),j},m.stop=function(){for(var a=this.inAnim(),b=0,c=a.length;c>b;b++)a[b].stop();return this},m.animate=function(a,d,e,f){"function"!=typeof e||e.length||(f=e,e=c.linear),a instanceof w&&(f=a.callback,e=a.easing,d=a.dur,a=a.attr);var g,h,i,j,l=[],m=[],p={},q=this;for(var r in a)if(a[t](r)){q.equal?(j=q.equal(r,o(a[r])),g=j.from,h=j.to,i=j.f):(g=+q.attr(r),h=+a[r]);var s=n(g,"array")?g.length:1;p[r]=k(l.length,l.length+s,i),l=l.concat(g),m=m.concat(h)}var u=c.time(),v=c(l,m,u,u+d,c.time,function(a){var b={};for(var c in p)p[t](c)&&(b[c]=p[c](a));q.attr(b)},e);return q.anims[v.id]=v,v._attrs=a,v._callback=f,b("snap.animcreated."+q.id,v),b.once("mina.finish."+v.id,function(){delete q.anims[v.id],f&&f.call(q)}),b.once("mina.stop."+v.id,function(){delete q.anims[v.id]}),q};var x={};m.data=function(a,c){var e=x[this.id]=x[this.id]||{};if(0==arguments.length)return b("snap.data.get."+this.id,this,e,null),e;
+if(1==arguments.length){if(d.is(a,"object")){for(var f in a)a[t](f)&&this.data(f,a[f]);return this}return b("snap.data.get."+this.id,this,e[a],a),e[a]}return e[a]=c,b("snap.data.set."+this.id,this,c,a),this},m.removeData=function(a){return null==a?x[this.id]={}:x[this.id]&&delete x[this.id][a],this},m.outerSVG=m.toString=l(1),m.innerSVG=l(),m.toDataURL=function(){if(a&&a.btoa){var b=this.getBBox(),c=d.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>',{x:+b.x.toFixed(3),y:+b.y.toFixed(3),width:+b.width.toFixed(3),height:+b.height.toFixed(3),contents:this.outerSVG()});return"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(c)))}},h.prototype.select=m.select,h.prototype.selectAll=m.selectAll}),d.plugin(function(a){function b(a,b,d,e,f,g){return null==b&&"[object SVGMatrix]"==c.call(a)?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,void(this.f=a.f)):void(null!=a?(this.a=+a,this.b=+b,this.c=+d,this.d=+e,this.e=+f,this.f=+g):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0))}var c=Object.prototype.toString,d=String,e=Math,f="";!function(c){function g(a){return a[0]*a[0]+a[1]*a[1]}function h(a){var b=e.sqrt(g(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}c.add=function(a,c,d,e,f,g){var h,i,j,k,l=[[],[],[]],m=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],n=[[a,d,f],[c,e,g],[0,0,1]];for(a&&a instanceof b&&(n=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),h=0;3>h;h++)for(i=0;3>i;i++){for(k=0,j=0;3>j;j++)k+=m[h][j]*n[j][i];l[h][i]=k}return this.a=l[0][0],this.b=l[1][0],this.c=l[0][1],this.d=l[1][1],this.e=l[0][2],this.f=l[1][2],this},c.invert=function(){var a=this,c=a.a*a.d-a.b*a.c;return new b(a.d/c,-a.b/c,-a.c/c,a.a/c,(a.c*a.f-a.d*a.e)/c,(a.b*a.e-a.a*a.f)/c)},c.clone=function(){return new b(this.a,this.b,this.c,this.d,this.e,this.f)},c.translate=function(a,b){return this.add(1,0,0,1,a,b)},c.scale=function(a,b,c,d){return null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d),this},c.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var f=+e.cos(b).toFixed(9),g=+e.sin(b).toFixed(9);return this.add(f,g,-g,f,c,d),this.add(1,0,0,1,-c,-d)},c.x=function(a,b){return a*this.a+b*this.c+this.e},c.y=function(a,b){return a*this.b+b*this.d+this.f},c.get=function(a){return+this[d.fromCharCode(97+a)].toFixed(4)},c.toString=function(){return"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")"},c.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},c.determinant=function(){return this.a*this.d-this.b*this.c},c.split=function(){var b={};b.dx=this.e,b.dy=this.f;var c=[[this.a,this.c],[this.b,this.d]];b.scalex=e.sqrt(g(c[0])),h(c[0]),b.shear=c[0][0]*c[1][0]+c[0][1]*c[1][1],c[1]=[c[1][0]-c[0][0]*b.shear,c[1][1]-c[0][1]*b.shear],b.scaley=e.sqrt(g(c[1])),h(c[1]),b.shear/=b.scaley,this.determinant()<0&&(b.scalex=-b.scalex);var d=-c[0][1],f=c[1][1];return 0>f?(b.rotate=a.deg(e.acos(f)),0>d&&(b.rotate=360-b.rotate)):b.rotate=a.deg(e.asin(d)),b.isSimple=!(+b.shear.toFixed(9)||b.scalex.toFixed(9)!=b.scaley.toFixed(9)&&b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate,b},c.toTransformString=function(a){var b=a||this.split();return+b.shear.toFixed(9)?"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]:(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[+b.dx.toFixed(4),+b.dy.toFixed(4)]:f)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:f)+(b.rotate?"r"+[+b.rotate.toFixed(4),0,0]:f))}}(b.prototype),a.Matrix=b,a.matrix=function(a,c,d,e,f,g){return new b(a,c,d,e,f,g)}}),d.plugin(function(a,c,d,e,f){function g(d){return function(e){if(b.stop(),e instanceof f&&1==e.node.childNodes.length&&("radialGradient"==e.node.firstChild.tagName||"linearGradient"==e.node.firstChild.tagName||"pattern"==e.node.firstChild.tagName)&&(e=e.node.firstChild,n(this).appendChild(e),e=l(e)),e instanceof c)if("radialGradient"==e.type||"linearGradient"==e.type||"pattern"==e.type){e.node.id||p(e.node,{id:e.id});var g=q(e.node.id)}else g=e.attr(d);else if(g=a.color(e),g.error){var h=a(n(this).ownerSVGElement).gradient(e);h?(h.node.id||p(h.node,{id:h.id}),g=q(h.node.id)):g=e}else g=r(g);var i={};i[d]=g,p(this.node,i),this.node.style[d]=t}}function h(a){b.stop(),a==+a&&(a+="px"),this.node.style.fontSize=a}function i(a){for(var b=[],c=a.childNodes,d=0,e=c.length;e>d;d++){var f=c[d];3==f.nodeType&&b.push(f.nodeValue),"tspan"==f.tagName&&b.push(1==f.childNodes.length&&3==f.firstChild.nodeType?f.firstChild.nodeValue:i(f))}return b}function j(){return b.stop(),this.node.style.fontSize}var k=a._.make,l=a._.wrap,m=a.is,n=a._.getSomeDefs,o=/^url\(#?([^)]+)\)$/,p=a._.$,q=a.url,r=String,s=a._.separator,t="";b.on("snap.util.attr.mask",function(a){if(a instanceof c||a instanceof f){if(b.stop(),a instanceof f&&1==a.node.childNodes.length&&(a=a.node.firstChild,n(this).appendChild(a),a=l(a)),"mask"==a.type)var d=a;else d=k("mask",n(this)),d.node.appendChild(a.node);!d.node.id&&p(d.node,{id:d.id}),p(this.node,{mask:q(d.id)})}}),function(a){b.on("snap.util.attr.clip",a),b.on("snap.util.attr.clip-path",a),b.on("snap.util.attr.clipPath",a)}(function(a){if(a instanceof c||a instanceof f){if(b.stop(),"clipPath"==a.type)var d=a;else d=k("clipPath",n(this)),d.node.appendChild(a.node),!d.node.id&&p(d.node,{id:d.id});p(this.node,{"clip-path":q(d.node.id||d.id)})}}),b.on("snap.util.attr.fill",g("fill")),b.on("snap.util.attr.stroke",g("stroke"));var u=/^([lr])(?:\(([^)]*)\))?(.*)$/i;b.on("snap.util.grad.parse",function(a){a=r(a);var b=a.match(u);if(!b)return null;var c=b[1],d=b[2],e=b[3];return d=d.split(/\s*,\s*/).map(function(a){return+a==a?+a:a}),1==d.length&&0==d[0]&&(d=[]),e=e.split("-"),e=e.map(function(a){a=a.split(":");var b={color:a[0]};return a[1]&&(b.offset=parseFloat(a[1])),b}),{type:c,params:d,stops:e}}),b.on("snap.util.attr.d",function(c){b.stop(),m(c,"array")&&m(c[0],"array")&&(c=a.path.toString.call(c)),c=r(c),c.match(/[ruo]/i)&&(c=a.path.toAbsolute(c)),p(this.node,{d:c})})(-1),b.on("snap.util.attr.#text",function(a){b.stop(),a=r(a);for(var c=e.doc.createTextNode(a);this.node.firstChild;)this.node.removeChild(this.node.firstChild);this.node.appendChild(c)})(-1),b.on("snap.util.attr.path",function(a){b.stop(),this.attr({d:a})})(-1),b.on("snap.util.attr.class",function(a){b.stop(),this.node.className.baseVal=a})(-1),b.on("snap.util.attr.viewBox",function(a){var c;c=m(a,"object")&&"x"in a?[a.x,a.y,a.width,a.height].join(" "):m(a,"array")?a.join(" "):a,p(this.node,{viewBox:c}),b.stop()})(-1),b.on("snap.util.attr.transform",function(a){this.transform(a),b.stop()})(-1),b.on("snap.util.attr.r",function(a){"rect"==this.type&&(b.stop(),p(this.node,{rx:a,ry:a}))})(-1),b.on("snap.util.attr.textpath",function(a){if(b.stop(),"text"==this.type){var d,e,f;if(!a&&this.textPath){for(e=this.textPath;e.node.firstChild;)this.node.appendChild(e.node.firstChild);return e.remove(),void delete this.textPath}if(m(a,"string")){var g=n(this),h=l(g.parentNode).path(a);g.appendChild(h.node),d=h.id,h.attr({id:d})}else a=l(a),a instanceof c&&(d=a.attr("id"),d||(d=a.id,a.attr({id:d})));if(d)if(e=this.textPath,f=this.node,e)e.attr({"xlink:href":"#"+d});else{for(e=p("textPath",{"xlink:href":"#"+d});f.firstChild;)e.appendChild(f.firstChild);f.appendChild(e),this.textPath=l(e)}}})(-1),b.on("snap.util.attr.text",function(a){if("text"==this.type){for(var c=this.node,d=function(a){var b=p("tspan");if(m(a,"array"))for(var c=0;c<a.length;c++)b.appendChild(d(a[c]));else b.appendChild(e.doc.createTextNode(a));return b.normalize&&b.normalize(),b};c.firstChild;)c.removeChild(c.firstChild);for(var f=d(a);f.firstChild;)c.appendChild(f.firstChild)}b.stop()})(-1),b.on("snap.util.attr.fontSize",h)(-1),b.on("snap.util.attr.font-size",h)(-1),b.on("snap.util.getattr.transform",function(){return b.stop(),this.transform()})(-1),b.on("snap.util.getattr.textpath",function(){return b.stop(),this.textPath})(-1),function(){function c(c){return function(){b.stop();var d=e.doc.defaultView.getComputedStyle(this.node,null).getPropertyValue("marker-"+c);return"none"==d?d:a(e.doc.getElementById(d.match(o)[1]))}}function d(a){return function(c){b.stop();var d="marker"+a.charAt(0).toUpperCase()+a.substring(1);if(""==c||!c)return void(this.node.style[d]="none");if("marker"==c.type){var e=c.node.id;return e||p(c.node,{id:c.id}),void(this.node.style[d]=q(e))}}}b.on("snap.util.getattr.marker-end",c("end"))(-1),b.on("snap.util.getattr.markerEnd",c("end"))(-1),b.on("snap.util.getattr.marker-start",c("start"))(-1),b.on("snap.util.getattr.markerStart",c("start"))(-1),b.on("snap.util.getattr.marker-mid",c("mid"))(-1),b.on("snap.util.getattr.markerMid",c("mid"))(-1),b.on("snap.util.attr.marker-end",d("end"))(-1),b.on("snap.util.attr.markerEnd",d("end"))(-1),b.on("snap.util.attr.marker-start",d("start"))(-1),b.on("snap.util.attr.markerStart",d("start"))(-1),b.on("snap.util.attr.marker-mid",d("mid"))(-1),b.on("snap.util.attr.markerMid",d("mid"))(-1)}(),b.on("snap.util.getattr.r",function(){return"rect"==this.type&&p(this.node,"rx")==p(this.node,"ry")?(b.stop(),p(this.node,"rx")):void 0})(-1),b.on("snap.util.getattr.text",function(){if("text"==this.type||"tspan"==this.type){b.stop();var a=i(this.node);return 1==a.length?a[0]:a}})(-1),b.on("snap.util.getattr.#text",function(){return this.node.textContent})(-1),b.on("snap.util.getattr.viewBox",function(){b.stop();var c=p(this.node,"viewBox");return c?(c=c.split(s),a._.box(+c[0],+c[1],+c[2],+c[3])):void 0})(-1),b.on("snap.util.getattr.points",function(){var a=p(this.node,"points");return b.stop(),a?a.split(s):void 0})(-1),b.on("snap.util.getattr.path",function(){var a=p(this.node,"d");return b.stop(),a})(-1),b.on("snap.util.getattr.class",function(){return this.node.className.baseVal})(-1),b.on("snap.util.getattr.fontSize",j)(-1),b.on("snap.util.getattr.font-size",j)(-1)}),d.plugin(function(a,b){var c=/\S+/g,d=String,e=b.prototype;e.addClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(h.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e||k.push(f);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.removeClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(k.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e&&k.splice(e,1);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.hasClass=function(a){var b=this.node,d=b.className.baseVal,e=d.match(c)||[];return!!~e.indexOf(a)},e.toggleClass=function(a,b){if(null!=b)return b?this.addClass(a):this.removeClass(a);var d,e,f,g,h=(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];for(d=0;f=h[d++];)e=k.indexOf(f),~e?k.splice(e,1):k.push(f);return g=k.join(" "),j!=g&&(i.className.baseVal=g),this}}),d.plugin(function(){function a(a){return a}function c(a){return function(b){return+b.toFixed(3)+a}}var d={"+":function(a,b){return a+b},"-":function(a,b){return a-b},"/":function(a,b){return a/b},"*":function(a,b){return a*b}},e=String,f=/[a-z]+$/i,g=/^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;b.on("snap.util.attr",function(a){var c=e(a).match(g);if(c){var h=b.nt(),i=h.substring(h.lastIndexOf(".")+1),j=this.attr(i),k={};b.stop();var l=c[3]||"",m=j.match(f),n=d[c[1]];if(m&&m==l?a=n(parseFloat(j),+c[2]):(j=this.asPX(i),a=n(this.asPX(i),this.asPX(i,c[2]+l))),isNaN(j)||isNaN(a))return;k[i]=a,this.attr(k)}})(-10),b.on("snap.util.equal",function(h,i){var j=e(this.attr(h)||""),k=e(i).match(g);if(k){b.stop();var l=k[3]||"",m=j.match(f),n=d[k[1]];return m&&m==l?{from:parseFloat(j),to:n(parseFloat(j),+k[2]),f:c(m)}:(j=this.asPX(h),{from:j,to:n(j,this.asPX(h,k[2]+l)),f:a})}})(-10)}),d.plugin(function(c,d,e,f){var g=e.prototype,h=c.is;g.rect=function(a,b,c,d,e,f){var g;return null==f&&(f=e),h(a,"object")&&"[object Object]"==a?g=a:null!=a&&(g={x:a,y:b,width:c,height:d},null!=e&&(g.rx=e,g.ry=f)),this.el("rect",g)},g.circle=function(a,b,c){var d;return h(a,"object")&&"[object Object]"==a?d=a:null!=a&&(d={cx:a,cy:b,r:c}),this.el("circle",d)};var i=function(){function a(){this.parentNode.removeChild(this)}return function(b,c){var d=f.doc.createElement("img"),e=f.doc.body;d.style.cssText="position:absolute;left:-9999em;top:-9999em",d.onload=function(){c.call(d),d.onload=d.onerror=null,e.removeChild(d)},d.onerror=a,e.appendChild(d),d.src=b}}();g.image=function(a,b,d,e,f){var g=this.el("image");if(h(a,"object")&&"src"in a)g.attr(a);else if(null!=a){var j={"xlink:href":a,preserveAspectRatio:"none"};null!=b&&null!=d&&(j.x=b,j.y=d),null!=e&&null!=f?(j.width=e,j.height=f):i(a,function(){c._.$(g.node,{width:this.offsetWidth,height:this.offsetHeight})}),c._.$(g.node,j)}return g},g.ellipse=function(a,b,c,d){var e;return h(a,"object")&&"[object Object]"==a?e=a:null!=a&&(e={cx:a,cy:b,rx:c,ry:d}),this.el("ellipse",e)},g.path=function(a){var b;return h(a,"object")&&!h(a,"array")?b=a:a&&(b={d:a}),this.el("path",b)},g.group=g.g=function(a){var b=this.el("g");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.svg=function(a,b,c,d,e,f,g,i){var j={};return h(a,"object")&&null==b?j=a:(null!=a&&(j.x=a),null!=b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),null!=e&&null!=f&&null!=g&&null!=i&&(j.viewBox=[e,f,g,i])),this.el("svg",j)},g.mask=function(a){var b=this.el("mask");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.ptrn=function(a,b,c,d,e,f,g,i){if(h(a,"object"))var j=a;else j={patternUnits:"userSpaceOnUse"},a&&(j.x=a),b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),j.viewBox=null!=e&&null!=f&&null!=g&&null!=i?[e,f,g,i]:[a||0,b||0,c||0,d||0];return this.el("pattern",j)},g.use=function(a){return null!=a?(a instanceof d&&(a.attr("id")||a.attr({id:c._.id(a)}),a=a.attr("id")),"#"==String(a).charAt()&&(a=a.substring(1)),this.el("use",{"xlink:href":"#"+a})):d.prototype.use.call(this)},g.symbol=function(a,b,c,d){var e={};return null!=a&&null!=b&&null!=c&&null!=d&&(e.viewBox=[a,b,c,d]),this.el("symbol",e)},g.text=function(a,b,c){var d={};return h(a,"object")?d=a:null!=a&&(d={x:a,y:b,text:c||""}),this.el("text",d)},g.line=function(a,b,c,d){var e={};return h(a,"object")?e=a:null!=a&&(e={x1:a,x2:c,y1:b,y2:d}),this.el("line",e)},g.polyline=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polyline",b)},g.polygon=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polygon",b)},function(){function d(){return this.selectAll("stop")}function e(a,b){var d=k("stop"),e={offset:+b+"%"};return a=c.color(a),e["stop-color"]=a.hex,a.opacity<1&&(e["stop-opacity"]=a.opacity),k(d,e),this.node.appendChild(d),this}function f(){if("linearGradient"==this.type){var a=k(this.node,"x1")||0,b=k(this.node,"x2")||1,d=k(this.node,"y1")||0,e=k(this.node,"y2")||0;return c._.box(a,d,math.abs(b-a),math.abs(e-d))}var f=this.node.cx||.5,g=this.node.cy||.5,h=this.node.r||0;return c._.box(f-h,g-h,2*h,2*h)}function h(a,c){function d(a,b){for(var c=(b-l)/(a-m),d=m;a>d;d++)g[d].offset=+(+l+c*(d-m)).toFixed(2);m=a,l=b}var e,f=b("snap.util.grad.parse",null,c).firstDefined();if(!f)return null;f.params.unshift(a),e="l"==f.type.toLowerCase()?i.apply(0,f.params):j.apply(0,f.params),f.type!=f.type.toLowerCase()&&k(e.node,{gradientUnits:"userSpaceOnUse"});var g=f.stops,h=g.length,l=0,m=0;h--;for(var n=0;h>n;n++)"offset"in g[n]&&d(n,g[n].offset);for(g[h].offset=g[h].offset||100,d(h,g[h].offset),n=0;h>=n;n++){var o=g[n];e.addStop(o.color,o.offset)}return e}function i(a,b,g,h,i){var j=c._.make("linearGradient",a);return j.stops=d,j.addStop=e,j.getBBox=f,null!=b&&k(j.node,{x1:b,y1:g,x2:h,y2:i}),j}function j(a,b,g,h,i,j){var l=c._.make("radialGradient",a);return l.stops=d,l.addStop=e,l.getBBox=f,null!=b&&k(l.node,{cx:b,cy:g,r:h}),null!=i&&null!=j&&k(l.node,{fx:i,fy:j}),l}var k=c._.$;g.gradient=function(a){return h(this.defs,a)},g.gradientLinear=function(a,b,c,d){return i(this.defs,a,b,c,d)},g.gradientRadial=function(a,b,c,d,e){return j(this.defs,a,b,c,d,e)},g.toString=function(){var a,b=this.node.ownerDocument,d=b.createDocumentFragment(),e=b.createElement("div"),f=this.node.cloneNode(!0);return d.appendChild(e),e.appendChild(f),c._.$(f,{xmlns:"http://www.w3.org/2000/svg"}),a=e.innerHTML,d.removeChild(d.firstChild),a},g.toDataURL=function(){return a&&a.btoa?"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(this))):void 0},g.clear=function(){for(var a,b=this.node.firstChild;b;)a=b.nextSibling,"defs"!=b.tagName?b.parentNode.removeChild(b):g.clear.call({node:b}),b=a}}()}),d.plugin(function(a,b){function c(a){var b=c.ps=c.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[K](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]}function d(a,b,c,d){return null==a&&(a=b=c=d=0),null==b&&(b=a.y,c=a.width,d=a.height,a=a.x),{x:a,y:b,width:c,w:c,height:d,h:d,x2:a+c,y2:b+d,cx:a+c/2,cy:b+d/2,r1:N.min(c,d)/2,r2:N.max(c,d)/2,r0:N.sqrt(c*c+d*d)/2,path:w(a,b,c,d),vb:[a,b,c,d].join(" ")}}function e(){return this.join(",").replace(L,"$1")}function f(a){var b=J(a);return b.toString=e,b}function g(a,b,c,d,e,f,g,h,j){return null==j?n(a,b,c,d,e,f,g,h):i(a,b,c,d,e,f,g,h,o(a,b,c,d,e,f,g,h,j))}function h(c,d){function e(a){return+(+a).toFixed(3)}return a._.cacher(function(a,f,h){a instanceof b&&(a=a.attr("d")),a=E(a);for(var j,k,l,m,n,o="",p={},q=0,r=0,s=a.length;s>r;r++){if(l=a[r],"M"==l[0])j=+l[1],k=+l[2];else{if(m=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6]),q+m>f){if(d&&!p.start){if(n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q),o+=["C"+e(n.start.x),e(n.start.y),e(n.m.x),e(n.m.y),e(n.x),e(n.y)],h)return o;p.start=o,o=["M"+e(n.x),e(n.y)+"C"+e(n.n.x),e(n.n.y),e(n.end.x),e(n.end.y),e(l[5]),e(l[6])].join(),q+=m,j=+l[5],k=+l[6];continue}if(!c&&!d)return n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q)}q+=m,j=+l[5],k=+l[6]}o+=l.shift()+l}return p.end=o,n=c?q:d?p:i(j,k,l[0],l[1],l[2],l[3],l[4],l[5],1)},null,a._.clone)}function i(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/O;return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}}function j(b,c,e,f,g,h,i,j){a.is(b,"array")||(b=[b,c,e,f,g,h,i,j]);var k=D.apply(null,b);return d(k.min.x,k.min.y,k.max.x-k.min.x,k.max.y-k.min.y)}function k(a,b,c){return b>=a.x&&b<=a.x+a.width&&c>=a.y&&c<=a.y+a.height}function l(a,b){return a=d(a),b=d(b),k(b,a.x,a.y)||k(b,a.x2,a.y)||k(b,a.x,a.y2)||k(b,a.x2,a.y2)||k(a,b.x,b.y)||k(a,b.x2,b.y)||k(a,b.x,b.y2)||k(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)}function m(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function n(a,b,c,d,e,f,g,h,i){null==i&&(i=1),i=i>1?1:0>i?0:i;for(var j=i/2,k=12,l=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;k>p;p++){var q=j*l[p]+j,r=m(q,a,c,e,g),s=m(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return j*o}function o(a,b,c,d,e,f,g,h,i){if(!(0>i||n(a,b,c,d,e,f,g,h)<i)){var j,k=1,l=k/2,m=k-l,o=.01;for(j=n(a,b,c,d,e,f,g,h,m);S(j-i)>o;)l/=2,m+=(i>j?1:-1)*l,j=n(a,b,c,d,e,f,g,h,m);return m}}function p(a,b,c,d,e,f,g,h){if(!(Q(a,c)<P(e,g)||P(a,c)>Q(e,g)||Q(b,d)<P(f,h)||P(b,d)>Q(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+Q(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+Q(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+Q(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+Q(f,h).toFixed(2)))return{x:l,y:m}}}}function q(a,b,c){var d=j(a),e=j(b);if(!l(d,e))return c?0:[];for(var f=n.apply(0,a),g=n.apply(0,b),h=~~(f/8),k=~~(g/8),m=[],o=[],q={},r=c?0:[],s=0;h+1>s;s++){var t=i.apply(0,a.concat(s/h));m.push({x:t.x,y:t.y,t:s/h})}for(s=0;k+1>s;s++)t=i.apply(0,b.concat(s/k)),o.push({x:t.x,y:t.y,t:s/k});for(s=0;h>s;s++)for(var u=0;k>u;u++){var v=m[s],w=m[s+1],x=o[u],y=o[u+1],z=S(w.x-v.x)<.001?"y":"x",A=S(y.x-x.x)<.001?"y":"x",B=p(v.x,v.y,w.x,w.y,x.x,x.y,y.x,y.y);if(B){if(q[B.x.toFixed(4)]==B.y.toFixed(4))continue;q[B.x.toFixed(4)]=B.y.toFixed(4);var C=v.t+S((B[z]-v[z])/(w[z]-v[z]))*(w.t-v.t),D=x.t+S((B[A]-x[A])/(y[A]-x[A]))*(y.t-x.t);C>=0&&1>=C&&D>=0&&1>=D&&(c?r++:r.push({x:B.x,y:B.y,t1:C,t2:D}))}}return r}function r(a,b){return t(a,b)}function s(a,b){return t(a,b,1)}function t(a,b,c){a=E(a),b=E(b);for(var d,e,f,g,h,i,j,k,l,m,n=c?0:[],o=0,p=a.length;p>o;o++){var r=a[o];if("M"==r[0])d=h=r[1],e=i=r[2];else{"C"==r[0]?(l=[d,e].concat(r.slice(1)),d=l[6],e=l[7]):(l=[d,e,d,e,h,i,h,i],d=h,e=i);for(var s=0,t=b.length;t>s;s++){var u=b[s];if("M"==u[0])f=j=u[1],g=k=u[2];else{"C"==u[0]?(m=[f,g].concat(u.slice(1)),f=m[6],g=m[7]):(m=[f,g,f,g,j,k,j,k],f=j,g=k);var v=q(l,m,c);if(c)n+=v;else{for(var w=0,x=v.length;x>w;w++)v[w].segment1=o,v[w].segment2=s,v[w].bez1=l,v[w].bez2=m;n=n.concat(v)}}}}}return n}function u(a,b,c){var d=v(a);return k(d,b,c)&&t(a,[["M",b,c],["H",d.x2+10]],1)%2==1}function v(a){var b=c(a);if(b.bbox)return J(b.bbox);if(!a)return d();a=E(a);for(var e,f=0,g=0,h=[],i=[],j=0,k=a.length;k>j;j++)if(e=a[j],"M"==e[0])f=e[1],g=e[2],h.push(f),i.push(g);else{var l=D(f,g,e[1],e[2],e[3],e[4],e[5],e[6]);h=h.concat(l.min.x,l.max.x),i=i.concat(l.min.y,l.max.y),f=e[5],g=e[6]}var m=P.apply(0,h),n=P.apply(0,i),o=Q.apply(0,h),p=Q.apply(0,i),q=d(m,n,o-m,p-n);return b.bbox=J(q),q}function w(a,b,c,d,f){if(f)return[["M",+a+ +f,b],["l",c-2*f,0],["a",f,f,0,0,1,f,f],["l",0,d-2*f],["a",f,f,0,0,1,-f,f],["l",2*f-c,0],["a",f,f,0,0,1,-f,-f],["l",0,2*f-d],["a",f,f,0,0,1,f,-f],["z"]];var g=[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]];return g.toString=e,g}function x(a,b,c,d,f){if(null==f&&null==d&&(d=c),a=+a,b=+b,c=+c,d=+d,null!=f)var g=Math.PI/180,h=a+c*Math.cos(-d*g),i=a+c*Math.cos(-f*g),j=b+c*Math.sin(-d*g),k=b+c*Math.sin(-f*g),l=[["M",h,j],["A",c,c,0,+(f-d>180),0,i,k]];else l=[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]];return l.toString=e,l}function y(b){var d=c(b),g=String.prototype.toLowerCase;if(d.rel)return f(d.rel);a.is(b,"array")&&a.is(b&&b[0],"array")||(b=a.parsePathString(b));var h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=b[0][1],j=b[0][2],k=i,l=j,m++,h.push(["M",i,j]));for(var n=m,o=b.length;o>n;n++){var p=h[n]=[],q=b[n];if(q[0]!=g.call(q[0]))switch(p[0]=g.call(q[0]),p[0]){case"a":p[1]=q[1],p[2]=q[2],p[3]=q[3],p[4]=q[4],p[5]=q[5],p[6]=+(q[6]-i).toFixed(3),p[7]=+(q[7]-j).toFixed(3);break;case"v":p[1]=+(q[1]-j).toFixed(3);break;case"m":k=q[1],l=q[2];default:for(var r=1,s=q.length;s>r;r++)p[r]=+(q[r]-(r%2?i:j)).toFixed(3)}else{p=h[n]=[],"m"==q[0]&&(k=q[1]+i,l=q[2]+j);for(var t=0,u=q.length;u>t;t++)h[n][t]=q[t]}var v=h[n].length;switch(h[n][0]){case"z":i=k,j=l;break;case"h":i+=+h[n][v-1];break;case"v":j+=+h[n][v-1];break;default:i+=+h[n][v-2],j+=+h[n][v-1]}}return h.toString=e,d.rel=f(h),h}function z(b){var d=c(b);if(d.abs)return f(d.abs);if(I(b,"array")&&I(b&&b[0],"array")||(b=a.parsePathString(b)),!b||!b.length)return[["M",0,0]];var g,h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=+b[0][1],j=+b[0][2],k=i,l=j,m++,h[0]=["M",i,j]);for(var n,o,p=3==b.length&&"M"==b[0][0]&&"R"==b[1][0].toUpperCase()&&"Z"==b[2][0].toUpperCase(),q=m,r=b.length;r>q;q++){if(h.push(n=[]),o=b[q],g=o[0],g!=g.toUpperCase())switch(n[0]=g.toUpperCase(),n[0]){case"A":n[1]=o[1],n[2]=o[2],n[3]=o[3],n[4]=o[4],n[5]=o[5],n[6]=+o[6]+i,n[7]=+o[7]+j;break;case"V":n[1]=+o[1]+j;break;case"H":n[1]=+o[1]+i;break;case"R":for(var s=[i,j].concat(o.slice(1)),t=2,u=s.length;u>t;t++)s[t]=+s[t]+i,s[++t]=+s[t]+j;h.pop(),h=h.concat(G(s,p));break;case"O":h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);break;case"U":h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));break;case"M":k=+o[1]+i,l=+o[2]+j;default:for(t=1,u=o.length;u>t;t++)n[t]=+o[t]+(t%2?i:j)}else if("R"==g)s=[i,j].concat(o.slice(1)),h.pop(),h=h.concat(G(s,p)),n=["R"].concat(o.slice(-2));else if("O"==g)h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);else if("U"==g)h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));else for(var v=0,w=o.length;w>v;v++)n[v]=o[v];if(g=g.toUpperCase(),"O"!=g)switch(n[0]){case"Z":i=+k,j=+l;break;case"H":i=n[1];break;case"V":j=n[1];break;case"M":k=n[n.length-2],l=n[n.length-1];default:i=n[n.length-2],j=n[n.length-1]}}return h.toString=e,d.abs=f(h),h}function A(a,b,c,d){return[a,b,c,d,c,d]}function B(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]}function C(b,c,d,e,f,g,h,i,j,k){var l,m=120*O/180,n=O/180*(+f||0),o=[],p=a._.cacher(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(b,c,-n),b=l.x,c=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(O/180*f),N.sin(O/180*f),(b-i)/2),r=(c-j)/2,s=q*q/(d*d)+r*r/(e*e);s>1&&(s=N.sqrt(s),d=s*d,e=s*e);var t=d*d,u=e*e,v=(g==h?-1:1)*N.sqrt(S((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*d*r/e+(b+i)/2,x=v*-e*q/d+(c+j)/2,y=N.asin(((c-x)/e).toFixed(9)),z=N.asin(((j-x)/e).toFixed(9));y=w>b?O-y:y,z=w>i?O-z:z,0>y&&(y=2*O+y),0>z&&(z=2*O+z),h&&y>z&&(y-=2*O),!h&&z>y&&(z-=2*O)}var A=z-y;if(S(A)>m){var B=z,D=i,E=j;z=y+m*(h&&z>y?1:-1),i=w+d*N.cos(z),j=x+e*N.sin(z),o=C(i,j,d,e,f,0,h,D,E,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),J=N.tan(A/4),K=4/3*d*J,L=4/3*e*J,M=[b,c],P=[b+K*G,c-L*F],Q=[i+K*I,j-L*H],R=[i,j];if(P[0]=2*M[0]-P[0],P[1]=2*M[1]-P[1],k)return[P,Q,R].concat(o);o=[P,Q,R].concat(o).join().split(",");for(var T=[],U=0,V=o.length;V>U;U++)T[U]=U%2?p(o[U-1],o[U],n).y:p(o[U],o[U+1],n).x;return T}function D(a,b,c,d,e,f,g,h){for(var i,j,k,l,m,n,o,p,q=[],r=[[],[]],s=0;2>s;++s)if(0==s?(j=6*a-12*c+6*e,i=-3*a+9*c-9*e+3*g,k=3*c-3*a):(j=6*b-12*d+6*f,i=-3*b+9*d-9*f+3*h,k=3*d-3*b),S(i)<1e-12){if(S(j)<1e-12)continue;l=-k/j,l>0&&1>l&&q.push(l)}else o=j*j-4*k*i,p=N.sqrt(o),0>o||(m=(-j+p)/(2*i),m>0&&1>m&&q.push(m),n=(-j-p)/(2*i),n>0&&1>n&&q.push(n));for(var t,u=q.length,v=u;u--;)l=q[u],t=1-l,r[0][u]=t*t*t*a+3*t*t*l*c+3*t*l*l*e+l*l*l*g,r[1][u]=t*t*t*b+3*t*t*l*d+3*t*l*l*f+l*l*l*h;return r[0][v]=a,r[1][v]=b,r[0][v+1]=g,r[1][v+1]=h,r[0].length=r[1].length=v+2,{min:{x:P.apply(0,r[0]),y:P.apply(0,r[1])},max:{x:Q.apply(0,r[0]),y:Q.apply(0,r[1])}}}function E(a,b){var d=!b&&c(a);if(!b&&d.curve)return f(d.curve);for(var e=z(a),g=b&&z(b),h={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},i={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},j=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"].concat(C.apply(0,[b.x,b.y].concat(a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e].concat(a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"].concat(B(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"].concat(B(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"].concat(A(b.x,b.y,a[1],a[2]));break;case"H":a=["C"].concat(A(b.x,b.y,a[1],b.y));break;case"V":a=["C"].concat(A(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"].concat(A(b.x,b.y,b.X,b.Y))}return a}),k=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)m[b]="A",g&&(n[b]="A"),a.splice(b++,0,["C"].concat(c.splice(0,6)));a.splice(b,1),r=Q(e.length,g&&g.length||0)}},l=function(a,b,c,d,f){a&&b&&"M"==a[f][0]&&"M"!=b[f][0]&&(b.splice(f,0,["M",d.x,d.y]),c.bx=0,c.by=0,c.x=a[f][1],c.y=a[f][2],r=Q(e.length,g&&g.length||0))},m=[],n=[],o="",p="",q=0,r=Q(e.length,g&&g.length||0);r>q;q++){e[q]&&(o=e[q][0]),"C"!=o&&(m[q]=o,q&&(p=m[q-1])),e[q]=j(e[q],h,p),"A"!=m[q]&&"C"==o&&(m[q]="C"),k(e,q),g&&(g[q]&&(o=g[q][0]),"C"!=o&&(n[q]=o,q&&(p=n[q-1])),g[q]=j(g[q],i,p),"A"!=n[q]&&"C"==o&&(n[q]="C"),k(g,q)),l(e,g,h,i,q),l(g,e,i,h,q);var s=e[q],t=g&&g[q],u=s.length,v=g&&t.length;h.x=s[u-2],h.y=s[u-1],h.bx=M(s[u-4])||h.x,h.by=M(s[u-3])||h.y,i.bx=g&&(M(t[v-4])||i.x),i.by=g&&(M(t[v-3])||i.y),i.x=g&&t[v-2],i.y=g&&t[v-1]}return g||(d.curve=f(e)),g?[e,g]:e}function F(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=E(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a}function G(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}var H=b.prototype,I=a.is,J=a._.clone,K="hasOwnProperty",L=/,?([a-z]),?/gi,M=parseFloat,N=Math,O=N.PI,P=N.min,Q=N.max,R=N.pow,S=N.abs,T=h(1),U=h(),V=h(0,1),W=a._unit2px,X={path:function(a){return a.attr("path")},circle:function(a){var b=W(a);return x(b.cx,b.cy,b.r)},ellipse:function(a){var b=W(a);return x(b.cx||0,b.cy||0,b.rx,b.ry)},rect:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height,b.rx,b.ry)},image:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height)},line:function(a){return"M"+[a.attr("x1")||0,a.attr("y1")||0,a.attr("x2"),a.attr("y2")]},polyline:function(a){return"M"+a.attr("points")},polygon:function(a){return"M"+a.attr("points")+"z"},deflt:function(a){var b=a.node.getBBox();return w(b.x,b.y,b.width,b.height)}};a.path=c,a.path.getTotalLength=T,a.path.getPointAtLength=U,a.path.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return V(a,b).end;var d=V(a,c,1);return b?V(d,b).end:d},H.getTotalLength=function(){return this.node.getTotalLength?this.node.getTotalLength():void 0},H.getPointAtLength=function(a){return U(this.attr("d"),a)},H.getSubpath=function(b,c){return a.path.getSubpath(this.attr("d"),b,c)},a._.box=d,a.path.findDotsAtSegment=i,a.path.bezierBBox=j,a.path.isPointInsideBBox=k,a.closest=function(b,c,e,f){for(var g=100,h=d(b-g/2,c-g/2,g,g),i=[],j=e[0].hasOwnProperty("x")?function(a){return{x:e[a].x,y:e[a].y}}:function(a){return{x:e[a],y:f[a]}},l=0;1e6>=g&&!l;){for(var m=0,n=e.length;n>m;m++){var o=j(m);if(k(h,o.x,o.y)){l++,i.push(o);break}}l||(g*=2,h=d(b-g/2,c-g/2,g,g))}if(1e6!=g){var p,q=1/0;for(m=0,n=i.length;n>m;m++){var r=a.len(b,c,i[m].x,i[m].y);q>r&&(q=r,i[m].len=r,p=i[m])}return p}},a.path.isBBoxIntersect=l,a.path.intersection=r,a.path.intersectionNumber=s,a.path.isPointInside=u,a.path.getBBox=v,a.path.get=X,a.path.toRelative=y,a.path.toAbsolute=z,a.path.toCubic=E,a.path.map=F,a.path.toString=e,a.path.clone=f}),d.plugin(function(a){var d=Math.max,e=Math.min,f=function(a){if(this.items=[],this.bindings={},this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)a[b]&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},g=f.prototype;g.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],a&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},g.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},g.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this},g.animate=function(d,e,f,g){"function"!=typeof f||f.length||(g=f,f=c.linear),d instanceof a._.Animation&&(g=d.callback,f=d.easing,e=f.dur,d=d.attr);var h=arguments;if(a.is(d,"array")&&a.is(h[h.length-1],"array"))var i=!0;var j,k=function(){j?this.b=j:j=this.b},l=0,m=this,n=g&&function(){++l==m.length&&g.call(this)
+};return this.forEach(function(a,c){b.once("snap.animcreated."+a.id,k),i?h[c]&&a.animate.apply(a,h[c]):a.animate(d,e,f,n)})},g.remove=function(){for(;this.length;)this.pop().remove();return this},g.bind=function(a,b,c){var d={};if("function"==typeof b)this.bindings[a]=b;else{var e=c||a;this.bindings[a]=function(a){d[e]=a,b.attr(d)}}return this},g.attr=function(a){var b={};for(var c in a)this.bindings[c]?this.bindings[c](a[c]):b[c]=a[c];for(var d=0,e=this.items.length;e>d;d++)this.items[d].attr(b);return this},g.clear=function(){for(;this.length;)this.pop()},g.splice=function(a,b){a=0>a?d(this.length+a,0):a,b=d(0,e(this.length-a,b));var c,g=[],h=[],i=[];for(c=2;c<arguments.length;c++)i.push(arguments[c]);for(c=0;b>c;c++)h.push(this[a+c]);for(;c<this.length-a;c++)g.push(this[a+c]);var j=i.length;for(c=0;c<j+g.length;c++)this.items[a+c]=this[a+c]=j>c?i[c]:g[c-j];for(c=this.items.length=this.length-=b-j;this[c];)delete this[c++];return new f(h)},g.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0;return!1},g.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},g.getBBox=function(){for(var a=[],b=[],c=[],f=[],g=this.items.length;g--;)if(!this.items[g].removed){var h=this.items[g].getBBox();a.push(h.x),b.push(h.y),c.push(h.x+h.width),f.push(h.y+h.height)}return a=e.apply(0,a),b=e.apply(0,b),c=d.apply(0,c),f=d.apply(0,f),{x:a,y:b,x2:c,y2:f,width:c-a,height:f-b,cx:a+(c-a)/2,cy:b+(f-b)/2}},g.clone=function(a){a=new f;for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},g.toString=function(){return"Snap‘s set"},g.type="set",a.Set=f,a.set=function(){var a=new f;return arguments.length&&a.push.apply(a,Array.prototype.slice.call(arguments,0)),a}}),d.plugin(function(a,c){function d(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}}function e(b,c,e){c=p(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];for(var f,g,h,i,l=Math.max(b.length,c.length),m=[],n=[],o=0;l>o;o++){if(h=b[o]||d(c[o]),i=c[o]||d(h),h[0]!=i[0]||"r"==h[0].toLowerCase()&&(h[2]!=i[2]||h[3]!=i[3])||"s"==h[0].toLowerCase()&&(h[3]!=i[3]||h[4]!=i[4])){b=a._.transform2matrix(b,e()),c=a._.transform2matrix(c,e()),m=[["m",b.a,b.b,b.c,b.d,b.e,b.f]],n=[["m",c.a,c.b,c.c,c.d,c.e,c.f]];break}for(m[o]=[],n[o]=[],f=0,g=Math.max(h.length,i.length);g>f;f++)f in h&&(m[o][f]=h[f]),f in i&&(n[o][f]=i[f])}return{from:k(m),to:k(n),f:j(m)}}function f(a){return a}function g(a){return function(b){return+b.toFixed(3)+a}}function h(a){return a.join(" ")}function i(b){return a.rgb(b[0],b[1],b[2])}function j(a){var b,c,d,e,f,g,h=0,i=[];for(b=0,c=a.length;c>b;b++){for(f="[",g=['"'+a[b][0]+'"'],d=1,e=a[b].length;e>d;d++)g[d]="val["+h++ +"]";f+=g+"]",i[b]=f}return Function("val","return Snap.path.toString.call(["+i+"])")}function k(a){for(var b=[],c=0,d=a.length;d>c;c++)for(var e=1,f=a[c].length;f>e;e++)b.push(a[c][e]);return b}function l(a){return isFinite(parseFloat(a))}function m(b,c){return a.is(b,"array")&&a.is(c,"array")?b.toString()==c.toString():!1}var n={},o=/[a-z]+$/i,p=String;n.stroke=n.fill="colour",c.prototype.equal=function(a,c){return b("snap.util.equal",this,a,c).firstDefined()},b.on("snap.util.equal",function(b,c){var d,q,r=p(this.attr(b)||""),s=this;if(l(r)&&l(c))return{from:parseFloat(r),to:parseFloat(c),f:f};if("colour"==n[b])return d=a.color(r),q=a.color(c),{from:[d.r,d.g,d.b,d.opacity],to:[q.r,q.g,q.b,q.opacity],f:i};if("viewBox"==b)return d=this.attr(b).vb.split(" ").map(Number),q=c.split(" ").map(Number),{from:d,to:q,f:h};if("transform"==b||"gradientTransform"==b||"patternTransform"==b)return c instanceof a.Matrix&&(c=c.toTransformString()),a._.rgTransform.test(c)||(c=a._.svgTransform2string(c)),e(r,c,function(){return s.getBBox(1)});if("d"==b||"path"==b)return d=a.path.toCubic(r,c),{from:k(d[0]),to:k(d[1]),f:j(d[0])};if("points"==b)return d=p(r).split(a._.separator),q=p(c).split(a._.separator),{from:d,to:q,f:function(a){return a}};var t=r.match(o),u=p(c).match(o);return t&&m(t,u)?{from:parseFloat(r),to:parseFloat(c),f:g(t)}:{from:this.asPX(b),to:this.asPX(b,c),f:f}})}),d.plugin(function(a,c,d,e){for(var f=c.prototype,g="hasOwnProperty",h=("createTouch"in e.doc),i=["click","dblclick","mousedown","mousemove","mouseout","mouseover","mouseup","touchstart","touchmove","touchend","touchcancel"],j={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},k=(function(a,b){var c="y"==a?"scrollTop":"scrollLeft",d=b&&b.node?b.node.ownerDocument:e.doc;return d[c in d.documentElement?"documentElement":"body"][c]}),l=function(){return this.originalEvent.preventDefault()},m=function(){return this.originalEvent.stopPropagation()},n=function(a,b,c,d){var e=h&&j[b]?j[b]:b,f=function(e){var f=k("y",d),i=k("x",d);if(h&&j[g](b))for(var n=0,o=e.targetTouches&&e.targetTouches.length;o>n;n++)if(e.targetTouches[n].target==a||a.contains(e.targetTouches[n].target)){var p=e;e=e.targetTouches[n],e.originalEvent=p,e.preventDefault=l,e.stopPropagation=m;break}var q=e.clientX+i,r=e.clientY+f;return c.call(d,e,q,r)};return b!==e&&a.addEventListener(b,f,!1),a.addEventListener(e,f,!1),function(){return b!==e&&a.removeEventListener(b,f,!1),a.removeEventListener(e,f,!1),!0}},o=[],p=function(a){for(var c,d=a.clientX,e=a.clientY,f=k("y"),g=k("x"),i=o.length;i--;){if(c=o[i],h){for(var j,l=a.touches&&a.touches.length;l--;)if(j=a.touches[l],j.identifier==c.el._drag.id||c.el.node.contains(j.target)){d=j.clientX,e=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();{var m=c.el.node;m.nextSibling,m.parentNode,m.style.display}d+=g,e+=f,b("snap.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},q=function(c){a.unmousemove(p).unmouseup(q);for(var d,e=o.length;e--;)d=o[e],d.el._drag={},b("snap.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c),b.off("snap.drag.*."+d.el.id);o=[]},r=i.length;r--;)!function(b){a[b]=f[b]=function(c,d){if(a.is(c,"function"))this.events=this.events||[],this.events.push({name:b,f:c,unbind:n(this.node||document,b,c,d||this)});else for(var e=0,f=this.events.length;f>e;e++)if(this.events[e].name==b)try{this.events[e].f.call(this)}catch(g){}return this},a["un"+b]=f["un"+b]=function(a){for(var c=this.events||[],d=c.length;d--;)if(c[d].name==b&&(c[d].f==a||!a))return c[d].unbind(),c.splice(d,1),!c.length&&delete this.events,this;return this}}(i[r]);f.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},f.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var s=[];f.drag=function(c,d,e,f,g,h){function i(i,j,l){(i.originalEvent||i).preventDefault(),k._drag.x=j,k._drag.y=l,k._drag.id=i.identifier,!o.length&&a.mousemove(p).mouseup(q),o.push({el:k,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("snap.drag.start."+k.id,d),c&&b.on("snap.drag.move."+k.id,c),e&&b.on("snap.drag.end."+k.id,e),b("snap.drag.start."+k.id,g||f||k,j,l,i)}function j(a,c,d){b("snap.draginit."+k.id,k,a,c,d)}var k=this;if(!arguments.length){var l;return k.drag(function(a,b){this.attr({transform:l+(l?"T":"t")+[a,b]})},function(){l=this.transform().local})}return b.on("snap.draginit."+k.id,i),k._drag={},s.push({el:k,start:i,init:j}),k.mousedown(j),k},f.undrag=function(){for(var c=s.length;c--;)s[c].el==this&&(this.unmousedown(s[c].init),s.splice(c,1),b.unbind("snap.drag.*."+this.id),b.unbind("snap.draginit."+this.id));return!s.length&&a.unmousemove(p).unmouseup(q),this}}),d.plugin(function(a,c,d){var e=(c.prototype,d.prototype),f=/^\s*url\((.+)\)/,g=String,h=a._.$;a.filter={},e.filter=function(b){var d=this;"svg"!=d.type&&(d=d.paper);var e=a.parse(g(b)),f=a._.id(),i=(d.node.offsetWidth,d.node.offsetHeight,h("filter"));return h(i,{id:f,filterUnits:"userSpaceOnUse"}),i.appendChild(e.node),d.defs.appendChild(i),new c(i)},b.on("snap.util.getattr.filter",function(){b.stop();var c=h(this.node,"filter");if(c){var d=g(c).match(f);return d&&a.select(d[1])}}),b.on("snap.util.attr.filter",function(d){if(d instanceof c&&"filter"==d.type){b.stop();var e=d.node.id;e||(h(d.node,{id:d.id}),e=d.id),h(this.node,{filter:a.url(e)})}d&&"none"!=d||(b.stop(),this.node.removeAttribute("filter"))}),a.filter.blur=function(b,c){null==b&&(b=2);var d=null==c?b:[b,c];return a.format('<feGaussianBlur stdDeviation="{def}"/>',{def:d})},a.filter.blur.toString=function(){return this()},a.filter.shadow=function(b,c,d,e,f){return"string"==typeof d&&(e=d,f=e,d=4),"string"!=typeof e&&(f=e,e="#000"),e=e||"#000",null==d&&(d=4),null==f&&(f=1),null==b&&(b=0,c=2),null==c&&(c=b),e=a.color(e),a.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>',{color:e,dx:b,dy:c,blur:d,opacity:f})},a.filter.shadow.toString=function(){return this()},a.filter.grayscale=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>',{a:.2126+.7874*(1-b),b:.7152-.7152*(1-b),c:.0722-.0722*(1-b),d:.2126-.2126*(1-b),e:.7152+.2848*(1-b),f:.0722-.0722*(1-b),g:.2126-.2126*(1-b),h:.0722+.9278*(1-b)})},a.filter.grayscale.toString=function(){return this()},a.filter.sepia=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>',{a:.393+.607*(1-b),b:.769-.769*(1-b),c:.189-.189*(1-b),d:.349-.349*(1-b),e:.686+.314*(1-b),f:.168-.168*(1-b),g:.272-.272*(1-b),h:.534-.534*(1-b),i:.131+.869*(1-b)})},a.filter.sepia.toString=function(){return this()},a.filter.saturate=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="saturate" values="{amount}"/>',{amount:1-b})},a.filter.saturate.toString=function(){return this()},a.filter.hueRotate=function(b){return b=b||0,a.format('<feColorMatrix type="hueRotate" values="{angle}"/>',{angle:b})},a.filter.hueRotate.toString=function(){return this()},a.filter.invert=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>',{amount:b,amount2:1-b})},a.filter.invert.toString=function(){return this()},a.filter.brightness=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>',{amount:b})},a.filter.brightness.toString=function(){return this()},a.filter.contrast=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>',{amount:b,amount2:.5-b/2})},a.filter.contrast.toString=function(){return this()}}),d.plugin(function(a,b){var c=a._.box,d=a.is,e=/^[^a-z]*([tbmlrc])/i,f=function(){return"T"+this.dx+","+this.dy};b.prototype.getAlign=function(a,b){null==b&&d(a,"string")&&(b=a,a=null),a=a||this.paper;var g=a.getBBox?a.getBBox():c(a),h=this.getBBox(),i={};switch(b=b&&b.match(e),b=b?b[1].toLowerCase():"c"){case"t":i.dx=0,i.dy=g.y-h.y;break;case"b":i.dx=0,i.dy=g.y2-h.y2;break;case"m":i.dx=0,i.dy=g.cy-h.cy;break;case"l":i.dx=g.x-h.x,i.dy=0;break;case"r":i.dx=g.x2-h.x2,i.dy=0;break;default:i.dx=g.cx-h.cx,i.dy=0}return i.toString=f,i},b.prototype.align=function(a,b){return this.transform("..."+this.getAlign(a,b))}}),d});
diff --git a/web/pgadmin/misc/static/explain/js/snap.svg.js b/web/pgadmin/misc/static/explain/js/snap.svg.js
new file mode 100644
index 0000000..ef0fb6d
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg.js
@@ -0,0 +1,8149 @@
+// Snap.svg 0.4.1
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// build: 2015-04-13
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ┌────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.4.2 - JavaScript Events Library                      │ \\
+// ├────────────────────────────────────────────────────────────┤ \\
+// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+// └────────────────────────────────────────────────────────────┘ \\
+(function (glob) {
+    var version = "0.4.2",
+        has = "hasOwnProperty",
+        separator = /[\.\/]/,
+        comaseparator = /\s*,\s*/,
+        wildcard = "*",
+        fun = function () {},
+        numsort = function (a, b) {
+            return a - b;
+        },
+        current_event,
+        stop,
+        events = {n: {}},
+        firstDefined = function () {
+            for (var i = 0, ii = this.length; i < ii; i++) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+        lastDefined = function () {
+            var i = this.length;
+            while (--i) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+    /*\
+     * eve
+     [ method ]
+     * Fires event with given `name`, given scope and other parameters.
+     > Arguments
+     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
+     - scope (object) context for the event handlers
+     - varargs (...) the rest of arguments will be sent to event handlers
+     = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value.
+    \*/
+        eve = function (name, scope) {
+            name = String(name);
+            var e = events,
+                oldstop = stop,
+                args = Array.prototype.slice.call(arguments, 2),
+                listeners = eve.listeners(name),
+                z = 0,
+                f = false,
+                l,
+                indexed = [],
+                queue = {},
+                out = [],
+                ce = current_event,
+                errors = [];
+            out.firstDefined = firstDefined;
+            out.lastDefined = lastDefined;
+            current_event = name;
+            stop = 0;
+            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                indexed.push(listeners[i].zIndex);
+                if (listeners[i].zIndex < 0) {
+                    queue[listeners[i].zIndex] = listeners[i];
+                }
+            }
+            indexed.sort(numsort);
+            while (indexed[z] < 0) {
+                l = queue[indexed[z++]];
+                out.push(l.apply(scope, args));
+                if (stop) {
+                    stop = oldstop;
+                    return out;
+                }
+            }
+            for (i = 0; i < ii; i++) {
+                l = listeners[i];
+                if ("zIndex" in l) {
+                    if (l.zIndex == indexed[z]) {
+                        out.push(l.apply(scope, args));
+                        if (stop) {
+                            break;
+                        }
+                        do {
+                            z++;
+                            l = queue[indexed[z]];
+                            l && out.push(l.apply(scope, args));
+                            if (stop) {
+                                break;
+                            }
+                        } while (l)
+                    } else {
+                        queue[l.zIndex] = l;
+                    }
+                } else {
+                    out.push(l.apply(scope, args));
+                    if (stop) {
+                        break;
+                    }
+                }
+            }
+            stop = oldstop;
+            current_event = ce;
+            return out;
+        };
+        // Undocumented. Debug only.
+        eve._events = events;
+    /*\
+     * eve.listeners
+     [ method ]
+     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
+     > Arguments
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated
+     = (array) array of event handlers
+    \*/
+    eve.listeners = function (name) {
+        var names = name.split(separator),
+            e = events,
+            item,
+            items,
+            k,
+            i,
+            ii,
+            j,
+            jj,
+            nes,
+            es = [e],
+            out = [];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            nes = [];
+            for (j = 0, jj = es.length; j < jj; j++) {
+                e = es[j].n;
+                items = [e[names[i]], e[wildcard]];
+                k = 2;
+                while (k--) {
+                    item = items[k];
+                    if (item) {
+                        nes.push(item);
+                        out = out.concat(item.f || []);
+                    }
+                }
+            }
+            es = nes;
+        }
+        return out;
+    };
+    /*\
+     * eve.on
+     [ method ]
+     **
+     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
+     | eve.on("*.under.*", f);
+     | eve("mouse.under.floor"); // triggers f
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
+     > Example:
+     | eve.on("mouse", eatIt)(2);
+     | eve.on("mouse", scream);
+     | eve.on("mouse", catchIt)(1);
+     * This will ensure that `catchIt` function will be called before `eatIt`.
+     *
+     * If you want to put your handler before non-indexed handlers, specify a negative value.
+     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
+    \*/
+    eve.on = function (name, f) {
+        name = String(name);
+        if (typeof f != "function") {
+            return function () {};
+        }
+        var names = name.split(comaseparator);
+        for (var i = 0, ii = names.length; i < ii; i++) {
+            (function (name) {
+                var names = name.split(separator),
+                    e = events,
+                    exist;
+                for (var i = 0, ii = names.length; i < ii; i++) {
+                    e = e.n;
+                    e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
+                }
+                e.f = e.f || [];
+                for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+                    exist = true;
+                    break;
+                }
+                !exist && e.f.push(f);
+            }(names[i]));
+        }
+        return function (zIndex) {
+            if (+zIndex == +zIndex) {
+                f.zIndex = +zIndex;
+            }
+        };
+    };
+    /*\
+     * eve.f
+     [ method ]
+     **
+     * Returns function that will fire given event with optional arguments.
+     * Arguments that will be passed to the result function will be also
+     * concated to the list of final arguments.
+     | el.onclick = eve.f("click", 1, 2);
+     | eve.on("click", function (a, b, c) {
+     |     console.log(a, b, c); // 1, 2, [event object]
+     | });
+     > Arguments
+     - event (string) event name
+     - varargs (…) and any other arguments
+     = (function) possible event handler function
+    \*/
+    eve.f = function (event) {
+        var attrs = [].slice.call(arguments, 1);
+        return function () {
+            eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
+        };
+    };
+    /*\
+     * eve.stop
+     [ method ]
+     **
+     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
+    \*/
+    eve.stop = function () {
+        stop = 1;
+    };
+    /*\
+     * eve.nt
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     > Arguments
+     **
+     - subname (string) #optional subname of the event
+     **
+     = (string) name of the event, if `subname` is not specified
+     * or
+     = (boolean) `true`, if current event’s name contains `subname`
+    \*/
+    eve.nt = function (subname) {
+        if (subname) {
+            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+        }
+        return current_event;
+    };
+    /*\
+     * eve.nts
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     **
+     = (array) names of the event
+    \*/
+    eve.nts = function () {
+        return current_event.split(separator);
+    };
+    /*\
+     * eve.off
+     [ method ]
+     **
+     * Removes given function from the list of event listeners assigned to given name.
+     * If no arguments specified all the events will be cleared.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+    \*/
+    /*\
+     * eve.unbind
+     [ method ]
+     **
+     * See @eve.off
+    \*/
+    eve.off = eve.unbind = function (name, f) {
+        if (!name) {
+            eve._events = events = {n: {}};
+            return;
+        }
+        var names = name.split(comaseparator);
+        if (names.length > 1) {
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                eve.off(names[i], f);
+            }
+            return;
+        }
+        names = name.split(separator);
+        var e,
+            key,
+            splice,
+            i, ii, j, jj,
+            cur = [events];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
+                splice = [j, 1];
+                e = cur[j].n;
+                if (names[i] != wildcard) {
+                    if (e[names[i]]) {
+                        splice.push(e[names[i]]);
+                    }
+                } else {
+                    for (key in e) if (e[has](key)) {
+                        splice.push(e[key]);
+                    }
+                }
+                cur.splice.apply(cur, splice);
+            }
+        }
+        for (i = 0, ii = cur.length; i < ii; i++) {
+            e = cur[i];
+            while (e.n) {
+                if (f) {
+                    if (e.f) {
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
+                            break;
+                        }
+                        !e.f.length && delete e.f;
+                    }
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        var funcs = e.n[key].f;
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
+                            break;
+                        }
+                        !funcs.length && delete e.n[key].f;
+                    }
+                } else {
+                    delete e.f;
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        delete e.n[key].f;
+                    }
+                }
+                e = e.n;
+            }
+        }
+    };
+    /*\
+     * eve.once
+     [ method ]
+     **
+     * Binds given event handler with a given name to only run once then unbind itself.
+     | eve.once("login", f);
+     | eve("login"); // triggers f
+     | eve("login"); // no listeners
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) same return function as @eve.on
+    \*/
+    eve.once = function (name, f) {
+        var f2 = function () {
+            eve.unbind(name, f2);
+            return f.apply(this, arguments);
+        };
+        return eve.on(name, f2);
+    };
+    /*\
+     * eve.version
+     [ property (string) ]
+     **
+     * Current version of the library.
+    \*/
+    eve.version = version;
+    eve.toString = function () {
+        return "You are running Eve " + version;
+    };
+    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
+})(this);
+
+(function (glob, factory) {
+    // AMD support
+    if (typeof define == "function" && define.amd) {
+        // Define as an anonymous module
+        define(["eve"], function (eve) {
+            return factory(glob, eve);
+        });
+    } else if (typeof exports != 'undefined') {
+        // Next for Node.js or CommonJS
+        var eve = require('eve');
+        module.exports = factory(glob, eve);
+    } else {
+        // Browser globals (glob is window)
+        // Snap adds itself to window
+        factory(glob, glob.eve);
+    }
+}(window || this, function (window, eve) {
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+var mina = (function (eve) {
+    var animations = {},
+    requestAnimFrame = window.requestAnimationFrame       ||
+                       window.webkitRequestAnimationFrame ||
+                       window.mozRequestAnimationFrame    ||
+                       window.oRequestAnimationFrame      ||
+                       window.msRequestAnimationFrame     ||
+                       function (callback) {
+                           setTimeout(callback, 16);
+                       },
+    isArray = Array.isArray || function (a) {
+        return a instanceof Array ||
+            Object.prototype.toString.call(a) == "[object Array]";
+    },
+    idgen = 0,
+    idprefix = "M" + (+new Date).toString(36),
+    ID = function () {
+        return idprefix + (idgen++).toString(36);
+    },
+    diff = function (a, b, A, B) {
+        if (isArray(a)) {
+            res = [];
+            for (var i = 0, ii = a.length; i < ii; i++) {
+                res[i] = diff(a[i], b, A[i], B);
+            }
+            return res;
+        }
+        var dif = (A - a) / (B - b);
+        return function (bb) {
+            return a + dif * (bb - b);
+        };
+    },
+    timer = Date.now || function () {
+        return +new Date;
+    },
+    sta = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.s;
+        }
+        var ds = a.s - val;
+        a.b += a.dur * ds;
+        a.B += a.dur * ds;
+        a.s = val;
+    },
+    speed = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.spd;
+        }
+        a.spd = val;
+    },
+    duration = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.dur;
+        }
+        a.s = a.s * val / a.dur;
+        a.dur = val;
+    },
+    stopit = function () {
+        var a = this;
+        delete animations[a.id];
+        a.update();
+        eve("mina.stop." + a.id, a);
+    },
+    pause = function () {
+        var a = this;
+        if (a.pdif) {
+            return;
+        }
+        delete animations[a.id];
+        a.update();
+        a.pdif = a.get() - a.b;
+    },
+    resume = function () {
+        var a = this;
+        if (!a.pdif) {
+            return;
+        }
+        a.b = a.get() - a.pdif;
+        delete a.pdif;
+        animations[a.id] = a;
+    },
+    update = function () {
+        var a = this,
+            res;
+        if (isArray(a.start)) {
+            res = [];
+            for (var j = 0, jj = a.start.length; j < jj; j++) {
+                res[j] = +a.start[j] +
+                    (a.end[j] - a.start[j]) * a.easing(a.s);
+            }
+        } else {
+            res = +a.start + (a.end - a.start) * a.easing(a.s);
+        }
+        a.set(res);
+    },
+    frame = function () {
+        var len = 0;
+        for (var i in animations) if (animations.hasOwnProperty(i)) {
+            var a = animations[i],
+                b = a.get(),
+                res;
+            len++;
+            a.s = (b - a.b) / (a.dur / a.spd);
+            if (a.s >= 1) {
+                delete animations[i];
+                a.s = 1;
+                len--;
+                (function (a) {
+                    setTimeout(function () {
+                        eve("mina.finish." + a.id, a);
+                    });
+                }(a));
+            }
+            a.update();
+        }
+        len && requestAnimFrame(frame);
+    },
+    /*\
+     * mina
+     [ method ]
+     **
+     * Generic animation of numbers
+     **
+     - a (number) start _slave_ number
+     - A (number) end _slave_ number
+     - b (number) start _master_ number (start time in general case)
+     - B (number) end _master_ number (end time in gereal case)
+     - get (function) getter of _master_ number (see @mina.time)
+     - set (function) setter of _slave_ number
+     - easing (function) #optional easing function, default is @mina.linear
+     = (object) animation descriptor
+     o {
+     o         id (string) animation id,
+     o         start (number) start _slave_ number,
+     o         end (number) end _slave_ number,
+     o         b (number) start _master_ number,
+     o         s (number) animation status (0..1),
+     o         dur (number) animation duration,
+     o         spd (number) animation speed,
+     o         get (function) getter of _master_ number (see @mina.time),
+     o         set (function) setter of _slave_ number,
+     o         easing (function) easing function, default is @mina.linear,
+     o         status (function) status getter/setter,
+     o         speed (function) speed getter/setter,
+     o         duration (function) duration getter/setter,
+     o         stop (function) animation stopper
+     o         pause (function) pauses the animation
+     o         resume (function) resumes the animation
+     o         update (function) calles setter with the right value of the animation
+     o }
+    \*/
+    mina = function (a, A, b, B, get, set, easing) {
+        var anim = {
+            id: ID(),
+            start: a,
+            end: A,
+            b: b,
+            s: 0,
+            dur: B - b,
+            spd: 1,
+            get: get,
+            set: set,
+            easing: easing || mina.linear,
+            status: sta,
+            speed: speed,
+            duration: duration,
+            stop: stopit,
+            pause: pause,
+            resume: resume,
+            update: update
+        };
+        animations[anim.id] = anim;
+        var len = 0, i;
+        for (i in animations) if (animations.hasOwnProperty(i)) {
+            len++;
+            if (len == 2) {
+                break;
+            }
+        }
+        len == 1 && requestAnimFrame(frame);
+        return anim;
+    };
+    /*\
+     * mina.time
+     [ method ]
+     **
+     * Returns the current time. Equivalent to:
+     | function () {
+     |     return (new Date).getTime();
+     | }
+    \*/
+    mina.time = timer;
+    /*\
+     * mina.getById
+     [ method ]
+     **
+     * Returns an animation by its id
+     - id (string) animation's id
+     = (object) See @mina
+    \*/
+    mina.getById = function (id) {
+        return animations[id] || null;
+    };
+
+    /*\
+     * mina.linear
+     [ method ]
+     **
+     * Default linear easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.linear = function (n) {
+        return n;
+    };
+    /*\
+     * mina.easeout
+     [ method ]
+     **
+     * Easeout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeout = function (n) {
+        return Math.pow(n, 1.7);
+    };
+    /*\
+     * mina.easein
+     [ method ]
+     **
+     * Easein easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easein = function (n) {
+        return Math.pow(n, .48);
+    };
+    /*\
+     * mina.easeinout
+     [ method ]
+     **
+     * Easeinout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeinout = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        if (n == 0) {
+            return 0;
+        }
+        var q = .48 - n / 1.04,
+            Q = Math.sqrt(.1734 + q * q),
+            x = Q - q,
+            X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+            y = -Q - q,
+            Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+            t = X + Y + .5;
+        return (1 - t) * 3 * t * t + t * t * t;
+    };
+    /*\
+     * mina.backin
+     [ method ]
+     **
+     * Backin easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backin = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        var s = 1.70158;
+        return n * n * ((s + 1) * n - s);
+    };
+    /*\
+     * mina.backout
+     [ method ]
+     **
+     * Backout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backout = function (n) {
+        if (n == 0) {
+            return 0;
+        }
+        n = n - 1;
+        var s = 1.70158;
+        return n * n * ((s + 1) * n + s) + 1;
+    };
+    /*\
+     * mina.elastic
+     [ method ]
+     **
+     * Elastic easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.elastic = function (n) {
+        if (n == !!n) {
+            return n;
+        }
+        return Math.pow(2, -10 * n) * Math.sin((n - .075) *
+            (2 * Math.PI) / .3) + 1;
+    };
+    /*\
+     * mina.bounce
+     [ method ]
+     **
+     * Bounce easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.bounce = function (n) {
+        var s = 7.5625,
+            p = 2.75,
+            l;
+        if (n < (1 / p)) {
+            l = s * n * n;
+        } else {
+            if (n < (2 / p)) {
+                n -= (1.5 / p);
+                l = s * n * n + .75;
+            } else {
+                if (n < (2.5 / p)) {
+                    n -= (2.25 / p);
+                    l = s * n * n + .9375;
+                } else {
+                    n -= (2.625 / p);
+                    l = s * n * n + .984375;
+                }
+            }
+        }
+        return l;
+    };
+    window.mina = mina;
+    return mina;
+})(typeof eve == "undefined" ? function () {} : eve);
+// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var Snap = (function(root) {
+Snap.version = "0.4.0";
+/*\
+ * Snap
+ [ method ]
+ **
+ * Creates a drawing surface or wraps existing SVG element.
+ **
+ - width (number|string) width of surface
+ - height (number|string) height of surface
+ * or
+ - DOM (SVGElement) element to be wrapped into Snap structure
+ * or
+ - array (array) array of elements (will return set of elements)
+ * or
+ - query (string) CSS query selector
+ = (object) @Element
+\*/
+function Snap(w, h) {
+    if (w) {
+        if (w.nodeType) {
+            return wrap(w);
+        }
+        if (is(w, "array") && Snap.set) {
+            return Snap.set.apply(Snap, w);
+        }
+        if (w instanceof Element) {
+            return w;
+        }
+        if (h == null) {
+            w = glob.doc.querySelector(String(w));
+            return wrap(w);
+        }
+    }
+    w = w == null ? "100%" : w;
+    h = h == null ? "100%" : h;
+    return new Paper(w, h);
+}
+Snap.toString = function () {
+    return "Snap v" + this.version;
+};
+Snap._ = {};
+var glob = {
+    win: root.window,
+    doc: root.window.document
+};
+Snap._.glob = glob;
+var has = "hasOwnProperty",
+    Str = String,
+    toFloat = parseFloat,
+    toInt = parseInt,
+    math = Math,
+    mmax = math.max,
+    mmin = math.min,
+    abs = math.abs,
+    pow = math.pow,
+    PI = math.PI,
+    round = math.round,
+    E = "",
+    S = " ",
+    objectToString = Object.prototype.toString,
+    ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+    colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,
+    bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+    reURLValue = /^url\(#?([^)]+)\)$/,
+    separator = Snap._.separator = /[,\s]+/,
+    whitespace = /[\s]/g,
+    commaSpaces = /[\s]*,[\s]*/,
+    hsrg = {hs: 1, rg: 1},
+    pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig,
+    idgen = 0,
+    idprefix = "S" + (+new Date).toString(36),
+    ID = function (el) {
+        return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36);
+    },
+    xlink = "http://www.w3.org/1999/xlink",
+    xmlns = "http://www.w3.org/2000/svg",
+    hub = {},
+    URL = Snap.url = function (url) {
+        return "url('#" + url + "')";
+    };
+
+function $(el, attr) {
+    if (attr) {
+        if (el == "#text") {
+            el = glob.doc.createTextNode(attr.text || attr["#text"] || "");
+        }
+        if (el == "#comment") {
+            el = glob.doc.createComment(attr.text || attr["#text"] || "");
+        }
+        if (typeof el == "string") {
+            el = $(el);
+        }
+        if (typeof attr == "string") {
+            if (el.nodeType == 1) {
+                if (attr.substring(0, 6) == "xlink:") {
+                    return el.getAttributeNS(xlink, attr.substring(6));
+                }
+                if (attr.substring(0, 4) == "xml:") {
+                    return el.getAttributeNS(xmlns, attr.substring(4));
+                }
+                return el.getAttribute(attr);
+            } else if (attr == "text") {
+                return el.nodeValue;
+            } else {
+                return null;
+            }
+        }
+        if (el.nodeType == 1) {
+            for (var key in attr) if (attr[has](key)) {
+                var val = Str(attr[key]);
+                if (val) {
+                    if (key.substring(0, 6) == "xlink:") {
+                        el.setAttributeNS(xlink, key.substring(6), val);
+                    } else if (key.substring(0, 4) == "xml:") {
+                        el.setAttributeNS(xmlns, key.substring(4), val);
+                    } else {
+                        el.setAttribute(key, val);
+                    }
+                } else {
+                    el.removeAttribute(key);
+                }
+            }
+        } else if ("text" in attr) {
+            el.nodeValue = attr.text;
+        }
+    } else {
+        el = glob.doc.createElementNS(xmlns, el);
+    }
+    return el;
+}
+Snap._.$ = $;
+Snap._.id = ID;
+function getAttrs(el) {
+    var attrs = el.attributes,
+        name,
+        out = {};
+    for (var i = 0; i < attrs.length; i++) {
+        if (attrs[i].namespaceURI == xlink) {
+            name = "xlink:";
+        } else {
+            name = "";
+        }
+        name += attrs[i].name;
+        out[name] = attrs[i].textContent;
+    }
+    return out;
+}
+function is(o, type) {
+    type = Str.prototype.toLowerCase.call(type);
+    if (type == "finite") {
+        return isFinite(o);
+    }
+    if (type == "array" &&
+        (o instanceof Array || Array.isArray && Array.isArray(o))) {
+        return true;
+    }
+    return  (type == "null" && o === null) ||
+            (type == typeof o && o !== null) ||
+            (type == "object" && o === Object(o)) ||
+            objectToString.call(o).slice(8, -1).toLowerCase() == type;
+}
+/*\
+ * Snap.format
+ [ method ]
+ **
+ * Replaces construction of type `{<name>}` to the corresponding argument
+ **
+ - token (string) string to format
+ - json (object) object which properties are used as a replacement
+ = (string) formatted string
+ > Usage
+ | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z"
+ | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
+ |     x: 10,
+ |     y: 20,
+ |     dim: {
+ |         width: 40,
+ |         height: 50,
+ |         "negative width": -40
+ |     }
+ | }));
+\*/
+Snap.format = (function () {
+    var tokenRegex = /\{([^\}]+)\}/g,
+        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+        replacer = function (all, key, obj) {
+            var res = obj;
+            key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+                name = name || quotedName;
+                if (res) {
+                    if (name in res) {
+                        res = res[name];
+                    }
+                    typeof res == "function" && isFunc && (res = res());
+                }
+            });
+            res = (res == null || res == obj ? all : res) + "";
+            return res;
+        };
+    return function (str, obj) {
+        return Str(str).replace(tokenRegex, function (all, key) {
+            return replacer(all, key, obj);
+        });
+    };
+})();
+function clone(obj) {
+    if (typeof obj == "function" || Object(obj) !== obj) {
+        return obj;
+    }
+    var res = new obj.constructor;
+    for (var key in obj) if (obj[has](key)) {
+        res[key] = clone(obj[key]);
+    }
+    return res;
+}
+Snap._.clone = clone;
+function repush(array, item) {
+    for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+        return array.push(array.splice(i, 1)[0]);
+    }
+}
+function cacher(f, scope, postprocessor) {
+    function newf() {
+        var arg = Array.prototype.slice.call(arguments, 0),
+            args = arg.join("\u2400"),
+            cache = newf.cache = newf.cache || {},
+            count = newf.count = newf.count || [];
+        if (cache[has](args)) {
+            repush(count, args);
+            return postprocessor ? postprocessor(cache[args]) : cache[args];
+        }
+        count.length >= 1e3 && delete cache[count.shift()];
+        count.push(args);
+        cache[args] = f.apply(scope, arg);
+        return postprocessor ? postprocessor(cache[args]) : cache[args];
+    }
+    return newf;
+}
+Snap._.cacher = cacher;
+function angle(x1, y1, x2, y2, x3, y3) {
+    if (x3 == null) {
+        var x = x1 - x2,
+            y = y1 - y2;
+        if (!x && !y) {
+            return 0;
+        }
+        return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+    } else {
+        return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3);
+    }
+}
+function rad(deg) {
+    return deg % 360 * PI / 180;
+}
+function deg(rad) {
+    return rad * 180 / PI % 360;
+}
+function x_y() {
+    return this.x + S + this.y;
+}
+function x_y_w_h() {
+    return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+}
+
+/*\
+ * Snap.rad
+ [ method ]
+ **
+ * Transform angle to radians
+ - deg (number) angle in degrees
+ = (number) angle in radians
+\*/
+Snap.rad = rad;
+/*\
+ * Snap.deg
+ [ method ]
+ **
+ * Transform angle to degrees
+ - rad (number) angle in radians
+ = (number) angle in degrees
+\*/
+Snap.deg = deg;
+/*\
+ * Snap.sin
+ [ method ]
+ **
+ * Equivalent to `Math.sin()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) sin
+\*/
+Snap.sin = function (angle) {
+    return math.sin(Snap.rad(angle));
+};
+/*\
+ * Snap.tan
+ [ method ]
+ **
+ * Equivalent to `Math.tan()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) tan
+\*/
+Snap.tan = function (angle) {
+    return math.tan(Snap.rad(angle));
+};
+/*\
+ * Snap.cos
+ [ method ]
+ **
+ * Equivalent to `Math.cos()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) cos
+\*/
+Snap.cos = function (angle) {
+    return math.cos(Snap.rad(angle));
+};
+/*\
+ * Snap.asin
+ [ method ]
+ **
+ * Equivalent to `Math.asin()` only works with degrees, not radians.
+ - num (number) value
+ = (number) asin in degrees
+\*/
+Snap.asin = function (num) {
+    return Snap.deg(math.asin(num));
+};
+/*\
+ * Snap.acos
+ [ method ]
+ **
+ * Equivalent to `Math.acos()` only works with degrees, not radians.
+ - num (number) value
+ = (number) acos in degrees
+\*/
+Snap.acos = function (num) {
+    return Snap.deg(math.acos(num));
+};
+/*\
+ * Snap.atan
+ [ method ]
+ **
+ * Equivalent to `Math.atan()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan in degrees
+\*/
+Snap.atan = function (num) {
+    return Snap.deg(math.atan(num));
+};
+/*\
+ * Snap.atan2
+ [ method ]
+ **
+ * Equivalent to `Math.atan2()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan2 in degrees
+\*/
+Snap.atan2 = function (num) {
+    return Snap.deg(math.atan2(num));
+};
+/*\
+ * Snap.angle
+ [ method ]
+ **
+ * Returns an angle between two or three points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ - x3 (number) #optional x coord of third point
+ - y3 (number) #optional y coord of third point
+ = (number) angle in degrees
+\*/
+Snap.angle = angle;
+/*\
+ * Snap.len
+ [ method ]
+ **
+ * Returns distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len = function (x1, y1, x2, y2) {
+    return Math.sqrt(Snap.len2(x1, y1, x2, y2));
+};
+/*\
+ * Snap.len2
+ [ method ]
+ **
+ * Returns squared distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len2 = function (x1, y1, x2, y2) {
+    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
+};
+/*\
+ * Snap.closestPoint
+ [ method ]
+ **
+ * Returns closest point to a given one on a given path.
+ > Parameters
+ - path (Element) path element
+ - x (number) x coord of a point
+ - y (number) y coord of a point
+ = (object) in format
+ {
+    x (number) x coord of the point on the path
+    y (number) y coord of the point on the path
+    length (number) length of the path to the point
+    distance (number) distance from the given point to the path
+ }
+\*/
+// Copied from http://bl.ocks.org/mbostock/8027637
+Snap.closestPoint = function (path, x, y) {
+    function distance2(p) {
+        var dx = p.x - x,
+            dy = p.y - y;
+        return dx * dx + dy * dy;
+    }
+    var pathNode = path.node,
+        pathLength = pathNode.getTotalLength(),
+        precision = pathLength / pathNode.pathSegList.numberOfItems * .125,
+        best,
+        bestLength,
+        bestDistance = Infinity;
+
+    // linear scan for coarse approximation
+    for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
+        if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
+            best = scan, bestLength = scanLength, bestDistance = scanDistance;
+        }
+    }
+
+    // binary search for precise estimate
+    precision *= .5;
+    while (precision > .5) {
+        var before,
+            after,
+            beforeLength,
+            afterLength,
+            beforeDistance,
+            afterDistance;
+        if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
+            best = before, bestLength = beforeLength, bestDistance = beforeDistance;
+        } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
+            best = after, bestLength = afterLength, bestDistance = afterDistance;
+        } else {
+            precision *= .5;
+        }
+    }
+
+    best = {
+        x: best.x,
+        y: best.y,
+        length: bestLength,
+        distance: Math.sqrt(bestDistance)
+    };
+    return best;
+}
+/*\
+ * Snap.is
+ [ method ]
+ **
+ * Handy replacement for the `typeof` operator
+ - o (…) any object or primitive
+ - type (string) name of the type, e.g., `string`, `function`, `number`, etc.
+ = (boolean) `true` if given value is of given type
+\*/
+Snap.is = is;
+/*\
+ * Snap.snapTo
+ [ method ]
+ **
+ * Snaps given value to given grid
+ - values (array|number) given array of values or step of the grid
+ - value (number) value to adjust
+ - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`.
+ = (number) adjusted value
+\*/
+Snap.snapTo = function (values, value, tolerance) {
+    tolerance = is(tolerance, "finite") ? tolerance : 10;
+    if (is(values, "array")) {
+        var i = values.length;
+        while (i--) if (abs(values[i] - value) <= tolerance) {
+            return values[i];
+        }
+    } else {
+        values = +values;
+        var rem = value % values;
+        if (rem < tolerance) {
+            return value - rem;
+        }
+        if (rem > values - tolerance) {
+            return value - rem + values;
+        }
+    }
+    return value;
+};
+// Colour
+/*\
+ * Snap.getRGB
+ [ method ]
+ **
+ * Parses color string as RGB object
+ - color (string) color string in one of the following formats:
+ # <ul>
+ #     <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li>
+ #     <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li>
+ #     <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li>
+ #     <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200,&nbsp;100,&nbsp;0)</code>)</li>
+ #     <li>rgba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li>
+ #     <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>)</li>
+ #     <li>hsba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;0.5)</code>)</li>
+ #     <li>hsla(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li>
+ # </ul>
+ * Note that `%` can be used any time: `rgb(20%, 255, 50%)`.
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) true if string can't be parsed
+ o }
+\*/
+Snap.getRGB = cacher(function (colour) {
+    if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    if (colour == "none") {
+        return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString};
+    }
+    !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+    if (!colour) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    var res,
+        red,
+        green,
+        blue,
+        opacity,
+        t,
+        values,
+        rgb = colour.match(colourRegExp);
+    if (rgb) {
+        if (rgb[2]) {
+            blue = toInt(rgb[2].substring(5), 16);
+            green = toInt(rgb[2].substring(3, 5), 16);
+            red = toInt(rgb[2].substring(1, 3), 16);
+        }
+        if (rgb[3]) {
+            blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+            green = toInt((t = rgb[3].charAt(2)) + t, 16);
+            red = toInt((t = rgb[3].charAt(1)) + t, 16);
+        }
+        if (rgb[4]) {
+            values = rgb[4].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red *= 2.55);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green *= 2.55);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue *= 2.55);
+            rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+        }
+        if (rgb[5]) {
+            values = rgb[5].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsb2rgb(red, green, blue, opacity);
+        }
+        if (rgb[6]) {
+            values = rgb[6].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsl2rgb(red, green, blue, opacity);
+        }
+        red = mmin(math.round(red), 255);
+        green = mmin(math.round(green), 255);
+        blue = mmin(math.round(blue), 255);
+        opacity = mmin(mmax(opacity, 0), 1);
+        rgb = {r: red, g: green, b: blue, toString: rgbtoString};
+        rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+        rgb.opacity = is(opacity, "finite") ? opacity : 1;
+        return rgb;
+    }
+    return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+}, Snap);
+/*\
+ * Snap.hsb
+ [ method ]
+ **
+ * Converts HSB values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - b (number) value or brightness
+ = (string) hex representation of the color
+\*/
+Snap.hsb = cacher(function (h, s, b) {
+    return Snap.hsb2rgb(h, s, b).hex;
+});
+/*\
+ * Snap.hsl
+ [ method ]
+ **
+ * Converts HSL values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (string) hex representation of the color
+\*/
+Snap.hsl = cacher(function (h, s, l) {
+    return Snap.hsl2rgb(h, s, l).hex;
+});
+/*\
+ * Snap.rgb
+ [ method ]
+ **
+ * Converts RGB values to a hex representation of the color
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (string) hex representation of the color
+\*/
+Snap.rgb = cacher(function (r, g, b, o) {
+    if (is(o, "finite")) {
+        var round = math.round;
+        return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")";
+    }
+    return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+});
+var toHex = function (color) {
+    var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0],
+        red = "rgb(255, 0, 0)";
+    toHex = cacher(function (color) {
+        if (color.toLowerCase() == "red") {
+            return red;
+        }
+        i.style.color = red;
+        i.style.color = color;
+        var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+        return out == red ? null : out;
+    });
+    return toHex(color);
+},
+hsbtoString = function () {
+    return "hsb(" + [this.h, this.s, this.b] + ")";
+},
+hsltoString = function () {
+    return "hsl(" + [this.h, this.s, this.l] + ")";
+},
+rgbtoString = function () {
+    return this.opacity == 1 || this.opacity == null ?
+            this.hex :
+            "rgba(" + [this.r, this.g, this.b, this.opacity] + ")";
+},
+prepareRGB = function (r, g, b) {
+    if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) {
+        b = r.b;
+        g = r.g;
+        r = r.r;
+    }
+    if (g == null && is(r, string)) {
+        var clr = Snap.getRGB(r);
+        r = clr.r;
+        g = clr.g;
+        b = clr.b;
+    }
+    if (r > 1 || g > 1 || b > 1) {
+        r /= 255;
+        g /= 255;
+        b /= 255;
+    }
+
+    return [r, g, b];
+},
+packageRGB = function (r, g, b, o) {
+    r = math.round(r * 255);
+    g = math.round(g * 255);
+    b = math.round(b * 255);
+    var rgb = {
+        r: r,
+        g: g,
+        b: b,
+        opacity: is(o, "finite") ? o : 1,
+        hex: Snap.rgb(r, g, b),
+        toString: rgbtoString
+    };
+    is(o, "finite") && (rgb.opacity = o);
+    return rgb;
+};
+/*\
+ * Snap.color
+ [ method ]
+ **
+ * Parses the color string and returns an object featuring the color's component values
+ - clr (string) color string in one of the supported formats (see @Snap.getRGB)
+ = (object) Combined RGB/HSB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) `true` if string can't be parsed,
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     v (number) value (brightness),
+ o     l (number) lightness
+ o }
+\*/
+Snap.color = function (clr) {
+    var rgb;
+    if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+        rgb = Snap.hsb2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+        rgb = Snap.hsl2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else {
+        if (is(clr, "string")) {
+            clr = Snap.getRGB(clr);
+        }
+        if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) {
+            rgb = Snap.rgb2hsl(clr);
+            clr.h = rgb.h;
+            clr.s = rgb.s;
+            clr.l = rgb.l;
+            rgb = Snap.rgb2hsb(clr);
+            clr.v = rgb.b;
+        } else {
+            clr = {hex: "none"};
+            clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+            clr.error = 1;
+        }
+    }
+    clr.toString = rgbtoString;
+    return clr;
+};
+/*\
+ * Snap.hsb2rgb
+ [ method ]
+ **
+ * Converts HSB values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - v (number) value or brightness
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsb2rgb = function (h, s, v, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "b" in h) {
+        v = h.b;
+        s = h.s;
+        o = h.o;
+        h = h.h;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = v * s;
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = v - C;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.hsl2rgb
+ [ method ]
+ **
+ * Converts HSL values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsl2rgb = function (h, s, l, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "l" in h) {
+        l = h.l;
+        s = h.s;
+        h = h.h;
+    }
+    if (h > 1 || s > 1 || l > 1) {
+        h /= 360;
+        s /= 100;
+        l /= 100;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = 2 * s * (l < .5 ? l : 1 - l);
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = l - C / 2;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.rgb2hsb
+ [ method ]
+ **
+ * Converts RGB values to an HSB object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSB object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     b (number) brightness
+ o }
+\*/
+Snap.rgb2hsb = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, V, C;
+    V = mmax(r, g, b);
+    C = V - mmin(r, g, b);
+    H = (C == 0 ? null :
+         V == r ? (g - b) / C :
+         V == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4
+        );
+    H = ((H + 360) % 6) * 60 / 360;
+    S = C == 0 ? 0 : C / V;
+    return {h: H, s: S, b: V, toString: hsbtoString};
+};
+/*\
+ * Snap.rgb2hsl
+ [ method ]
+ **
+ * Converts RGB values to an HSL object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSL object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     l (number) luminosity
+ o }
+\*/
+Snap.rgb2hsl = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, L, M, m, C;
+    M = mmax(r, g, b);
+    m = mmin(r, g, b);
+    C = M - m;
+    H = (C == 0 ? null :
+         M == r ? (g - b) / C :
+         M == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4);
+    H = ((H + 360) % 6) * 60 / 360;
+    L = (M + m) / 2;
+    S = (C == 0 ? 0 :
+         L < .5 ? C / (2 * L) :
+                  C / (2 - 2 * L));
+    return {h: H, s: S, l: L, toString: hsltoString};
+};
+
+// Transformations
+/*\
+ * Snap.parsePathString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given path string into an array of arrays of path segments
+ - pathString (string|array) path string or array of segments (in the last case it is returned straight away)
+ = (array) array of segments
+\*/
+Snap.parsePathString = function (pathString) {
+    if (!pathString) {
+        return null;
+    }
+    var pth = Snap.path(pathString);
+    if (pth.arr) {
+        return Snap.path.clone(pth.arr);
+    }
+
+    var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
+        data = [];
+    if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
+        data = Snap.path.clone(pathString);
+    }
+    if (!data.length) {
+        Str(pathString).replace(pathCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            if (name == "m" && params.length > 2) {
+                data.push([b].concat(params.splice(0, 2)));
+                name = "l";
+                b = b == "m" ? "l" : "L";
+            }
+            if (name == "o" && params.length == 1) {
+                data.push([b, params[0]]);
+            }
+            if (name == "r") {
+                data.push([b].concat(params));
+            } else while (params.length >= paramCounts[name]) {
+                data.push([b].concat(params.splice(0, paramCounts[name])));
+                if (!paramCounts[name]) {
+                    break;
+                }
+            }
+        });
+    }
+    data.toString = Snap.path.toString;
+    pth.arr = Snap.path.clone(data);
+    return data;
+};
+/*\
+ * Snap.parseTransformString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given transform string into an array of transformations
+ - TString (string|array) transform string or array of transformations (in the last case it is returned straight away)
+ = (array) array of transformations
+\*/
+var parseTransformString = Snap.parseTransformString = function (TString) {
+    if (!TString) {
+        return null;
+    }
+    var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+        data = [];
+    if (is(TString, "array") && is(TString[0], "array")) { // rough assumption
+        data = Snap.path.clone(TString);
+    }
+    if (!data.length) {
+        Str(TString).replace(tCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            data.push([b].concat(params));
+        });
+    }
+    data.toString = Snap.path.toString;
+    return data;
+};
+function svgTransform2string(tstr) {
+    var res = [];
+    tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
+        params = params.split(/\s*,\s*|\s+/);
+        if (name == "rotate" && params.length == 1) {
+            params.push(0, 0);
+        }
+        if (name == "scale") {
+            if (params.length > 2) {
+                params = params.slice(0, 2);
+            } else if (params.length == 2) {
+                params.push(0, 0);
+            }
+            if (params.length == 1) {
+                params.push(params[0], 0, 0);
+            }
+        }
+        if (name == "skewX") {
+            res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]);
+        } else if (name == "skewY") {
+            res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]);
+        } else {
+            res.push([name.charAt(0)].concat(params));
+        }
+        return all;
+    });
+    return res;
+}
+Snap._.svgTransform2string = svgTransform2string;
+Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i;
+function transform2matrix(tstr, bbox) {
+    var tdata = parseTransformString(tstr),
+        m = new Snap.Matrix;
+    if (tdata) {
+        for (var i = 0, ii = tdata.length; i < ii; i++) {
+            var t = tdata[i],
+                tlen = t.length,
+                command = Str(t[0]).toLowerCase(),
+                absolute = t[0] != command,
+                inver = absolute ? m.invert() : 0,
+                x1,
+                y1,
+                x2,
+                y2,
+                bb;
+            if (command == "t" && tlen == 2){
+                m.translate(t[1], 0);
+            } else if (command == "t" && tlen == 3) {
+                if (absolute) {
+                    x1 = inver.x(0, 0);
+                    y1 = inver.y(0, 0);
+                    x2 = inver.x(t[1], t[2]);
+                    y2 = inver.y(t[1], t[2]);
+                    m.translate(x2 - x1, y2 - y1);
+                } else {
+                    m.translate(t[1], t[2]);
+                }
+            } else if (command == "r") {
+                if (tlen == 2) {
+                    bb = bb || bbox;
+                    m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.rotate(t[1], x2, y2);
+                    } else {
+                        m.rotate(t[1], t[2], t[3]);
+                    }
+                }
+            } else if (command == "s") {
+                if (tlen == 2 || tlen == 3) {
+                    bb = bb || bbox;
+                    m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.scale(t[1], t[1], x2, y2);
+                    } else {
+                        m.scale(t[1], t[1], t[2], t[3]);
+                    }
+                } else if (tlen == 5) {
+                    if (absolute) {
+                        x2 = inver.x(t[3], t[4]);
+                        y2 = inver.y(t[3], t[4]);
+                        m.scale(t[1], t[2], x2, y2);
+                    } else {
+                        m.scale(t[1], t[2], t[3], t[4]);
+                    }
+                }
+            } else if (command == "m" && tlen == 7) {
+                m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+            }
+        }
+    }
+    return m;
+}
+Snap._.transform2matrix = transform2matrix;
+Snap._unit2px = unit2px;
+var contains = glob.doc.contains || glob.doc.compareDocumentPosition ?
+    function (a, b) {
+        var adown = a.nodeType == 9 ? a.documentElement : a,
+            bup = b && b.parentNode;
+            return a == bup || !!(bup && bup.nodeType == 1 && (
+                adown.contains ?
+                    adown.contains(bup) :
+                    a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
+            ));
+    } :
+    function (a, b) {
+        if (b) {
+            while (b) {
+                b = b.parentNode;
+                if (b == a) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
+function getSomeDefs(el) {
+    var p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) ||
+            (el.node.parentNode && wrap(el.node.parentNode)) ||
+            Snap.select("svg") ||
+            Snap(0, 0),
+        pdefs = p.select("defs"),
+        defs  = pdefs == null ? false : pdefs.node;
+    if (!defs) {
+        defs = make("defs", p.node).node;
+    }
+    return defs;
+}
+function getSomeSVG(el) {
+    return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg");
+}
+Snap._.getSomeDefs = getSomeDefs;
+Snap._.getSomeSVG = getSomeSVG;
+function unit2px(el, name, value) {
+    var svg = getSomeSVG(el).node,
+        out = {},
+        mgr = svg.querySelector(".svg---mgr");
+    if (!mgr) {
+        mgr = $("rect");
+        $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"});
+        svg.appendChild(mgr);
+    }
+    function getW(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {width: val});
+        try {
+            return mgr.getBBox().width;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function getH(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {height: val});
+        try {
+            return mgr.getBBox().height;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function set(nam, f) {
+        if (name == null) {
+            out[nam] = f(el.attr(nam) || 0);
+        } else if (nam == name) {
+            out = f(value == null ? el.attr(nam) || 0 : value);
+        }
+    }
+    switch (el.type) {
+        case "rect":
+            set("rx", getW);
+            set("ry", getH);
+        case "image":
+            set("width", getW);
+            set("height", getH);
+        case "text":
+            set("x", getW);
+            set("y", getH);
+        break;
+        case "circle":
+            set("cx", getW);
+            set("cy", getH);
+            set("r", getW);
+        break;
+        case "ellipse":
+            set("cx", getW);
+            set("cy", getH);
+            set("rx", getW);
+            set("ry", getH);
+        break;
+        case "line":
+            set("x1", getW);
+            set("x2", getW);
+            set("y1", getH);
+            set("y2", getH);
+        break;
+        case "marker":
+            set("refX", getW);
+            set("markerWidth", getW);
+            set("refY", getH);
+            set("markerHeight", getH);
+        break;
+        case "radialGradient":
+            set("fx", getW);
+            set("fy", getH);
+        break;
+        case "tspan":
+            set("dx", getW);
+            set("dy", getH);
+        break;
+        default:
+            set(name, getW);
+    }
+    svg.removeChild(mgr);
+    return out;
+}
+/*\
+ * Snap.select
+ [ method ]
+ **
+ * Wraps a DOM element specified by CSS selector as @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.select = function (query) {
+    query = Str(query).replace(/([^\\]):/g, "$1\\:");
+    return wrap(glob.doc.querySelector(query));
+};
+/*\
+ * Snap.selectAll
+ [ method ]
+ **
+ * Wraps DOM elements specified by CSS selector as set or array of @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.selectAll = function (query) {
+    var nodelist = glob.doc.querySelectorAll(query),
+        set = (Snap.set || Array)();
+    for (var i = 0; i < nodelist.length; i++) {
+        set.push(wrap(nodelist[i]));
+    }
+    return set;
+};
+
+function add2group(list) {
+    if (!is(list, "array")) {
+        list = Array.prototype.slice.call(arguments, 0);
+    }
+    var i = 0,
+        j = 0,
+        node = this.node;
+    while (this[i]) delete this[i++];
+    for (i = 0; i < list.length; i++) {
+        if (list[i].type == "set") {
+            list[i].forEach(function (el) {
+                node.appendChild(el.node);
+            });
+        } else {
+            node.appendChild(list[i].node);
+        }
+    }
+    var children = node.childNodes;
+    for (i = 0; i < children.length; i++) {
+        this[j++] = wrap(children[i]);
+    }
+    return this;
+}
+// Hub garbage collector every 10s
+setInterval(function () {
+    for (var key in hub) if (hub[has](key)) {
+        var el = hub[key],
+            node = el.node;
+        if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
+            delete hub[key];
+        }
+    }
+}, 1e4);
+function Element(el) {
+    if (el.snap in hub) {
+        return hub[el.snap];
+    }
+    var svg;
+    try {
+        svg = el.ownerSVGElement;
+    } catch(e) {}
+    /*\
+     * Element.node
+     [ property (object) ]
+     **
+     * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
+     > Usage
+     | // draw a circle at coordinate 10,10 with radius of 10
+     | var c = paper.circle(10, 10, 10);
+     | c.node.onclick = function () {
+     |     c.attr("fill", "red");
+     | };
+    \*/
+    this.node = el;
+    if (svg) {
+        this.paper = new Paper(svg);
+    }
+    /*\
+     * Element.type
+     [ property (string) ]
+     **
+     * SVG tag name of the given element.
+    \*/
+    this.type = el.tagName || el.nodeName;
+    var id = this.id = ID(this);
+    this.anims = {};
+    this._ = {
+        transform: []
+    };
+    el.snap = id;
+    hub[id] = this;
+    if (this.type == "g") {
+        this.add = add2group;
+    }
+    if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) {
+        for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
+            this[method] = Paper.prototype[method];
+        }
+    }
+}
+   /*\
+     * Element.attr
+     [ method ]
+     **
+     * Gets or sets given attributes of the element.
+     **
+     - params (object) contains key-value pairs of attributes you want to set
+     * or
+     - param (string) name of the attribute
+     = (Element) the current element
+     * or
+     = (string) value of attribute
+     > Usage
+     | el.attr({
+     |     fill: "#fc0",
+     |     stroke: "#000",
+     |     strokeWidth: 2, // CamelCase...
+     |     "fill-opacity": 0.5, // or dash-separated names
+     |     width: "*=2" // prefixed values
+     | });
+     | console.log(el.attr("fill")); // #fc0
+     * Prefixed values in format `"+=10"` supported. All four operations
+     * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+`
+     * and `-`: `"+=2em"`.
+    \*/
+    Element.prototype.attr = function (params, value) {
+        var el = this,
+            node = el.node;
+        if (!params) {
+            if (node.nodeType != 1) {
+                return {
+                    text: node.nodeValue
+                };
+            }
+            var attr = node.attributes,
+                out = {};
+            for (var i = 0, ii = attr.length; i < ii; i++) {
+                out[attr[i].nodeName] = attr[i].nodeValue;
+            }
+            return out;
+        }
+        if (is(params, "string")) {
+            if (arguments.length > 1) {
+                var json = {};
+                json[params] = value;
+                params = json;
+            } else {
+                return eve("snap.util.getattr." + params, el).firstDefined();
+            }
+        }
+        for (var att in params) {
+            if (params[has](att)) {
+                eve("snap.util.attr." + att, el, params[att]);
+            }
+        }
+        return el;
+    };
+/*\
+ * Snap.parse
+ [ method ]
+ **
+ * Parses SVG fragment and converts it into a @Fragment
+ **
+ - svg (string) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.parse = function (svg) {
+    var f = glob.doc.createDocumentFragment(),
+        full = true,
+        div = glob.doc.createElement("div");
+    svg = Str(svg);
+    if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) {
+        svg = "<svg>" + svg + "</svg>";
+        full = false;
+    }
+    div.innerHTML = svg;
+    svg = div.getElementsByTagName("svg")[0];
+    if (svg) {
+        if (full) {
+            f = svg;
+        } else {
+            while (svg.firstChild) {
+                f.appendChild(svg.firstChild);
+            }
+        }
+    }
+    return new Fragment(f);
+};
+function Fragment(frag) {
+    this.node = frag;
+}
+/*\
+ * Snap.fragment
+ [ method ]
+ **
+ * Creates a DOM fragment from a given list of elements or strings
+ **
+ - varargs (…) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.fragment = function () {
+    var args = Array.prototype.slice.call(arguments, 0),
+        f = glob.doc.createDocumentFragment();
+    for (var i = 0, ii = args.length; i < ii; i++) {
+        var item = args[i];
+        if (item.node && item.node.nodeType) {
+            f.appendChild(item.node);
+        }
+        if (item.nodeType) {
+            f.appendChild(item);
+        }
+        if (typeof item == "string") {
+            f.appendChild(Snap.parse(item).node);
+        }
+    }
+    return new Fragment(f);
+};
+
+function make(name, parent) {
+    var res = $(name);
+    parent.appendChild(res);
+    var el = wrap(res);
+    return el;
+}
+function Paper(w, h) {
+    var res,
+        desc,
+        defs,
+        proto = Paper.prototype;
+    if (w && w.tagName == "svg") {
+        if (w.snap in hub) {
+            return hub[w.snap];
+        }
+        var doc = w.ownerDocument;
+        res = new Element(w);
+        desc = w.getElementsByTagName("desc")[0];
+        defs = w.getElementsByTagName("defs")[0];
+        if (!desc) {
+            desc = $("desc");
+            desc.appendChild(doc.createTextNode("Created with Snap"));
+            res.node.appendChild(desc);
+        }
+        if (!defs) {
+            defs = $("defs");
+            res.node.appendChild(defs);
+        }
+        res.defs = defs;
+        for (var key in proto) if (proto[has](key)) {
+            res[key] = proto[key];
+        }
+        res.paper = res.root = res;
+    } else {
+        res = make("svg", glob.doc.body);
+        $(res.node, {
+            height: h,
+            version: 1.1,
+            width: w,
+            xmlns: xmlns
+        });
+    }
+    return res;
+}
+function wrap(dom) {
+    if (!dom) {
+        return dom;
+    }
+    if (dom instanceof Element || dom instanceof Fragment) {
+        return dom;
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "svg") {
+        return new Paper(dom);
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") {
+        return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]);
+    }
+    return new Element(dom);
+}
+
+Snap._.make = make;
+Snap._.wrap = wrap;
+/*\
+ * Paper.el
+ [ method ]
+ **
+ * Creates an element on paper with a given name and no attributes
+ **
+ - name (string) tag name
+ - attr (object) attributes
+ = (Element) the current element
+ > Usage
+ | var c = paper.circle(10, 10, 10); // is the same as...
+ | var c = paper.el("circle").attr({
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+ | // and the same as
+ | var c = paper.el("circle", {
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+\*/
+Paper.prototype.el = function (name, attr) {
+    var el = make(name, this.node);
+    attr && el.attr(attr);
+    return el;
+};
+/*\
+ * Element.children
+ [ method ]
+ **
+ * Returns array of all the children of the element.
+ = (array) array of Elements
+\*/
+Element.prototype.children = function () {
+    var out = [],
+        ch = this.node.childNodes;
+    for (var i = 0, ii = ch.length; i < ii; i++) {
+        out[i] = Snap(ch[i]);
+    }
+    return out;
+};
+function jsonFiller(root, o) {
+    for (var i = 0, ii = root.length; i < ii; i++) {
+        var item = {
+                type: root[i].type,
+                attr: root[i].attr()
+            },
+            children = root[i].children();
+        o.push(item);
+        if (children.length) {
+            jsonFiller(children, item.childNodes = []);
+        }
+    }
+}
+/*\
+ * Element.toJSON
+ [ method ]
+ **
+ * Returns object representation of the given element and all its children.
+ = (object) in format
+ o {
+ o     type (string) this.type,
+ o     attr (object) attributes map,
+ o     childNodes (array) optional array of children in the same format
+ o }
+\*/
+Element.prototype.toJSON = function () {
+    var out = [];
+    jsonFiller([this], out);
+    return out[0];
+};
+// default
+eve.on("snap.util.getattr", function () {
+    var att = eve.nt();
+    att = att.substring(att.lastIndexOf(".") + 1);
+    var css = att.replace(/[A-Z]/g, function (letter) {
+        return "-" + letter.toLowerCase();
+    });
+    if (cssAttr[has](css)) {
+        return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css);
+    } else {
+        return $(this.node, att);
+    }
+});
+var cssAttr = {
+    "alignment-baseline": 0,
+    "baseline-shift": 0,
+    "clip": 0,
+    "clip-path": 0,
+    "clip-rule": 0,
+    "color": 0,
+    "color-interpolation": 0,
+    "color-interpolation-filters": 0,
+    "color-profile": 0,
+    "color-rendering": 0,
+    "cursor": 0,
+    "direction": 0,
+    "display": 0,
+    "dominant-baseline": 0,
+    "enable-background": 0,
+    "fill": 0,
+    "fill-opacity": 0,
+    "fill-rule": 0,
+    "filter": 0,
+    "flood-color": 0,
+    "flood-opacity": 0,
+    "font": 0,
+    "font-family": 0,
+    "font-size": 0,
+    "font-size-adjust": 0,
+    "font-stretch": 0,
+    "font-style": 0,
+    "font-variant": 0,
+    "font-weight": 0,
+    "glyph-orientation-horizontal": 0,
+    "glyph-orientation-vertical": 0,
+    "image-rendering": 0,
+    "kerning": 0,
+    "letter-spacing": 0,
+    "lighting-color": 0,
+    "marker": 0,
+    "marker-end": 0,
+    "marker-mid": 0,
+    "marker-start": 0,
+    "mask": 0,
+    "opacity": 0,
+    "overflow": 0,
+    "pointer-events": 0,
+    "shape-rendering": 0,
+    "stop-color": 0,
+    "stop-opacity": 0,
+    "stroke": 0,
+    "stroke-dasharray": 0,
+    "stroke-dashoffset": 0,
+    "stroke-linecap": 0,
+    "stroke-linejoin": 0,
+    "stroke-miterlimit": 0,
+    "stroke-opacity": 0,
+    "stroke-width": 0,
+    "text-anchor": 0,
+    "text-decoration": 0,
+    "text-rendering": 0,
+    "unicode-bidi": 0,
+    "visibility": 0,
+    "word-spacing": 0,
+    "writing-mode": 0
+};
+
+eve.on("snap.util.attr", function (value) {
+    var att = eve.nt(),
+        attr = {};
+    att = att.substring(att.lastIndexOf(".") + 1);
+    attr[att] = value;
+    var style = att.replace(/-(\w)/gi, function (all, letter) {
+            return letter.toUpperCase();
+        }),
+        css = att.replace(/[A-Z]/g, function (letter) {
+            return "-" + letter.toLowerCase();
+        });
+    if (cssAttr[has](css)) {
+        this.node.style[style] = value == null ? E : value;
+    } else {
+        $(this.node, attr);
+    }
+});
+(function (proto) {}(Paper.prototype));
+
+// simple ajax
+/*\
+ * Snap.ajax
+ [ method ]
+ **
+ * Simple implementation of Ajax
+ **
+ - url (string) URL
+ - postData (object|string) data for post request
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ * or
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ = (XMLHttpRequest) the XMLHttpRequest object, just in case
+\*/
+Snap.ajax = function (url, postData, callback, scope){
+    var req = new XMLHttpRequest,
+        id = ID();
+    if (req) {
+        if (is(postData, "function")) {
+            scope = callback;
+            callback = postData;
+            postData = null;
+        } else if (is(postData, "object")) {
+            var pd = [];
+            for (var key in postData) if (postData.hasOwnProperty(key)) {
+                pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
+            }
+            postData = pd.join("&");
+        }
+        req.open((postData ? "POST" : "GET"), url, true);
+        if (postData) {
+            req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+            req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+        }
+        if (callback) {
+            eve.once("snap.ajax." + id + ".0", callback);
+            eve.once("snap.ajax." + id + ".200", callback);
+            eve.once("snap.ajax." + id + ".304", callback);
+        }
+        req.onreadystatechange = function() {
+            if (req.readyState != 4) return;
+            eve("snap.ajax." + id + "." + req.status, scope, req);
+        };
+        if (req.readyState == 4) {
+            return req;
+        }
+        req.send(postData);
+        return req;
+    }
+};
+/*\
+ * Snap.load
+ [ method ]
+ **
+ * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX)
+ **
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+\*/
+Snap.load = function (url, callback, scope) {
+    Snap.ajax(url, function (req) {
+        var f = Snap.parse(req.responseText);
+        scope ? callback.call(scope, f) : callback(f);
+    });
+};
+var getOffset = function (elem) {
+    var box = elem.getBoundingClientRect(),
+        doc = elem.ownerDocument,
+        body = doc.body,
+        docElem = doc.documentElement,
+        clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+        top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
+        left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+    return {
+        y: top,
+        x: left
+    };
+};
+/*\
+ * Snap.getElementByPoint
+ [ method ]
+ **
+ * Returns you topmost element under given point.
+ **
+ = (object) Snap element object
+ - x (number) x coordinate from the top left corner of the window
+ - y (number) y coordinate from the top left corner of the window
+ > Usage
+ | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
+\*/
+Snap.getElementByPoint = function (x, y) {
+    var paper = this,
+        svg = paper.canvas,
+        target = glob.doc.elementFromPoint(x, y);
+    if (glob.win.opera && target.tagName == "svg") {
+        var so = getOffset(target),
+            sr = target.createSVGRect();
+        sr.x = x - so.x;
+        sr.y = y - so.y;
+        sr.width = sr.height = 1;
+        var hits = target.getIntersectionList(sr, null);
+        if (hits.length) {
+            target = hits[hits.length - 1];
+        }
+    }
+    if (!target) {
+        return null;
+    }
+    return wrap(target);
+};
+/*\
+ * Snap.plugin
+ [ method ]
+ **
+ * Let you write plugins. You pass in a function with five arguments, like this:
+ | Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
+ |     Snap.newmethod = function () {};
+ |     Element.prototype.newmethod = function () {};
+ |     Paper.prototype.newmethod = function () {};
+ | });
+ * Inside the function you have access to all main objects (and their
+ * prototypes). This allow you to extend anything you want.
+ **
+ - f (function) your plugin body
+\*/
+Snap.plugin = function (f) {
+    f(Snap, Element, Paper, glob, Fragment);
+};
+glob.win.Snap = Snap;
+return Snap;
+}(window || this));
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        Str = String,
+        unit2px = Snap._unit2px,
+        $ = Snap._.$,
+        make = Snap._.make,
+        getSomeDefs = Snap._.getSomeDefs,
+        has = "hasOwnProperty",
+        wrap = Snap._.wrap;
+    /*\
+     * Element.getBBox
+     [ method ]
+     **
+     * Returns the bounding box descriptor for the given element
+     **
+     = (object) bounding box descriptor:
+     o {
+     o     cx: (number) x of the center,
+     o     cy: (number) x of the center,
+     o     h: (number) height,
+     o     height: (number) height,
+     o     path: (string) path command for the box,
+     o     r0: (number) radius of a circle that fully encloses the box,
+     o     r1: (number) radius of the smallest circle that can be enclosed,
+     o     r2: (number) radius of the largest circle that can be enclosed,
+     o     vb: (string) box as a viewbox command,
+     o     w: (number) width,
+     o     width: (number) width,
+     o     x2: (number) x of the right side,
+     o     x: (number) x of the left side,
+     o     y2: (number) y of the bottom edge,
+     o     y: (number) y of the top edge
+     o }
+    \*/
+    elproto.getBBox = function (isWithoutTransform) {
+        if (!Snap.Matrix || !Snap.path) {
+            return this.node.getBBox();
+        }
+        var el = this,
+            m = new Snap.Matrix;
+        if (el.removed) {
+            return Snap._.box();
+        }
+        while (el.type == "use") {
+            if (!isWithoutTransform) {
+                m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0));
+            }
+            if (el.original) {
+                el = el.original;
+            } else {
+                var href = el.attr("xlink:href");
+                el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1));
+            }
+        }
+        var _ = el._,
+            pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt;
+        try {
+            if (isWithoutTransform) {
+                _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox());
+                return Snap._.box(_.bboxwt);
+            } else {
+                el.realPath = pathfinder(el);
+                el.matrix = el.transform().localMatrix;
+                _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix)));
+                return Snap._.box(_.bbox);
+            }
+        } catch (e) {
+            // Firefox doesn’t give you bbox of hidden element
+            return Snap._.box();
+        }
+    };
+    var propString = function () {
+        return this.string;
+    };
+    function extractTransform(el, tstr) {
+        if (tstr == null) {
+            var doReturn = true;
+            if (el.type == "linearGradient" || el.type == "radialGradient") {
+                tstr = el.node.getAttribute("gradientTransform");
+            } else if (el.type == "pattern") {
+                tstr = el.node.getAttribute("patternTransform");
+            } else {
+                tstr = el.node.getAttribute("transform");
+            }
+            if (!tstr) {
+                return new Snap.Matrix;
+            }
+            tstr = Snap._.svgTransform2string(tstr);
+        } else {
+            if (!Snap._.rgTransform.test(tstr)) {
+                tstr = Snap._.svgTransform2string(tstr);
+            } else {
+                tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || "");
+            }
+            if (is(tstr, "array")) {
+                tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr);
+            }
+            el._.transform = tstr;
+        }
+        var m = Snap._.transform2matrix(tstr, el.getBBox(1));
+        if (doReturn) {
+            return m;
+        } else {
+            el.matrix = m;
+        }
+    }
+    /*\
+     * Element.transform
+     [ method ]
+     **
+     * Gets or sets transformation of the element
+     **
+     - tstr (string) transform string in Snap or SVG format
+     = (Element) the current element
+     * or
+     = (object) transformation descriptor:
+     o {
+     o     string (string) transform string,
+     o     globalMatrix (Matrix) matrix of all transformations applied to element or its parents,
+     o     localMatrix (Matrix) matrix of transformations applied only to the element,
+     o     diffMatrix (Matrix) matrix of difference between global and local transformations,
+     o     global (string) global transformation as string,
+     o     local (string) local transformation as string,
+     o     toString (function) returns `string` property
+     o }
+    \*/
+    elproto.transform = function (tstr) {
+        var _ = this._;
+        if (tstr == null) {
+            var papa = this,
+                global = new Snap.Matrix(this.node.getCTM()),
+                local = extractTransform(this),
+                ms = [local],
+                m = new Snap.Matrix,
+                i,
+                localString = local.toTransformString(),
+                string = Str(local) == Str(this.matrix) ?
+                            Str(_.transform) : localString;
+            while (papa.type != "svg" && (papa = papa.parent())) {
+                ms.push(extractTransform(papa));
+            }
+            i = ms.length;
+            while (i--) {
+                m.add(ms[i]);
+            }
+            return {
+                string: string,
+                globalMatrix: global,
+                totalMatrix: m,
+                localMatrix: local,
+                diffMatrix: global.clone().add(local.invert()),
+                global: global.toTransformString(),
+                total: m.toTransformString(),
+                local: localString,
+                toString: propString
+            };
+        }
+        if (tstr instanceof Snap.Matrix) {
+            this.matrix = tstr;
+            this._.transform = tstr.toTransformString();
+        } else {
+            extractTransform(this, tstr);
+        }
+
+        if (this.node) {
+            if (this.type == "linearGradient" || this.type == "radialGradient") {
+                $(this.node, {gradientTransform: this.matrix});
+            } else if (this.type == "pattern") {
+                $(this.node, {patternTransform: this.matrix});
+            } else {
+                $(this.node, {transform: this.matrix});
+            }
+        }
+
+        return this;
+    };
+    /*\
+     * Element.parent
+     [ method ]
+     **
+     * Returns the element's parent
+     **
+     = (Element) the parent element
+    \*/
+    elproto.parent = function () {
+        return wrap(this.node.parentNode);
+    };
+    /*\
+     * Element.append
+     [ method ]
+     **
+     * Appends the given element to current one
+     **
+     - el (Element|Set) element to append
+     = (Element) the parent element
+    \*/
+    /*\
+     * Element.add
+     [ method ]
+     **
+     * See @Element.append
+    \*/
+    elproto.append = elproto.add = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this;
+                el.forEach(function (el) {
+                    it.add(el);
+                });
+                return this;
+            }
+            el = wrap(el);
+            this.node.appendChild(el.node);
+            el.paper = this.paper;
+        }
+        return this;
+    };
+    /*\
+     * Element.appendTo
+     [ method ]
+     **
+     * Appends the current element to the given one
+     **
+     - el (Element) parent element to append to
+     = (Element) the child element
+    \*/
+    elproto.appendTo = function (el) {
+        if (el) {
+            el = wrap(el);
+            el.append(this);
+        }
+        return this;
+    };
+    /*\
+     * Element.prepend
+     [ method ]
+     **
+     * Prepends the given element to the current one
+     **
+     - el (Element) element to prepend
+     = (Element) the parent element
+    \*/
+    elproto.prepend = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this,
+                    first;
+                el.forEach(function (el) {
+                    if (first) {
+                        first.after(el);
+                    } else {
+                        it.prepend(el);
+                    }
+                    first = el;
+                });
+                return this;
+            }
+            el = wrap(el);
+            var parent = el.parent();
+            this.node.insertBefore(el.node, this.node.firstChild);
+            this.add && this.add();
+            el.paper = this.paper;
+            this.parent() && this.parent().add();
+            parent && parent.add();
+        }
+        return this;
+    };
+    /*\
+     * Element.prependTo
+     [ method ]
+     **
+     * Prepends the current element to the given one
+     **
+     - el (Element) parent element to prepend to
+     = (Element) the child element
+    \*/
+    elproto.prependTo = function (el) {
+        el = wrap(el);
+        el.prepend(this);
+        return this;
+    };
+    /*\
+     * Element.before
+     [ method ]
+     **
+     * Inserts given element before the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.before = function (el) {
+        if (el.type == "set") {
+            var it = this;
+            el.forEach(function (el) {
+                var parent = el.parent();
+                it.node.parentNode.insertBefore(el.node, it.node);
+                parent && parent.add();
+            });
+            this.parent().add();
+            return this;
+        }
+        el = wrap(el);
+        var parent = el.parent();
+        this.node.parentNode.insertBefore(el.node, this.node);
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.after
+     [ method ]
+     **
+     * Inserts given element after the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.after = function (el) {
+        el = wrap(el);
+        var parent = el.parent();
+        if (this.node.nextSibling) {
+            this.node.parentNode.insertBefore(el.node, this.node.nextSibling);
+        } else {
+            this.node.parentNode.appendChild(el.node);
+        }
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.insertBefore
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertBefore = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.insertAfter
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertAfter = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node.nextSibling);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.remove
+     [ method ]
+     **
+     * Removes element from the DOM
+     = (Element) the detached element
+    \*/
+    elproto.remove = function () {
+        var parent = this.parent();
+        this.node.parentNode && this.node.parentNode.removeChild(this.node);
+        delete this.paper;
+        this.removed = true;
+        parent && parent.add();
+        return this;
+    };
+    /*\
+     * Element.select
+     [ method ]
+     **
+     * Gathers the nested @Element matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Element) result of query selection
+    \*/
+    elproto.select = function (query) {
+        return wrap(this.node.querySelector(query));
+    };
+    /*\
+     * Element.selectAll
+     [ method ]
+     **
+     * Gathers nested @Element objects matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Set|array) result of query selection
+    \*/
+    elproto.selectAll = function (query) {
+        var nodelist = this.node.querySelectorAll(query),
+            set = (Snap.set || Array)();
+        for (var i = 0; i < nodelist.length; i++) {
+            set.push(wrap(nodelist[i]));
+        }
+        return set;
+    };
+    /*\
+     * Element.asPX
+     [ method ]
+     **
+     * Returns given attribute of the element as a `px` value (not %, em, etc.)
+     **
+     - attr (string) attribute name
+     - value (string) #optional attribute value
+     = (Element) result of query selection
+    \*/
+    elproto.asPX = function (attr, value) {
+        if (value == null) {
+            value = this.attr(attr);
+        }
+        return +unit2px(this, attr, value);
+    };
+    // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned <use> instantiates. It's a part of SVG with which ordinary web developers may be least familiar.
+    /*\
+     * Element.use
+     [ method ]
+     **
+     * Creates a `<use>` element linked to the current element
+     **
+     = (Element) the `<use>` element
+    \*/
+    elproto.use = function () {
+        var use,
+            id = this.node.id;
+        if (!id) {
+            id = this.id;
+            $(this.node, {
+                id: id
+            });
+        }
+        if (this.type == "linearGradient" || this.type == "radialGradient" ||
+            this.type == "pattern") {
+            use = make(this.type, this.node.parentNode);
+        } else {
+            use = make("use", this.node.parentNode);
+        }
+        $(use.node, {
+            "xlink:href": "#" + id
+        });
+        use.original = this;
+        return use;
+    };
+    function fixids(el) {
+        var els = el.selectAll("*"),
+            it,
+            url = /^\s*url\(("|'|)(.*)\1\)\s*$/,
+            ids = [],
+            uses = {};
+        function urltest(it, name) {
+            var val = $(it.node, name);
+            val = val && val.match(url);
+            val = val && val[2];
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    var attr = {};
+                    attr[name] = URL(id);
+                    $(it.node, attr);
+                });
+            }
+        }
+        function linktest(it) {
+            var val = $(it.node, "xlink:href");
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    it.attr("xlink:href", "#" + id);
+                });
+            }
+        }
+        for (var i = 0, ii = els.length; i < ii; i++) {
+            it = els[i];
+            urltest(it, "fill");
+            urltest(it, "stroke");
+            urltest(it, "filter");
+            urltest(it, "mask");
+            urltest(it, "clip-path");
+            linktest(it);
+            var oldid = $(it.node, "id");
+            if (oldid) {
+                $(it.node, {id: it.id});
+                ids.push({
+                    old: oldid,
+                    id: it.id
+                });
+            }
+        }
+        for (i = 0, ii = ids.length; i < ii; i++) {
+            var fs = uses[ids[i].old];
+            if (fs) {
+                for (var j = 0, jj = fs.length; j < jj; j++) {
+                    fs[j](ids[i].id);
+                }
+            }
+        }
+    }
+    /*\
+     * Element.clone
+     [ method ]
+     **
+     * Creates a clone of the element and inserts it after the element
+     **
+     = (Element) the clone
+    \*/
+    elproto.clone = function () {
+        var clone = wrap(this.node.cloneNode(true));
+        if ($(clone.node, "id")) {
+            $(clone.node, {id: clone.id});
+        }
+        fixids(clone);
+        clone.insertAfter(this);
+        return clone;
+    };
+    /*\
+     * Element.toDefs
+     [ method ]
+     **
+     * Moves element to the shared `<defs>` area
+     **
+     = (Element) the element
+    \*/
+    elproto.toDefs = function () {
+        var defs = getSomeDefs(this);
+        defs.appendChild(this.node);
+        return this;
+    };
+    /*\
+     * Element.toPattern
+     [ method ]
+     **
+     * Creates a `<pattern>` element from the current element
+     **
+     * To create a pattern you have to specify the pattern rect:
+     - x (string|number)
+     - y (string|number)
+     - width (string|number)
+     - height (string|number)
+     = (Element) the `<pattern>` element
+     * You can use pattern later on as an argument for `fill` attribute:
+     | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
+     |         fill: "none",
+     |         stroke: "#bada55",
+     |         strokeWidth: 5
+     |     }).pattern(0, 0, 10, 10),
+     |     c = paper.circle(200, 200, 100);
+     | c.attr({
+     |     fill: p
+     | });
+    \*/
+    elproto.pattern = elproto.toPattern = function (x, y, width, height) {
+        var p = make("pattern", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        $(p.node, {
+            x: x,
+            y: y,
+            width: width,
+            height: height,
+            patternUnits: "userSpaceOnUse",
+            id: p.id,
+            viewBox: [x, y, width, height].join(" ")
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+// SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path.
+// SIERRA Element.marker(): I suggest the method should accept default reference point values.  Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where?  Couldn't they also be assigned default values?
+    /*\
+     * Element.marker
+     [ method ]
+     **
+     * Creates a `<marker>` element from the current element
+     **
+     * To create a marker you have to specify the bounding rect and reference point:
+     - x (number)
+     - y (number)
+     - width (number)
+     - height (number)
+     - refX (number)
+     - refY (number)
+     = (Element) the `<marker>` element
+     * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end.
+    \*/
+    // TODO add usage for markers
+    elproto.marker = function (x, y, width, height, refX, refY) {
+        var p = make("marker", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            refX = x.refX || x.cx;
+            refY = x.refY || x.cy;
+            x = x.x;
+        }
+        $(p.node, {
+            viewBox: [x, y, width, height].join(" "),
+            markerWidth: width,
+            markerHeight: height,
+            orient: "auto",
+            refX: refX || 0,
+            refY: refY || 0,
+            id: p.id
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+    // animation
+    function slice(from, to, f) {
+        return function (arr) {
+            var res = arr.slice(from, to);
+            if (res.length == 1) {
+                res = res[0];
+            }
+            return f ? f(res) : res;
+        };
+    }
+    var Animation = function (attr, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        this.attr = attr;
+        this.dur = ms;
+        easing && (this.easing = easing);
+        callback && (this.callback = callback);
+    };
+    Snap._.Animation = Animation;
+    /*\
+     * Snap.animation
+     [ method ]
+     **
+     * Creates an animation object
+     **
+     - attr (object) attributes of final destination
+     - duration (number) duration of the animation, in milliseconds
+     - easing (function) #optional one of easing functions of @mina or custom one
+     - callback (function) #optional callback function that fires when animation ends
+     = (object) animation object
+    \*/
+    Snap.animation = function (attr, ms, easing, callback) {
+        return new Animation(attr, ms, easing, callback);
+    };
+    /*\
+     * Element.inAnim
+     [ method ]
+     **
+     * Returns a set of animations that may be able to manipulate the current element
+     **
+     = (object) in format:
+     o {
+     o     anim (object) animation object,
+     o     mina (object) @mina object,
+     o     curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+    \*/
+    elproto.inAnim = function () {
+        var el = this,
+            res = [];
+        for (var id in el.anims) if (el.anims[has](id)) {
+            (function (a) {
+                res.push({
+                    anim: new Animation(a._attrs, a.dur, a.easing, a._callback),
+                    mina: a,
+                    curStatus: a.status(),
+                    status: function (val) {
+                        return a.status(val);
+                    },
+                    stop: function () {
+                        a.stop();
+                    }
+                });
+            }(el.anims[id]));
+        }
+        return res;
+    };
+    /*\
+     * Snap.animate
+     [ method ]
+     **
+     * Runs generic animation of one number into another with a caring function
+     **
+     - from (number|array) number or array of numbers
+     - to (number|array) number or array of numbers
+     - setter (function) caring function that accepts one number argument
+     - duration (number) duration, in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function to execute when animation ends
+     = (object) animation object in @mina format
+     o {
+     o     id (string) animation id, consider it read-only,
+     o     duration (function) gets or sets the duration of the animation,
+     o     easing (function) easing,
+     o     speed (function) gets or sets the speed of the animation,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+     | var rect = Snap().rect(0, 0, 10, 10);
+     | Snap.animate(0, 10, function (val) {
+     |     rect.attr({
+     |         x: val
+     |     });
+     | }, 1000);
+     | // in given context is equivalent to
+     | rect.animate({x: 10}, 1000);
+    \*/
+    Snap.animate = function (from, to, setter, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        var now = mina.time(),
+            anim = mina(from, to, now, now + ms, mina.time, setter, easing);
+        callback && eve.once("mina.finish." + anim.id, callback);
+        return anim;
+    };
+    /*\
+     * Element.stop
+     [ method ]
+     **
+     * Stops all the animations for the current element
+     **
+     = (Element) the current element
+    \*/
+    elproto.stop = function () {
+        var anims = this.inAnim();
+        for (var i = 0, ii = anims.length; i < ii; i++) {
+            anims[i].stop();
+        }
+        return this;
+    };
+    /*\
+     * Element.animate
+     [ method ]
+     **
+     * Animates the given attributes of the element
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     = (Element) the current element
+    \*/
+    elproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = attrs.dur;
+            attrs = attrs.attr;
+        }
+        var fkeys = [], tkeys = [], keys = {}, from, to, f, eq,
+            el = this;
+        for (var key in attrs) if (attrs[has](key)) {
+            if (el.equal) {
+                eq = el.equal(key, Str(attrs[key]));
+                from = eq.from;
+                to = eq.to;
+                f = eq.f;
+            } else {
+                from = +el.attr(key);
+                to = +attrs[key];
+            }
+            var len = is(from, "array") ? from.length : 1;
+            keys[key] = slice(fkeys.length, fkeys.length + len, f);
+            fkeys = fkeys.concat(from);
+            tkeys = tkeys.concat(to);
+        }
+        var now = mina.time(),
+            anim = mina(fkeys, tkeys, now, now + ms, mina.time, function (val) {
+                var attr = {};
+                for (var key in keys) if (keys[has](key)) {
+                    attr[key] = keys[key](val);
+                }
+                el.attr(attr);
+            }, easing);
+        el.anims[anim.id] = anim;
+        anim._attrs = attrs;
+        anim._callback = callback;
+        eve("snap.animcreated." + el.id, anim);
+        eve.once("mina.finish." + anim.id, function () {
+            delete el.anims[anim.id];
+            callback && callback.call(el);
+        });
+        eve.once("mina.stop." + anim.id, function () {
+            delete el.anims[anim.id];
+        });
+        return el;
+    };
+    var eldata = {};
+    /*\
+     * Element.data
+     [ method ]
+     **
+     * Adds or retrieves given value associated with given key. (Don’t confuse
+     * with `data-` attributes)
+     *
+     * See also @Element.removeData
+     - key (string) key to store data
+     - value (any) #optional value to store
+     = (object) @Element
+     * or, if value is not specified:
+     = (any) value
+     > Usage
+     | for (var i = 0, i < 5, i++) {
+     |     paper.circle(10 + 15 * i, 10, 10)
+     |          .attr({fill: "#000"})
+     |          .data("i", i)
+     |          .click(function () {
+     |             alert(this.data("i"));
+     |          });
+     | }
+    \*/
+    elproto.data = function (key, value) {
+        var data = eldata[this.id] = eldata[this.id] || {};
+        if (arguments.length == 0){
+            eve("snap.data.get." + this.id, this, data, null);
+            return data;
+        }
+        if (arguments.length == 1) {
+            if (Snap.is(key, "object")) {
+                for (var i in key) if (key[has](i)) {
+                    this.data(i, key[i]);
+                }
+                return this;
+            }
+            eve("snap.data.get." + this.id, this, data[key], key);
+            return data[key];
+        }
+        data[key] = value;
+        eve("snap.data.set." + this.id, this, value, key);
+        return this;
+    };
+    /*\
+     * Element.removeData
+     [ method ]
+     **
+     * Removes value associated with an element by given key.
+     * If key is not provided, removes all the data of the element.
+     - key (string) #optional key
+     = (object) @Element
+    \*/
+    elproto.removeData = function (key) {
+        if (key == null) {
+            eldata[this.id] = {};
+        } else {
+            eldata[this.id] && delete eldata[this.id][key];
+        }
+        return this;
+    };
+    /*\
+     * Element.outerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element, equivalent to HTML's `outerHTML`.
+     *
+     * See also @Element.innerSVG
+     = (string) SVG code for the element
+    \*/
+    /*\
+     * Element.toString
+     [ method ]
+     **
+     * See @Element.outerSVG
+    \*/
+    elproto.outerSVG = elproto.toString = toString(1);
+    /*\
+     * Element.innerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML`
+     = (string) SVG code for the element
+    \*/
+    elproto.innerSVG = toString();
+    function toString(type) {
+        return function () {
+            var res = type ? "<" + this.type : "",
+                attr = this.node.attributes,
+                chld = this.node.childNodes;
+            if (type) {
+                for (var i = 0, ii = attr.length; i < ii; i++) {
+                    res += " " + attr[i].name + '="' +
+                            attr[i].value.replace(/"/g, '\\"') + '"';
+                }
+            }
+            if (chld.length) {
+                type && (res += ">");
+                for (i = 0, ii = chld.length; i < ii; i++) {
+                    if (chld[i].nodeType == 3) {
+                        res += chld[i].nodeValue;
+                    } else if (chld[i].nodeType == 1) {
+                        res += wrap(chld[i]).toString();
+                    }
+                }
+                type && (res += "</" + this.type + ">");
+            } else {
+                type && (res += "/>");
+            }
+            return res;
+        };
+    }
+    elproto.toDataURL = function () {
+        if (window && window.btoa) {
+            var bb = this.getBBox(),
+                svg = Snap.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>', {
+                x: +bb.x.toFixed(3),
+                y: +bb.y.toFixed(3),
+                width: +bb.width.toFixed(3),
+                height: +bb.height.toFixed(3),
+                contents: this.outerSVG()
+            });
+            return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
+        }
+    };
+    /*\
+     * Fragment.select
+     [ method ]
+     **
+     * See @Element.select
+    \*/
+    Fragment.prototype.select = elproto.select;
+    /*\
+     * Fragment.selectAll
+     [ method ]
+     **
+     * See @Element.selectAll
+    \*/
+    Fragment.prototype.selectAll = elproto.selectAll;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var objectToString = Object.prototype.toString,
+        Str = String,
+        math = Math,
+        E = "";
+    function Matrix(a, b, c, d, e, f) {
+        if (b == null && objectToString.call(a) == "[object SVGMatrix]") {
+            this.a = a.a;
+            this.b = a.b;
+            this.c = a.c;
+            this.d = a.d;
+            this.e = a.e;
+            this.f = a.f;
+            return;
+        }
+        if (a != null) {
+            this.a = +a;
+            this.b = +b;
+            this.c = +c;
+            this.d = +d;
+            this.e = +e;
+            this.f = +f;
+        } else {
+            this.a = 1;
+            this.b = 0;
+            this.c = 0;
+            this.d = 1;
+            this.e = 0;
+            this.f = 0;
+        }
+    }
+    (function (matrixproto) {
+        /*\
+         * Matrix.add
+         [ method ]
+         **
+         * Adds the given matrix to existing one
+         - a (number)
+         - b (number)
+         - c (number)
+         - d (number)
+         - e (number)
+         - f (number)
+         * or
+         - matrix (object) @Matrix
+        \*/
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
+
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
+
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
+                }
+            }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+            return this;
+        };
+        /*\
+         * Matrix.invert
+         [ method ]
+         **
+         * Returns an inverted version of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        /*\
+         * Matrix.clone
+         [ method ]
+         **
+         * Returns a copy of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        /*\
+         * Matrix.translate
+         [ method ]
+         **
+         * Translate the matrix
+         - x (number) horizontal offset distance
+         - y (number) vertical offset distance
+        \*/
+        matrixproto.translate = function (x, y) {
+            return this.add(1, 0, 0, 1, x, y);
+        };
+        /*\
+         * Matrix.scale
+         [ method ]
+         **
+         * Scales the matrix
+         - x (number) amount to be scaled, with `1` resulting in no change
+         - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.)
+         - cx (number) #optional horizontal origin point from which to scale
+         - cy (number) #optional vertical origin point from which to scale
+         * Default cx, cy is the middle point of the element.
+        \*/
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+            return this;
+        };
+        /*\
+         * Matrix.rotate
+         [ method ]
+         **
+         * Rotates the matrix
+         - a (number) angle of rotation, in degrees
+         - x (number) horizontal origin point from which to rotate
+         - y (number) vertical origin point from which to rotate
+        \*/
+        matrixproto.rotate = function (a, x, y) {
+            a = Snap.rad(a);
+            x = x || 0;
+            y = y || 0;
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            return this.add(1, 0, 0, 1, -x, -y);
+        };
+        /*\
+         * Matrix.x
+         [ method ]
+         **
+         * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y
+         - x (number)
+         - y (number)
+         = (number) x
+        \*/
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        /*\
+         * Matrix.y
+         [ method ]
+         **
+         * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x
+         - x (number)
+         - y (number)
+         = (number) y
+        \*/
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
+        }
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] && (a[0] /= mag);
+            a[1] && (a[1] /= mag);
+        }
+        /*\
+         * Matrix.determinant
+         [ method ]
+         **
+         * Finds determinant of the given matrix.
+         = (number) determinant
+        \*/
+        matrixproto.determinant = function () {
+            return this.a * this.d - this.b * this.c;
+        };
+        /*\
+         * Matrix.split
+         [ method ]
+         **
+         * Splits matrix into primitive transformations
+         = (object) in format:
+         o dx (number) translation by x
+         o dy (number) translation by y
+         o scalex (number) scale by x
+         o scaley (number) scale by y
+         o shear (number) shear
+         o rotate (number) rotation in deg
+         o isSimple (boolean) could it be represented via simple transformations
+        \*/
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
+
+            if (this.determinant() < 0) {
+                out.scalex = -out.scalex;
+            }
+
+            // rotation
+            var sin = -row[0][1],
+                cos = row[1][1];
+            if (cos < 0) {
+                out.rotate = Snap.deg(math.acos(cos));
+                if (sin < 0) {
+                    out.rotate = 360 - out.rotate;
+                }
+            } else {
+                out.rotate = Snap.deg(math.asin(sin));
+            }
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+            return out;
+        };
+        /*\
+         * Matrix.toTransformString
+         [ method ]
+         **
+         * Returns transform string that represents given matrix
+         = (string) transform string
+        \*/
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this.split();
+            if (!+s.shear.toFixed(9)) {
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) +
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E);
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+    /*\
+     * Snap.Matrix
+     [ method ]
+     **
+     * Matrix constructor, extend on your own risk.
+     * To create matrices use @Snap.matrix.
+    \*/
+    Snap.Matrix = Matrix;
+    /*\
+     * Snap.matrix
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns a matrix based on the given parameters
+     - a (number)
+     - b (number)
+     - c (number)
+     - d (number)
+     - e (number)
+     - f (number)
+     * or
+     - svgMatrix (SVGMatrix)
+     = (object) @Matrix
+    \*/
+    Snap.matrix = function (a, b, c, d, e, f) {
+        return new Matrix(a, b, c, d, e, f);
+    };
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var has = "hasOwnProperty",
+        make = Snap._.make,
+        wrap = Snap._.wrap,
+        is = Snap.is,
+        getSomeDefs = Snap._.getSomeDefs,
+        reURLValue = /^url\(#?([^)]+)\)$/,
+        $ = Snap._.$,
+        URL = Snap.url,
+        Str = String,
+        separator = Snap._.separator,
+        E = "";
+    // Attributes event handlers
+    eve.on("snap.util.attr.mask", function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value.type == "mask") {
+                var mask = value;
+            } else {
+                mask = make("mask", getSomeDefs(this));
+                mask.node.appendChild(value.node);
+            }
+            !mask.node.id && $(mask.node, {
+                id: mask.id
+            });
+            $(this.node, {
+                mask: URL(mask.id)
+            });
+        }
+    });
+    (function (clipIt) {
+        eve.on("snap.util.attr.clip", clipIt);
+        eve.on("snap.util.attr.clip-path", clipIt);
+        eve.on("snap.util.attr.clipPath", clipIt);
+    }(function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value.type == "clipPath") {
+                var clip = value;
+            } else {
+                clip = make("clipPath", getSomeDefs(this));
+                clip.node.appendChild(value.node);
+                !clip.node.id && $(clip.node, {
+                    id: clip.id
+                });
+            }
+            $(this.node, {
+                "clip-path": URL(clip.node.id || clip.id)
+            });
+        }
+    }));
+    function fillStroke(name) {
+        return function (value) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1 &&
+                (value.node.firstChild.tagName == "radialGradient" ||
+                value.node.firstChild.tagName == "linearGradient" ||
+                value.node.firstChild.tagName == "pattern")) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value instanceof Element) {
+                if (value.type == "radialGradient" || value.type == "linearGradient"
+                   || value.type == "pattern") {
+                    if (!value.node.id) {
+                        $(value.node, {
+                            id: value.id
+                        });
+                    }
+                    var fill = URL(value.node.id);
+                } else {
+                    fill = value.attr(name);
+                }
+            } else {
+                fill = Snap.color(value);
+                if (fill.error) {
+                    var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value);
+                    if (grad) {
+                        if (!grad.node.id) {
+                            $(grad.node, {
+                                id: grad.id
+                            });
+                        }
+                        fill = URL(grad.node.id);
+                    } else {
+                        fill = value;
+                    }
+                } else {
+                    fill = Str(fill);
+                }
+            }
+            var attrs = {};
+            attrs[name] = fill;
+            $(this.node, attrs);
+            this.node.style[name] = E;
+        };
+    }
+    eve.on("snap.util.attr.fill", fillStroke("fill"));
+    eve.on("snap.util.attr.stroke", fillStroke("stroke"));
+    var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i;
+    eve.on("snap.util.grad.parse", function parseGrad(string) {
+        string = Str(string);
+        var tokens = string.match(gradrg);
+        if (!tokens) {
+            return null;
+        }
+        var type = tokens[1],
+            params = tokens[2],
+            stops = tokens[3];
+        params = params.split(/\s*,\s*/).map(function (el) {
+            return +el == el ? +el : el;
+        });
+        if (params.length == 1 && params[0] == 0) {
+            params = [];
+        }
+        stops = stops.split("-");
+        stops = stops.map(function (el) {
+            el = el.split(":");
+            var out = {
+                color: el[0]
+            };
+            if (el[1]) {
+                out.offset = parseFloat(el[1]);
+            }
+            return out;
+        });
+        return {
+            type: type,
+            params: params,
+            stops: stops
+        };
+    });
+
+    eve.on("snap.util.attr.d", function (value) {
+        eve.stop();
+        if (is(value, "array") && is(value[0], "array")) {
+            value = Snap.path.toString.call(value);
+        }
+        value = Str(value);
+        if (value.match(/[ruo]/i)) {
+            value = Snap.path.toAbsolute(value);
+        }
+        $(this.node, {d: value});
+    })(-1);
+    eve.on("snap.util.attr.#text", function (value) {
+        eve.stop();
+        value = Str(value);
+        var txt = glob.doc.createTextNode(value);
+        while (this.node.firstChild) {
+            this.node.removeChild(this.node.firstChild);
+        }
+        this.node.appendChild(txt);
+    })(-1);
+    eve.on("snap.util.attr.path", function (value) {
+        eve.stop();
+        this.attr({d: value});
+    })(-1);
+    eve.on("snap.util.attr.class", function (value) {
+        eve.stop();
+        this.node.className.baseVal = value;
+    })(-1);
+    eve.on("snap.util.attr.viewBox", function (value) {
+        var vb;
+        if (is(value, "object") && "x" in value) {
+            vb = [value.x, value.y, value.width, value.height].join(" ");
+        } else if (is(value, "array")) {
+            vb = value.join(" ");
+        } else {
+            vb = value;
+        }
+        $(this.node, {
+            viewBox: vb
+        });
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.transform", function (value) {
+        this.transform(value);
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.r", function (value) {
+        if (this.type == "rect") {
+            eve.stop();
+            $(this.node, {
+                rx: value,
+                ry: value
+            });
+        }
+    })(-1);
+    eve.on("snap.util.attr.textpath", function (value) {
+        eve.stop();
+        if (this.type == "text") {
+            var id, tp, node;
+            if (!value && this.textPath) {
+                tp = this.textPath;
+                while (tp.node.firstChild) {
+                    this.node.appendChild(tp.node.firstChild);
+                }
+                tp.remove();
+                delete this.textPath;
+                return;
+            }
+            if (is(value, "string")) {
+                var defs = getSomeDefs(this),
+                    path = wrap(defs.parentNode).path(value);
+                defs.appendChild(path.node);
+                id = path.id;
+                path.attr({id: id});
+            } else {
+                value = wrap(value);
+                if (value instanceof Element) {
+                    id = value.attr("id");
+                    if (!id) {
+                        id = value.id;
+                        value.attr({id: id});
+                    }
+                }
+            }
+            if (id) {
+                tp = this.textPath;
+                node = this.node;
+                if (tp) {
+                    tp.attr({"xlink:href": "#" + id});
+                } else {
+                    tp = $("textPath", {
+                        "xlink:href": "#" + id
+                    });
+                    while (node.firstChild) {
+                        tp.appendChild(node.firstChild);
+                    }
+                    node.appendChild(tp);
+                    this.textPath = wrap(tp);
+                }
+            }
+        }
+    })(-1);
+    eve.on("snap.util.attr.text", function (value) {
+        if (this.type == "text") {
+            var i = 0,
+                node = this.node,
+                tuner = function (chunk) {
+                    var out = $("tspan");
+                    if (is(chunk, "array")) {
+                        for (var i = 0; i < chunk.length; i++) {
+                            out.appendChild(tuner(chunk[i]));
+                        }
+                    } else {
+                        out.appendChild(glob.doc.createTextNode(chunk));
+                    }
+                    out.normalize && out.normalize();
+                    return out;
+                };
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            var tuned = tuner(value);
+            while (tuned.firstChild) {
+                node.appendChild(tuned.firstChild);
+            }
+        }
+        eve.stop();
+    })(-1);
+    function setFontSize(value) {
+        eve.stop();
+        if (value == +value) {
+            value += "px";
+        }
+        this.node.style.fontSize = value;
+    }
+    eve.on("snap.util.attr.fontSize", setFontSize)(-1);
+    eve.on("snap.util.attr.font-size", setFontSize)(-1);
+
+
+    eve.on("snap.util.getattr.transform", function () {
+        eve.stop();
+        return this.transform();
+    })(-1);
+    eve.on("snap.util.getattr.textpath", function () {
+        eve.stop();
+        return this.textPath;
+    })(-1);
+    // Markers
+    (function () {
+        function getter(end) {
+            return function () {
+                eve.stop();
+                var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end);
+                if (style == "none") {
+                    return style;
+                } else {
+                    return Snap(glob.doc.getElementById(style.match(reURLValue)[1]));
+                }
+            };
+        }
+        function setter(end) {
+            return function (value) {
+                eve.stop();
+                var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1);
+                if (value == "" || !value) {
+                    this.node.style[name] = "none";
+                    return;
+                }
+                if (value.type == "marker") {
+                    var id = value.node.id;
+                    if (!id) {
+                        $(value.node, {id: value.id});
+                    }
+                    this.node.style[name] = URL(id);
+                    return;
+                }
+            };
+        }
+        eve.on("snap.util.getattr.marker-end", getter("end"))(-1);
+        eve.on("snap.util.getattr.markerEnd", getter("end"))(-1);
+        eve.on("snap.util.getattr.marker-start", getter("start"))(-1);
+        eve.on("snap.util.getattr.markerStart", getter("start"))(-1);
+        eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1);
+        eve.on("snap.util.getattr.markerMid", getter("mid"))(-1);
+        eve.on("snap.util.attr.marker-end", setter("end"))(-1);
+        eve.on("snap.util.attr.markerEnd", setter("end"))(-1);
+        eve.on("snap.util.attr.marker-start", setter("start"))(-1);
+        eve.on("snap.util.attr.markerStart", setter("start"))(-1);
+        eve.on("snap.util.attr.marker-mid", setter("mid"))(-1);
+        eve.on("snap.util.attr.markerMid", setter("mid"))(-1);
+    }());
+    eve.on("snap.util.getattr.r", function () {
+        if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
+            eve.stop();
+            return $(this.node, "rx");
+        }
+    })(-1);
+    function textExtract(node) {
+        var out = [];
+        var children = node.childNodes;
+        for (var i = 0, ii = children.length; i < ii; i++) {
+            var chi = children[i];
+            if (chi.nodeType == 3) {
+                out.push(chi.nodeValue);
+            }
+            if (chi.tagName == "tspan") {
+                if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) {
+                    out.push(chi.firstChild.nodeValue);
+                } else {
+                    out.push(textExtract(chi));
+                }
+            }
+        }
+        return out;
+    }
+    eve.on("snap.util.getattr.text", function () {
+        if (this.type == "text" || this.type == "tspan") {
+            eve.stop();
+            var out = textExtract(this.node);
+            return out.length == 1 ? out[0] : out;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.#text", function () {
+        return this.node.textContent;
+    })(-1);
+    eve.on("snap.util.getattr.viewBox", function () {
+        eve.stop();
+        var vb = $(this.node, "viewBox");
+        if (vb) {
+            vb = vb.split(separator);
+            return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.points", function () {
+        var p = $(this.node, "points");
+        eve.stop();
+        if (p) {
+            return p.split(separator);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.path", function () {
+        var p = $(this.node, "d");
+        eve.stop();
+        return p;
+    })(-1);
+    eve.on("snap.util.getattr.class", function () {
+        return this.node.className.baseVal;
+    })(-1);
+    function getFontSize() {
+        eve.stop();
+        return this.node.style.fontSize;
+    }
+    eve.on("snap.util.getattr.fontSize", getFontSize)(-1);
+    eve.on("snap.util.getattr.font-size", getFontSize)(-1);
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var rgNotSpace = /\S+/g,
+        rgBadSpace = /[\t\r\n\f]/g,
+        rgTrim = /(^\s+|\s+$)/g,
+        Str = String,
+        elproto = Element.prototype;
+    /*\
+     * Element.addClass
+     [ method ]
+     **
+     * Adds given class name or list of class names to the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.addClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+
+        if (classes.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (!~pos) {
+                    curClasses.push(clazz);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.removeClass
+     [ method ]
+     **
+     * Removes given class name or list of class names from the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.removeClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        if (curClasses.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (~pos) {
+                    curClasses.splice(pos, 1);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.hasClass
+     [ method ]
+     **
+     * Checks if the element has a given class name in the list of class names applied to it.
+     - value (string) class name
+     **
+     = (boolean) `true` if the element has given class
+    \*/
+    elproto.hasClass = function (value) {
+        var elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [];
+        return !!~curClasses.indexOf(value);
+    };
+    /*\
+     * Element.toggleClass
+     [ method ]
+     **
+     * Add or remove one or more classes from the element, depending on either
+     * the class’s presence or the value of the `flag` argument.
+     - value (string) class name or space separated list of class names
+     - flag (boolean) value to determine whether the class should be added or removed
+     **
+     = (Element) original element.
+    \*/
+    elproto.toggleClass = function (value, flag) {
+        if (flag != null) {
+            if (flag) {
+                return this.addClass(value);
+            } else {
+                return this.removeClass(value);
+            }
+        }
+        var classes = (value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        j = 0;
+        while ((clazz = classes[j++])) {
+            pos = curClasses.indexOf(clazz);
+            if (~pos) {
+                curClasses.splice(pos, 1);
+            } else {
+                curClasses.push(clazz);
+            }
+        }
+
+        finalValue = curClasses.join(" ");
+        if (className != finalValue) {
+            elem.className.baseVal = finalValue;
+        }
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var operators = {
+            "+": function (x, y) {
+                    return x + y;
+                },
+            "-": function (x, y) {
+                    return x - y;
+                },
+            "/": function (x, y) {
+                    return x / y;
+                },
+            "*": function (x, y) {
+                    return x * y;
+                }
+        },
+        Str = String,
+        reUnit = /[a-z]+$/i,
+        reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    eve.on("snap.util.attr", function (val) {
+        var plus = Str(val).match(reAddon);
+        if (plus) {
+            var evnt = eve.nt(),
+                name = evnt.substring(evnt.lastIndexOf(".") + 1),
+                a = this.attr(name),
+                atr = {};
+            eve.stop();
+            var unit = plus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[plus[1]];
+            if (aUnit && aUnit == unit) {
+                val = op(parseFloat(a), +plus[2]);
+            } else {
+                a = this.asPX(name);
+                val = op(this.asPX(name), this.asPX(name, plus[2] + unit));
+            }
+            if (isNaN(a) || isNaN(val)) {
+                return;
+            }
+            atr[name] = val;
+            this.attr(atr);
+        }
+    })(-10);
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this,
+            bplus = Str(b).match(reAddon);
+        if (bplus) {
+            eve.stop();
+            var unit = bplus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[bplus[1]];
+            if (aUnit && aUnit == unit) {
+                return {
+                    from: parseFloat(a),
+                    to: op(parseFloat(a), +bplus[2]),
+                    f: getUnit(aUnit)
+                };
+            } else {
+                a = this.asPX(name);
+                return {
+                    from: a,
+                    to: op(a, this.asPX(name, bplus[2] + unit)),
+                    f: getNumber
+                };
+            }
+        }
+    })(-10);
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var proto = Paper.prototype,
+        is = Snap.is;
+    /*\
+     * Paper.rect
+     [ method ]
+     *
+     * Draws a rectangle
+     **
+     - x (number) x coordinate of the top left corner
+     - y (number) y coordinate of the top left corner
+     - width (number) width
+     - height (number) height
+     - rx (number) #optional horizontal radius for rounded corners, default is 0
+     - ry (number) #optional vertical radius for rounded corners, default is rx or 0
+     = (object) the `rect` element
+     **
+     > Usage
+     | // regular rectangle
+     | var c = paper.rect(10, 10, 50, 50);
+     | // rectangle with rounded corners
+     | var c = paper.rect(40, 40, 50, 50, 10);
+    \*/
+    proto.rect = function (x, y, w, h, rx, ry) {
+        var attr;
+        if (ry == null) {
+            ry = rx;
+        }
+        if (is(x, "object") && x == "[object Object]") {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                width: w,
+                height: h
+            };
+            if (rx != null) {
+                attr.rx = rx;
+                attr.ry = ry;
+            }
+        }
+        return this.el("rect", attr);
+    };
+    /*\
+     * Paper.circle
+     [ method ]
+     **
+     * Draws a circle
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - r (number) radius
+     = (object) the `circle` element
+     **
+     > Usage
+     | var c = paper.circle(50, 50, 40);
+    \*/
+    proto.circle = function (cx, cy, r) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr = {
+                cx: cx,
+                cy: cy,
+                r: r
+            };
+        }
+        return this.el("circle", attr);
+    };
+
+    var preload = (function () {
+        function onerror() {
+            this.parentNode.removeChild(this);
+        }
+        return function (src, f) {
+            var img = glob.doc.createElement("img"),
+                body = glob.doc.body;
+            img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+            img.onload = function () {
+                f.call(img);
+                img.onload = img.onerror = null;
+                body.removeChild(img);
+            };
+            img.onerror = onerror;
+            body.appendChild(img);
+            img.src = src;
+        };
+    }());
+
+    /*\
+     * Paper.image
+     [ method ]
+     **
+     * Places an image on the surface
+     **
+     - src (string) URI of the source image
+     - x (number) x offset position
+     - y (number) y offset position
+     - width (number) width of the image
+     - height (number) height of the image
+     = (object) the `image` element
+     * or
+     = (object) Snap element object with type `image`
+     **
+     > Usage
+     | var c = paper.image("apple.png", 10, 10, 80, 80);
+    \*/
+    proto.image = function (src, x, y, width, height) {
+        var el = this.el("image");
+        if (is(src, "object") && "src" in src) {
+            el.attr(src);
+        } else if (src != null) {
+            var set = {
+                "xlink:href": src,
+                preserveAspectRatio: "none"
+            };
+            if (x != null && y != null) {
+                set.x = x;
+                set.y = y;
+            }
+            if (width != null && height != null) {
+                set.width = width;
+                set.height = height;
+            } else {
+                preload(src, function () {
+                    Snap._.$(el.node, {
+                        width: this.offsetWidth,
+                        height: this.offsetHeight
+                    });
+                });
+            }
+            Snap._.$(el.node, set);
+        }
+        return el;
+    };
+    /*\
+     * Paper.ellipse
+     [ method ]
+     **
+     * Draws an ellipse
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - rx (number) horizontal radius
+     - ry (number) vertical radius
+     = (object) the `ellipse` element
+     **
+     > Usage
+     | var c = paper.ellipse(50, 50, 40, 20);
+    \*/
+    proto.ellipse = function (cx, cy, rx, ry) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr ={
+                cx: cx,
+                cy: cy,
+                rx: rx,
+                ry: ry
+            };
+        }
+        return this.el("ellipse", attr);
+    };
+    // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier.
+    /*\
+     * Paper.path
+     [ method ]
+     **
+     * Creates a `<path>` element using the given string as the path's definition
+     - pathString (string) #optional path string in SVG format
+     * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:
+     | "M10,20L30,40"
+     * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.
+     *
+     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
+     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
+     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
+     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
+     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
+     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
+     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
+     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
+     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
+     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
+     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
+     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
+     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
+     * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier.
+     * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point.
+     > Usage
+     | var c = paper.path("M10 10L90 90");
+     | // draw a diagonal line:
+     | // move to 10,10, line to 90,90
+    \*/
+    proto.path = function (d) {
+        var attr;
+        if (is(d, "object") && !is(d, "array")) {
+            attr = d;
+        } else if (d) {
+            attr = {d: d};
+        }
+        return this.el("path", attr);
+    };
+    /*\
+     * Paper.g
+     [ method ]
+     **
+     * Creates a group element
+     **
+     - varargs (…) #optional elements to nest within the group
+     = (object) the `g` element
+     **
+     > Usage
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g(c2, c1); // note that the order of elements is different
+     * or
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g();
+     | g.add(c2, c1);
+    \*/
+    /*\
+     * Paper.group
+     [ method ]
+     **
+     * See @Paper.g
+    \*/
+    proto.group = proto.g = function (first) {
+        var attr,
+            el = this.el("g");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.svg
+     [ method ]
+     **
+     * Creates a nested SVG element.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `svg` element
+     **
+    \*/
+    proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) {
+        var attrs = {};
+        if (is(x, "object") && y == null) {
+            attrs = x;
+        } else {
+            if (x != null) {
+                attrs.x = x;
+            }
+            if (y != null) {
+                attrs.y = y;
+            }
+            if (width != null) {
+                attrs.width = width;
+            }
+            if (height != null) {
+                attrs.height = height;
+            }
+            if (vbx != null && vby != null && vbw != null && vbh != null) {
+                attrs.viewBox = [vbx, vby, vbw, vbh];
+            }
+        }
+        return this.el("svg", attrs);
+    };
+    /*\
+     * Paper.mask
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a mask.
+     **
+     = (object) the `mask` element
+     **
+    \*/
+    proto.mask = function (first) {
+        var attr,
+            el = this.el("mask");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.ptrn
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a pattern.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `pattern` element
+     **
+    \*/
+    proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) {
+        if (is(x, "object")) {
+            var attr = x;
+        } else {
+            attr = {patternUnits: "userSpaceOnUse"};
+            if (x) {
+                attr.x = x;
+            }
+            if (y) {
+                attr.y = y;
+            }
+            if (width != null) {
+                attr.width = width;
+            }
+            if (height != null) {
+                attr.height = height;
+            }
+            if (vx != null && vy != null && vw != null && vh != null) {
+                attr.viewBox = [vx, vy, vw, vh];
+            } else {
+                attr.viewBox = [x || 0, y || 0, width || 0, height || 0];
+            }
+        }
+        return this.el("pattern", attr);
+    };
+    /*\
+     * Paper.use
+     [ method ]
+     **
+     * Creates a <use> element.
+     - id (string) @optional id of element to link
+     * or
+     - id (Element) @optional element to link
+     **
+     = (object) the `use` element
+     **
+    \*/
+    proto.use = function (id) {
+        if (id != null) {
+            if (id instanceof Element) {
+                if (!id.attr("id")) {
+                    id.attr({id: Snap._.id(id)});
+                }
+                id = id.attr("id");
+            }
+            if (String(id).charAt() == "#") {
+                id = id.substring(1);
+            }
+            return this.el("use", {"xlink:href": "#" + id});
+        } else {
+            return Element.prototype.use.call(this);
+        }
+    };
+    /*\
+     * Paper.symbol
+     [ method ]
+     **
+     * Creates a <symbol> element.
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     = (object) the `symbol` element
+     **
+    \*/
+    proto.symbol = function (vx, vy, vw, vh) {
+        var attr = {};
+        if (vx != null && vy != null && vw != null && vh != null) {
+            attr.viewBox = [vx, vy, vw, vh];
+        }
+
+        return this.el("symbol", attr);
+    };
+    /*\
+     * Paper.text
+     [ method ]
+     **
+     * Draws a text string
+     **
+     - x (number) x coordinate position
+     - y (number) y coordinate position
+     - text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
+     = (object) the `text` element
+     **
+     > Usage
+     | var t1 = paper.text(50, 50, "Snap");
+     | var t2 = paper.text(50, 50, ["S","n","a","p"]);
+     | // Text path usage
+     | t1.attr({textpath: "M10,10L100,100"});
+     | // or
+     | var pth = paper.path("M10,10L100,100");
+     | t1.attr({textpath: pth});
+    \*/
+    proto.text = function (x, y, text) {
+        var attr = {};
+        if (is(x, "object")) {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                text: text || ""
+            };
+        }
+        return this.el("text", attr);
+    };
+    /*\
+     * Paper.line
+     [ method ]
+     **
+     * Draws a line
+     **
+     - x1 (number) x coordinate position of the start
+     - y1 (number) y coordinate position of the start
+     - x2 (number) x coordinate position of the end
+     - y2 (number) y coordinate position of the end
+     = (object) the `line` element
+     **
+     > Usage
+     | var t1 = paper.line(50, 50, 100, 100);
+    \*/
+    proto.line = function (x1, y1, x2, y2) {
+        var attr = {};
+        if (is(x1, "object")) {
+            attr = x1;
+        } else if (x1 != null) {
+            attr = {
+                x1: x1,
+                x2: x2,
+                y1: y1,
+                y2: y2
+            };
+        }
+        return this.el("line", attr);
+    };
+    /*\
+     * Paper.polyline
+     [ method ]
+     **
+     * Draws a polyline
+     **
+     - points (array) array of points
+     * or
+     - varargs (…) points
+     = (object) the `polyline` element
+     **
+     > Usage
+     | var p1 = paper.polyline([10, 10, 100, 100]);
+     | var p2 = paper.polyline(10, 10, 100, 100);
+    \*/
+    proto.polyline = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polyline", attr);
+    };
+    /*\
+     * Paper.polygon
+     [ method ]
+     **
+     * Draws a polygon. See @Paper.polyline
+    \*/
+    proto.polygon = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polygon", attr);
+    };
+    // gradients
+    (function () {
+        var $ = Snap._.$;
+        // gradients' helpers
+        function Gstops() {
+            return this.selectAll("stop");
+        }
+        function GaddStop(color, offset) {
+            var stop = $("stop"),
+                attr = {
+                    offset: +offset + "%"
+                };
+            color = Snap.color(color);
+            attr["stop-color"] = color.hex;
+            if (color.opacity < 1) {
+                attr["stop-opacity"] = color.opacity;
+            }
+            $(stop, attr);
+            this.node.appendChild(stop);
+            return this;
+        }
+        function GgetBBox() {
+            if (this.type == "linearGradient") {
+                var x1 = $(this.node, "x1") || 0,
+                    x2 = $(this.node, "x2") || 1,
+                    y1 = $(this.node, "y1") || 0,
+                    y2 = $(this.node, "y2") || 0;
+                return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1));
+            } else {
+                var cx = this.node.cx || .5,
+                    cy = this.node.cy || .5,
+                    r = this.node.r || 0;
+                return Snap._.box(cx - r, cy - r, r * 2, r * 2);
+            }
+        }
+        function gradient(defs, str) {
+            var grad = eve("snap.util.grad.parse", null, str).firstDefined(),
+                el;
+            if (!grad) {
+                return null;
+            }
+            grad.params.unshift(defs);
+            if (grad.type.toLowerCase() == "l") {
+                el = gradientLinear.apply(0, grad.params);
+            } else {
+                el = gradientRadial.apply(0, grad.params);
+            }
+            if (grad.type != grad.type.toLowerCase()) {
+                $(el.node, {
+                    gradientUnits: "userSpaceOnUse"
+                });
+            }
+            var stops = grad.stops,
+                len = stops.length,
+                start = 0,
+                j = 0;
+            function seed(i, end) {
+                var step = (end - start) / (i - j);
+                for (var k = j; k < i; k++) {
+                    stops[k].offset = +(+start + step * (k - j)).toFixed(2);
+                }
+                j = i;
+                start = end;
+            }
+            len--;
+            for (var i = 0; i < len; i++) if ("offset" in stops[i]) {
+                seed(i, stops[i].offset);
+            }
+            stops[len].offset = stops[len].offset || 100;
+            seed(len, stops[len].offset);
+            for (i = 0; i <= len; i++) {
+                var stop = stops[i];
+                el.addStop(stop.color, stop.offset);
+            }
+            return el;
+        }
+        function gradientLinear(defs, x1, y1, x2, y2) {
+            var el = Snap._.make("linearGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (x1 != null) {
+                $(el.node, {
+                    x1: x1,
+                    y1: y1,
+                    x2: x2,
+                    y2: y2
+                });
+            }
+            return el;
+        }
+        function gradientRadial(defs, cx, cy, r, fx, fy) {
+            var el = Snap._.make("radialGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (cx != null) {
+                $(el.node, {
+                    cx: cx,
+                    cy: cy,
+                    r: r
+                });
+            }
+            if (fx != null && fy != null) {
+                $(el.node, {
+                    fx: fx,
+                    fy: fy
+                });
+            }
+            return el;
+        }
+        /*\
+         * Paper.gradient
+         [ method ]
+         **
+         * Creates a gradient element
+         **
+         - gradient (string) gradient descriptor
+         > Gradient Descriptor
+         * The gradient descriptor is an expression formatted as
+         * follows: `<type>(<coords>)<colors>`.  The `<type>` can be
+         * either linear or radial.  The uppercase `L` or `R` letters
+         * indicate absolute coordinates offset from the SVG surface.
+         * Lowercase `l` or `r` letters indicate coordinates
+         * calculated relative to the element to which the gradient is
+         * applied.  Coordinates specify a linear gradient vector as
+         * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`,
+         * `r` and optional `fx`, `fy` specifying a focal point away
+         * from the center of the circle. Specify `<colors>` as a list
+         * of dash-separated CSS color values.  Each color may be
+         * followed by a custom offset value, separated with a colon
+         * character.
+         > Examples
+         * Linear gradient, relative from top-left corner to bottom-right
+         * corner, from black through red to white:
+         | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
+         * Linear gradient, absolute from (0, 0) to (100, 100), from black
+         * through red at 25% to white:
+         | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");
+         * Radial gradient, relative from the center of the element with radius
+         * half the width, from black to white:
+         | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
+         * To apply the gradient:
+         | paper.circle(50, 50, 40).attr({
+         |     fill: g
+         | });
+         = (object) the `gradient` element
+        \*/
+        proto.gradient = function (str) {
+            return gradient(this.defs, str);
+        };
+        proto.gradientLinear = function (x1, y1, x2, y2) {
+            return gradientLinear(this.defs, x1, y1, x2, y2);
+        };
+        proto.gradientRadial = function (cx, cy, r, fx, fy) {
+            return gradientRadial(this.defs, cx, cy, r, fx, fy);
+        };
+        /*\
+         * Paper.toString
+         [ method ]
+         **
+         * Returns SVG code for the @Paper
+         = (string) SVG code for the @Paper
+        \*/
+        proto.toString = function () {
+            var doc = this.node.ownerDocument,
+                f = doc.createDocumentFragment(),
+                d = doc.createElement("div"),
+                svg = this.node.cloneNode(true),
+                res;
+            f.appendChild(d);
+            d.appendChild(svg);
+            Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"});
+            res = d.innerHTML;
+            f.removeChild(f.firstChild);
+            return res;
+        };
+        /*\
+         * Paper.toDataURL
+         [ method ]
+         **
+         * Returns SVG code for the @Paper as Data URI string.
+         = (string) Data URI string
+        \*/
+        proto.toDataURL = function () {
+            if (window && window.btoa) {
+                return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this)));
+            }
+        };
+        /*\
+         * Paper.clear
+         [ method ]
+         **
+         * Removes all child nodes of the paper, except <defs>.
+        \*/
+        proto.clear = function () {
+            var node = this.node.firstChild,
+                next;
+            while (node) {
+                next = node.nextSibling;
+                if (node.tagName != "defs") {
+                    node.parentNode.removeChild(node);
+                } else {
+                    proto.clear.call({node: node});
+                }
+                node = next;
+            }
+        };
+    }());
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        clone = Snap._.clone,
+        has = "hasOwnProperty",
+        p2s = /,?([a-z]),?/gi,
+        toFloat = parseFloat,
+        math = Math,
+        PI = math.PI,
+        mmin = math.min,
+        mmax = math.max,
+        pow = math.pow,
+        abs = math.abs;
+    function paths(ps) {
+        var p = paths.ps = paths.ps || {};
+        if (p[ps]) {
+            p[ps].sleep = 100;
+        } else {
+            p[ps] = {
+                sleep: 100
+            };
+        }
+        setTimeout(function () {
+            for (var key in p) if (p[has](key) && key != ps) {
+                p[key].sleep--;
+                !p[key].sleep && delete p[key];
+            }
+        });
+        return p[ps];
+    }
+    function box(x, y, width, height) {
+        if (x == null) {
+            x = y = width = height = 0;
+        }
+        if (y == null) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        return {
+            x: x,
+            y: y,
+            width: width,
+            w: width,
+            height: height,
+            h: height,
+            x2: x + width,
+            y2: y + height,
+            cx: x + width / 2,
+            cy: y + height / 2,
+            r1: math.min(width, height) / 2,
+            r2: math.max(width, height) / 2,
+            r0: math.sqrt(width * width + height * height) / 2,
+            path: rectPath(x, y, width, height),
+            vb: [x, y, width, height].join(" ")
+        };
+    }
+    function toString() {
+        return this.join(",").replace(p2s, "$1");
+    }
+    function pathClone(pathArray) {
+        var res = clone(pathArray);
+        res.toString = toString;
+        return res;
+    }
+    function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        if (length == null) {
+            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+        } else {
+            return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
+                getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+        }
+    }
+    function getLengthFactory(istotal, subpath) {
+        function O(val) {
+            return +(+val).toFixed(3);
+        }
+        return Snap._.cacher(function (path, length, onlystart) {
+            if (path instanceof Element) {
+                path = path.attr("d");
+            }
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            sp += [
+                                "C" + O(point.start.x),
+                                O(point.start.y),
+                                O(point.m.x),
+                                O(point.m.y),
+                                O(point.x),
+                                O(point.y)
+                            ];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = [
+                                "M" + O(point.x),
+                                O(point.y) + "C" + O(point.n.x),
+                                O(point.n.y),
+                                O(point.end.x),
+                                O(point.end.y),
+                                O(p[5]),
+                                O(p[6])
+                            ].join();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            return point;
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p.shift() + p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+            return point;
+        }, null, Snap._.clone);
+    }
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            t13 = pow(t1, 3),
+            t12 = pow(t1, 2),
+            t2 = t * t,
+            t3 = t2 * t,
+            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+            ax = t1 * p1x + t * c1x,
+            ay = t1 * p1y + t * c1y,
+            cx = t1 * c2x + t * p2x,
+            cy = t1 * c2y + t * p2y,
+            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+        // (mx > nx || my < ny) && (alpha += 180);
+        return {
+            x: x,
+            y: y,
+            m: {x: mx, y: my},
+            n: {x: nx, y: ny},
+            start: {x: ax, y: ay},
+            end: {x: cx, y: cy},
+            alpha: alpha
+        };
+    }
+    function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        if (!Snap.is(p1x, "array")) {
+            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+        }
+        var bbox = curveDim.apply(null, p1x);
+        return box(
+            bbox.min.x,
+            bbox.min.y,
+            bbox.max.x - bbox.min.x,
+            bbox.max.y - bbox.min.y
+        );
+    }
+    function isPointInsideBBox(bbox, x, y) {
+        return  x >= bbox.x &&
+                x <= bbox.x + bbox.width &&
+                y >= bbox.y &&
+                y <= bbox.y + bbox.height;
+    }
+    function isBBoxIntersect(bbox1, bbox2) {
+        bbox1 = box(bbox1);
+        bbox2 = box(bbox2);
+        return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2)
+            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
+                || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
+                || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+    }
+    function base3(t, p1, p2, p3, p4) {
+        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+        return t * t2 - 3 * p1 + 3 * p2;
+    }
+    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+        if (z == null) {
+            z = 1;
+        }
+        z = z > 1 ? 1 : z < 0 ? 0 : z;
+        var z2 = z / 2,
+            n = 12,
+            Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
+            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
+            sum = 0;
+        for (var i = 0; i < n; i++) {
+            var ct = z2 * Tvalues[i] + z2,
+                xbase = base3(ct, x1, x2, x3, x4),
+                ybase = base3(ct, y1, y2, y3, y4),
+                comb = xbase * xbase + ybase * ybase;
+            sum += Cvalues[i] * math.sqrt(comb);
+        }
+        return z2 * sum;
+    }
+    function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+            return;
+        }
+        var t = 1,
+            step = t / 2,
+            t2 = t - step,
+            l,
+            e = .01;
+        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        while (abs(l - ll) > e) {
+            step /= 2;
+            t2 += (l < ll ? 1 : -1) * step;
+            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        }
+        return t2;
+    }
+    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+        if (
+            mmax(x1, x2) < mmin(x3, x4) ||
+            mmin(x1, x2) > mmax(x3, x4) ||
+            mmax(y1, y2) < mmin(y3, y4) ||
+            mmin(y1, y2) > mmax(y3, y4)
+        ) {
+            return;
+        }
+        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+        if (!denominator) {
+            return;
+        }
+        var px = nx / denominator,
+            py = ny / denominator,
+            px2 = +px.toFixed(2),
+            py2 = +py.toFixed(2);
+        if (
+            px2 < +mmin(x1, x2).toFixed(2) ||
+            px2 > +mmax(x1, x2).toFixed(2) ||
+            px2 < +mmin(x3, x4).toFixed(2) ||
+            px2 > +mmax(x3, x4).toFixed(2) ||
+            py2 < +mmin(y1, y2).toFixed(2) ||
+            py2 > +mmax(y1, y2).toFixed(2) ||
+            py2 < +mmin(y3, y4).toFixed(2) ||
+            py2 > +mmax(y3, y4).toFixed(2)
+        ) {
+            return;
+        }
+        return {x: px, y: py};
+    }
+    function inter(bez1, bez2) {
+        return interHelper(bez1, bez2);
+    }
+    function interCount(bez1, bez2) {
+        return interHelper(bez1, bez2, 1);
+    }
+    function interHelper(bez1, bez2, justCount) {
+        var bbox1 = bezierBBox(bez1),
+            bbox2 = bezierBBox(bez2);
+        if (!isBBoxIntersect(bbox1, bbox2)) {
+            return justCount ? 0 : [];
+        }
+        var l1 = bezlen.apply(0, bez1),
+            l2 = bezlen.apply(0, bez2),
+            n1 = ~~(l1 / 8),
+            n2 = ~~(l2 / 8),
+            dots1 = [],
+            dots2 = [],
+            xy = {},
+            res = justCount ? 0 : [];
+        for (var i = 0; i < n1 + 1; i++) {
+            var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
+            dots1.push({x: p.x, y: p.y, t: i / n1});
+        }
+        for (i = 0; i < n2 + 1; i++) {
+            p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
+            dots2.push({x: p.x, y: p.y, t: i / n2});
+        }
+        for (i = 0; i < n1; i++) {
+            for (var j = 0; j < n2; j++) {
+                var di = dots1[i],
+                    di1 = dots1[i + 1],
+                    dj = dots2[j],
+                    dj1 = dots2[j + 1],
+                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+                if (is) {
+                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+                        continue;
+                    }
+                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
+                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+                        if (justCount) {
+                            res++;
+                        } else {
+                            res.push({
+                                x: is.x,
+                                y: is.y,
+                                t1: t1,
+                                t2: t2
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function pathIntersection(path1, path2) {
+        return interPathHelper(path1, path2);
+    }
+    function pathIntersectionNumber(path1, path2) {
+        return interPathHelper(path1, path2, 1);
+    }
+    function interPathHelper(path1, path2, justCount) {
+        path1 = path2curve(path1);
+        path2 = path2curve(path2);
+        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+            res = justCount ? 0 : [];
+        for (var i = 0, ii = path1.length; i < ii; i++) {
+            var pi = path1[i];
+            if (pi[0] == "M") {
+                x1 = x1m = pi[1];
+                y1 = y1m = pi[2];
+            } else {
+                if (pi[0] == "C") {
+                    bez1 = [x1, y1].concat(pi.slice(1));
+                    x1 = bez1[6];
+                    y1 = bez1[7];
+                } else {
+                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+                    x1 = x1m;
+                    y1 = y1m;
+                }
+                for (var j = 0, jj = path2.length; j < jj; j++) {
+                    var pj = path2[j];
+                    if (pj[0] == "M") {
+                        x2 = x2m = pj[1];
+                        y2 = y2m = pj[2];
+                    } else {
+                        if (pj[0] == "C") {
+                            bez2 = [x2, y2].concat(pj.slice(1));
+                            x2 = bez2[6];
+                            y2 = bez2[7];
+                        } else {
+                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+                            x2 = x2m;
+                            y2 = y2m;
+                        }
+                        var intr = interHelper(bez1, bez2, justCount);
+                        if (justCount) {
+                            res += intr;
+                        } else {
+                            for (var k = 0, kk = intr.length; k < kk; k++) {
+                                intr[k].segment1 = i;
+                                intr[k].segment2 = j;
+                                intr[k].bez1 = bez1;
+                                intr[k].bez2 = bez2;
+                            }
+                            res = res.concat(intr);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function isPointInsidePath(path, x, y) {
+        var bbox = pathBBox(path);
+        return isPointInsideBBox(bbox, x, y) &&
+               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+    }
+    function pathBBox(path) {
+        var pth = paths(path);
+        if (pth.bbox) {
+            return clone(pth.bbox);
+        }
+        if (!path) {
+            return box();
+        }
+        path = path2curve(path);
+        var x = 0,
+            y = 0,
+            X = [],
+            Y = [],
+            p;
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            p = path[i];
+            if (p[0] == "M") {
+                x = p[1];
+                y = p[2];
+                X.push(x);
+                Y.push(y);
+            } else {
+                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                X = X.concat(dim.min.x, dim.max.x);
+                Y = Y.concat(dim.min.y, dim.max.y);
+                x = p[5];
+                y = p[6];
+            }
+        }
+        var xmin = mmin.apply(0, X),
+            ymin = mmin.apply(0, Y),
+            xmax = mmax.apply(0, X),
+            ymax = mmax.apply(0, Y),
+            bb = box(xmin, ymin, xmax - xmin, ymax - ymin);
+        pth.bbox = clone(bb);
+        return bb;
+    }
+    function rectPath(x, y, w, h, r) {
+        if (r) {
+            return [
+                ["M", +x + (+r), y],
+                ["l", w - r * 2, 0],
+                ["a", r, r, 0, 0, 1, r, r],
+                ["l", 0, h - r * 2],
+                ["a", r, r, 0, 0, 1, -r, r],
+                ["l", r * 2 - w, 0],
+                ["a", r, r, 0, 0, 1, -r, -r],
+                ["l", 0, r * 2 - h],
+                ["a", r, r, 0, 0, 1, r, -r],
+                ["z"]
+            ];
+        }
+        var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+        res.toString = toString;
+        return res;
+    }
+    function ellipsePath(x, y, rx, ry, a) {
+        if (a == null && ry == null) {
+            ry = rx;
+        }
+        x = +x;
+        y = +y;
+        rx = +rx;
+        ry = +ry;
+        if (a != null) {
+            var rad = Math.PI / 180,
+                x1 = x + rx * Math.cos(-ry * rad),
+                x2 = x + rx * Math.cos(-a * rad),
+                y1 = y + rx * Math.sin(-ry * rad),
+                y2 = y + rx * Math.sin(-a * rad),
+                res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]];
+        } else {
+            res = [
+                ["M", x, y],
+                ["m", 0, -ry],
+                ["a", rx, ry, 0, 1, 1, 0, 2 * ry],
+                ["a", rx, ry, 0, 1, 1, 0, -2 * ry],
+                ["z"]
+            ];
+        }
+        res.toString = toString;
+        return res;
+    }
+    var unit2px = Snap._unit2px,
+        getPath = {
+        path: function (el) {
+            return el.attr("path");
+        },
+        circle: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx, attr.cy, attr.r);
+        },
+        ellipse: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry);
+        },
+        rect: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry);
+        },
+        image: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height);
+        },
+        line: function (el) {
+            return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")];
+        },
+        polyline: function (el) {
+            return "M" + el.attr("points");
+        },
+        polygon: function (el) {
+            return "M" + el.attr("points") + "z";
+        },
+        deflt: function (el) {
+            var bbox = el.node.getBBox();
+            return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+        }
+    };
+    function pathToRelative(pathArray) {
+        var pth = paths(pathArray),
+            lowerCase = String.prototype.toLowerCase;
+        if (pth.rel) {
+            return pathClone(pth.rel);
+        }
+        if (!Snap.is(pathArray, "array") || !Snap.is(pathArray && pathArray[0], "array")) {
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0;
+        if (pathArray[0][0] == "M") {
+            x = pathArray[0][1];
+            y = pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res.push(["M", x, y]);
+        }
+        for (var i = start, ii = pathArray.length; i < ii; i++) {
+            var r = res[i] = [],
+                pa = pathArray[i];
+            if (pa[0] != lowerCase.call(pa[0])) {
+                r[0] = lowerCase.call(pa[0]);
+                switch (r[0]) {
+                    case "a":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +(pa[6] - x).toFixed(3);
+                        r[7] = +(pa[7] - y).toFixed(3);
+                        break;
+                    case "v":
+                        r[1] = +(pa[1] - y).toFixed(3);
+                        break;
+                    case "m":
+                        mx = pa[1];
+                        my = pa[2];
+                    default:
+                        for (var j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+                        }
+                }
+            } else {
+                r = res[i] = [];
+                if (pa[0] == "m") {
+                    mx = pa[1] + x;
+                    my = pa[2] + y;
+                }
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    res[i][k] = pa[k];
+                }
+            }
+            var len = res[i].length;
+            switch (res[i][0]) {
+                case "z":
+                    x = mx;
+                    y = my;
+                    break;
+                case "h":
+                    x += +res[i][len - 1];
+                    break;
+                case "v":
+                    y += +res[i][len - 1];
+                    break;
+                default:
+                    x += +res[i][len - 2];
+                    y += +res[i][len - 1];
+            }
+        }
+        res.toString = toString;
+        pth.rel = pathClone(res);
+        return res;
+    }
+    function pathToAbsolute(pathArray) {
+        var pth = paths(pathArray);
+        if (pth.abs) {
+            return pathClone(pth.abs);
+        }
+        if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        if (!pathArray || !pathArray.length) {
+            return [["M", 0, 0]];
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0,
+            pa0;
+        if (pathArray[0][0] == "M") {
+            x = +pathArray[0][1];
+            y = +pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res[0] = ["M", x, y];
+        }
+        var crz = pathArray.length == 3 &&
+            pathArray[0][0] == "M" &&
+            pathArray[1][0].toUpperCase() == "R" &&
+            pathArray[2][0].toUpperCase() == "Z";
+        for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+            res.push(r = []);
+            pa = pathArray[i];
+            pa0 = pa[0];
+            if (pa0 != pa0.toUpperCase()) {
+                r[0] = pa0.toUpperCase();
+                switch (r[0]) {
+                    case "A":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +pa[6] + x;
+                        r[7] = +pa[7] + y;
+                        break;
+                    case "V":
+                        r[1] = +pa[1] + y;
+                        break;
+                    case "H":
+                        r[1] = +pa[1] + x;
+                        break;
+                    case "R":
+                        var dots = [x, y].concat(pa.slice(1));
+                        for (var j = 2, jj = dots.length; j < jj; j++) {
+                            dots[j] = +dots[j] + x;
+                            dots[++j] = +dots[j] + y;
+                        }
+                        res.pop();
+                        res = res.concat(catmullRom2bezier(dots, crz));
+                        break;
+                    case "O":
+                        res.pop();
+                        dots = ellipsePath(x, y, pa[1], pa[2]);
+                        dots.push(dots[0]);
+                        res = res.concat(dots);
+                        break;
+                    case "U":
+                        res.pop();
+                        res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                        r = ["U"].concat(res[res.length - 1].slice(-2));
+                        break;
+                    case "M":
+                        mx = +pa[1] + x;
+                        my = +pa[2] + y;
+                    default:
+                        for (j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +pa[j] + ((j % 2) ? x : y);
+                        }
+                }
+            } else if (pa0 == "R") {
+                dots = [x, y].concat(pa.slice(1));
+                res.pop();
+                res = res.concat(catmullRom2bezier(dots, crz));
+                r = ["R"].concat(pa.slice(-2));
+            } else if (pa0 == "O") {
+                res.pop();
+                dots = ellipsePath(x, y, pa[1], pa[2]);
+                dots.push(dots[0]);
+                res = res.concat(dots);
+            } else if (pa0 == "U") {
+                res.pop();
+                res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                r = ["U"].concat(res[res.length - 1].slice(-2));
+            } else {
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    r[k] = pa[k];
+                }
+            }
+            pa0 = pa0.toUpperCase();
+            if (pa0 != "O") {
+                switch (r[0]) {
+                    case "Z":
+                        x = +mx;
+                        y = +my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
+                    default:
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
+                }
+            }
+        }
+        res.toString = toString;
+        pth.abs = pathClone(res);
+        return res;
+    }
+    function l2c(x1, y1, x2, y2) {
+        return [x1, y1, x2, y2, x2, y2];
+    }
+    function q2c(x1, y1, ax, ay, x2, y2) {
+        var _13 = 1 / 3,
+            _23 = 2 / 3;
+        return [
+                _13 * x1 + _23 * ax,
+                _13 * y1 + _23 * ay,
+                _13 * x2 + _23 * ax,
+                _13 * y2 + _23 * ay,
+                x2,
+                y2
+            ];
+    }
+    function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+        // for more information of where this math came from visit:
+        // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+        var _120 = PI * 120 / 180,
+            rad = PI / 180 * (+angle || 0),
+            res = [],
+            xy,
+            rotate = Snap._.cacher(function (x, y, rad) {
+                var X = x * math.cos(rad) - y * math.sin(rad),
+                    Y = x * math.sin(rad) + y * math.cos(rad);
+                return {x: X, y: Y};
+            });
+        if (!recursive) {
+            xy = rotate(x1, y1, -rad);
+            x1 = xy.x;
+            y1 = xy.y;
+            xy = rotate(x2, y2, -rad);
+            x2 = xy.x;
+            y2 = xy.y;
+            var cos = math.cos(PI / 180 * angle),
+                sin = math.sin(PI / 180 * angle),
+                x = (x1 - x2) / 2,
+                y = (y1 - y2) / 2;
+            var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+            if (h > 1) {
+                h = math.sqrt(h);
+                rx = h * rx;
+                ry = h * ry;
+            }
+            var rx2 = rx * rx,
+                ry2 = ry * ry,
+                k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                    math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                cx = k * rx * y / ry + (x1 + x2) / 2,
+                cy = k * -ry * x / rx + (y1 + y2) / 2,
+                f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+                f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+            f1 = x1 < cx ? PI - f1 : f1;
+            f2 = x2 < cx ? PI - f2 : f2;
+            f1 < 0 && (f1 = PI * 2 + f1);
+            f2 < 0 && (f2 = PI * 2 + f2);
+            if (sweep_flag && f1 > f2) {
+                f1 = f1 - PI * 2;
+            }
+            if (!sweep_flag && f2 > f1) {
+                f2 = f2 - PI * 2;
+            }
+        } else {
+            f1 = recursive[0];
+            f2 = recursive[1];
+            cx = recursive[2];
+            cy = recursive[3];
+        }
+        var df = f2 - f1;
+        if (abs(df) > _120) {
+            var f2old = f2,
+                x2old = x2,
+                y2old = y2;
+            f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+            x2 = cx + rx * math.cos(f2);
+            y2 = cy + ry * math.sin(f2);
+            res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+        }
+        df = f2 - f1;
+        var c1 = math.cos(f1),
+            s1 = math.sin(f1),
+            c2 = math.cos(f2),
+            s2 = math.sin(f2),
+            t = math.tan(df / 4),
+            hx = 4 / 3 * rx * t,
+            hy = 4 / 3 * ry * t,
+            m1 = [x1, y1],
+            m2 = [x1 + hx * s1, y1 - hy * c1],
+            m3 = [x2 + hx * s2, y2 - hy * c2],
+            m4 = [x2, y2];
+        m2[0] = 2 * m1[0] - m2[0];
+        m2[1] = 2 * m1[1] - m2[1];
+        if (recursive) {
+            return [m2, m3, m4].concat(res);
+        } else {
+            res = [m2, m3, m4].concat(res).join().split(",");
+            var newres = [];
+            for (var i = 0, ii = res.length; i < ii; i++) {
+                newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+            }
+            return newres;
+        }
+    }
+    function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t;
+        return {
+            x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+            y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+        };
+    }
+
+    // Returns bounding box of cubic bezier curve.
+    // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+    // Original version: NISHIO Hirokazu
+    // Modifications: https://github.com/timo22345
+    function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) {
+        var tvalues = [],
+            bounds = [[], []],
+            a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+        for (var i = 0; i < 2; ++i) {
+            if (i == 0) {
+                b = 6 * x0 - 12 * x1 + 6 * x2;
+                a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+                c = 3 * x1 - 3 * x0;
+            } else {
+                b = 6 * y0 - 12 * y1 + 6 * y2;
+                a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+                c = 3 * y1 - 3 * y0;
+            }
+            if (abs(a) < 1e-12) {
+                if (abs(b) < 1e-12) {
+                    continue;
+                }
+                t = -c / b;
+                if (0 < t && t < 1) {
+                    tvalues.push(t);
+                }
+                continue;
+            }
+            b2ac = b * b - 4 * c * a;
+            sqrtb2ac = math.sqrt(b2ac);
+            if (b2ac < 0) {
+                continue;
+            }
+            t1 = (-b + sqrtb2ac) / (2 * a);
+            if (0 < t1 && t1 < 1) {
+                tvalues.push(t1);
+            }
+            t2 = (-b - sqrtb2ac) / (2 * a);
+            if (0 < t2 && t2 < 1) {
+                tvalues.push(t2);
+            }
+        }
+
+        var x, y, j = tvalues.length,
+            jlen = j,
+            mt;
+        while (j--) {
+            t = tvalues[j];
+            mt = 1 - t;
+            bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
+            bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
+        }
+
+        bounds[0][jlen] = x0;
+        bounds[1][jlen] = y0;
+        bounds[0][jlen + 1] = x3;
+        bounds[1][jlen + 1] = y3;
+        bounds[0].length = bounds[1].length = jlen + 2;
+
+
+        return {
+          min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])},
+          max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])}
+        };
+    }
+
+    function path2curve(path, path2) {
+        var pth = !path2 && paths(path);
+        if (!path2 && pth.curve) {
+            return pathClone(pth.curve);
+        }
+        var p = pathToAbsolute(path),
+            p2 = path2 && pathToAbsolute(path2),
+            attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            processPath = function (path, d, pcom) {
+                var nx, ny;
+                if (!path) {
+                    return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+                }
+                !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
+                switch (path[0]) {
+                    case "M":
+                        d.X = path[1];
+                        d.Y = path[2];
+                        break;
+                    case "A":
+                        path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
+                        break;
+                    case "S":
+                        if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
+                            nx = d.x * 2 - d.bx;          // And reflect the previous
+                            ny = d.y * 2 - d.by;          // command's control point relative to the current point.
+                        }
+                        else {                            // or some else or nothing
+                            nx = d.x;
+                            ny = d.y;
+                        }
+                        path = ["C", nx, ny].concat(path.slice(1));
+                        break;
+                    case "T":
+                        if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
+                            d.qx = d.x * 2 - d.qx;        // And make a reflection similar
+                            d.qy = d.y * 2 - d.qy;        // to case "S".
+                        }
+                        else {                            // or something else or nothing
+                            d.qx = d.x;
+                            d.qy = d.y;
+                        }
+                        path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+                        break;
+                    case "Q":
+                        d.qx = path[1];
+                        d.qy = path[2];
+                        path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                        break;
+                    case "L":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
+                        break;
+                    case "H":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
+                        break;
+                    case "V":
+                        path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
+                        break;
+                    case "Z":
+                        path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
+                        break;
+                }
+                return path;
+            },
+            fixArc = function (pp, i) {
+                if (pp[i].length > 7) {
+                    pp[i].shift();
+                    var pi = pp[i];
+                    while (pi.length) {
+                        pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved
+                        p2 && (pcoms2[i] = "A"); // the same as above
+                        pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
+                    }
+                    pp.splice(i, 1);
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            fixM = function (path1, path2, a1, a2, i) {
+                if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                    path2.splice(i, 0, ["M", a2.x, a2.y]);
+                    a1.bx = 0;
+                    a1.by = 0;
+                    a1.x = path1[i][1];
+                    a1.y = path1[i][2];
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            pcoms1 = [], // path commands of original path p
+            pcoms2 = [], // path commands of original path p2
+            pfirst = "", // temporary holder for original path command
+            pcom = ""; // holder for previous path command of original path
+        for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+            p[i] && (pfirst = p[i][0]); // save current path command
+
+            if (pfirst != "C") // C is not saved yet, because it may be result of conversion
+            {
+                pcoms1[i] = pfirst; // Save current path command
+                i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom
+            }
+            p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
+
+            if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command
+            // which may produce multiple C:s
+            // so we have to make sure that C is also C in original path
+
+            fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
+
+            if (p2) { // the same procedures is done to p2
+                p2[i] && (pfirst = p2[i][0]);
+                if (pfirst != "C") {
+                    pcoms2[i] = pfirst;
+                    i && (pcom = pcoms2[i - 1]);
+                }
+                p2[i] = processPath(p2[i], attrs2, pcom);
+
+                if (pcoms2[i] != "A" && pfirst == "C") {
+                    pcoms2[i] = "C";
+                }
+
+                fixArc(p2, i);
+            }
+            fixM(p, p2, attrs, attrs2, i);
+            fixM(p2, p, attrs2, attrs, i);
+            var seg = p[i],
+                seg2 = p2 && p2[i],
+                seglen = seg.length,
+                seg2len = p2 && seg2.length;
+            attrs.x = seg[seglen - 2];
+            attrs.y = seg[seglen - 1];
+            attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+            attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+            attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+            attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+            attrs2.x = p2 && seg2[seg2len - 2];
+            attrs2.y = p2 && seg2[seg2len - 1];
+        }
+        if (!p2) {
+            pth.curve = pathClone(p);
+        }
+        return p2 ? [p, p2] : p;
+    }
+    function mapPath(path, matrix) {
+        if (!matrix) {
+            return path;
+        }
+        var x, y, i, j, ii, jj, pathi;
+        path = path2curve(path);
+        for (i = 0, ii = path.length; i < ii; i++) {
+            pathi = path[i];
+            for (j = 1, jj = pathi.length; j < jj; j += 2) {
+                x = matrix.x(pathi[j], pathi[j + 1]);
+                y = matrix.y(pathi[j], pathi[j + 1]);
+                pathi[j] = x;
+                pathi[j + 1] = y;
+            }
+        }
+        return path;
+    }
+
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp, z) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+            var p = [
+                        {x: +crp[i - 2], y: +crp[i - 1]},
+                        {x: +crp[i],     y: +crp[i + 1]},
+                        {x: +crp[i + 2], y: +crp[i + 3]},
+                        {x: +crp[i + 4], y: +crp[i + 5]}
+                    ];
+            if (z) {
+                if (!i) {
+                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+                } else if (iLen - 4 == i) {
+                    p[3] = {x: +crp[0], y: +crp[1]};
+                } else if (iLen - 2 == i) {
+                    p[2] = {x: +crp[0], y: +crp[1]};
+                    p[3] = {x: +crp[2], y: +crp[3]};
+                }
+            } else {
+                if (iLen - 4 == i) {
+                    p[3] = p[2];
+                } else if (!i) {
+                    p[0] = {x: +crp[i], y: +crp[i + 1]};
+                }
+            }
+            d.push(["C",
+                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                  (p[1].y + 6*p[2].y - p[3].y) / 6,
+                  p[2].x,
+                  p[2].y
+            ]);
+        }
+
+        return d;
+    }
+
+    // export
+    Snap.path = paths;
+
+    /*\
+     * Snap.path.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the given path in pixels
+     **
+     - path (string) SVG path string
+     **
+     = (number) length
+    \*/
+    Snap.path.getTotalLength = getTotalLength;
+    /*\
+     * Snap.path.getPointAtLength
+     [ method ]
+     **
+     * Returns the coordinates of the point located at the given length along the given path
+     **
+     - path (string) SVG path string
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    Snap.path.getPointAtLength = getPointAtLength;
+    /*\
+     * Snap.path.getSubpath
+     [ method ]
+     **
+     * Returns the subpath of a given path between given start and end lengths
+     **
+     - path (string) SVG path string
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    Snap.path.getSubpath = function (path, from, to) {
+        if (this.getTotalLength(path) - to < 1e-6) {
+            return getSubpathsAtLength(path, from).end;
+        }
+        var a = getSubpathsAtLength(path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+    /*\
+     * Element.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the path in pixels (only works for `path` elements)
+     = (number) length
+    \*/
+    elproto.getTotalLength = function () {
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+    };
+    // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a <path> is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length?
+    /*\
+     * Element.getPointAtLength
+     [ method ]
+     **
+     * Returns coordinates of the point located at the given length on the given path (only works for `path` elements)
+     **
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    elproto.getPointAtLength = function (length) {
+        return getPointAtLength(this.attr("d"), length);
+    };
+    // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear.
+    /*\
+     * Element.getSubpath
+     [ method ]
+     **
+     * Returns subpath of a given element from given start and end lengths (only works for `path` elements)
+     **
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    elproto.getSubpath = function (from, to) {
+        return Snap.path.getSubpath(this.attr("d"), from, to);
+    };
+    Snap._.box = box;
+    /*\
+     * Snap.path.findDotsAtSegment
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds dot coordinates on the given cubic beziér curve at the given t
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     - t (number) position on the curve (0..1)
+     = (object) point information in format:
+     o {
+     o     x: (number) x coordinate of the point,
+     o     y: (number) y coordinate of the point,
+     o     m: {
+     o         x: (number) x coordinate of the left anchor,
+     o         y: (number) y coordinate of the left anchor
+     o     },
+     o     n: {
+     o         x: (number) x coordinate of the right anchor,
+     o         y: (number) y coordinate of the right anchor
+     o     },
+     o     start: {
+     o         x: (number) x coordinate of the start of the curve,
+     o         y: (number) y coordinate of the start of the curve
+     o     },
+     o     end: {
+     o         x: (number) x coordinate of the end of the curve,
+     o         y: (number) y coordinate of the end of the curve
+     o     },
+     o     alpha: (number) angle of the curve derivative at the point
+     o }
+    \*/
+    Snap.path.findDotsAtSegment = findDotsAtSegment;
+    /*\
+     * Snap.path.bezierBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given cubic beziér curve
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     * or
+     - bez (array) array of six points for beziér curve
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.bezierBBox = bezierBBox;
+    /*\
+     * Snap.path.isPointInsideBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside bounding box
+     - bbox (string) bounding box
+     - x (string) x coordinate of the point
+     - y (string) y coordinate of the point
+     = (boolean) `true` if point is inside
+    \*/
+    Snap.path.isPointInsideBBox = isPointInsideBBox;
+    Snap.closest = function (x, y, X, Y) {
+        var r = 100,
+            b = box(x - r / 2, y - r / 2, r, r),
+            inside = [],
+            getter = X[0].hasOwnProperty("x") ? function (i) {
+                return {
+                    x: X[i].x,
+                    y: X[i].y
+                };
+            } : function (i) {
+                return {
+                    x: X[i],
+                    y: Y[i]
+                };
+            },
+            found = 0;
+        while (r <= 1e6 && !found) {
+            for (var i = 0, ii = X.length; i < ii; i++) {
+                var xy = getter(i);
+                if (isPointInsideBBox(b, xy.x, xy.y)) {
+                    found++;
+                    inside.push(xy);
+                    break;
+                }
+            }
+            if (!found) {
+                r *= 2;
+                b = box(x - r / 2, y - r / 2, r, r)
+            }
+        }
+        if (r == 1e6) {
+            return;
+        }
+        var len = Infinity,
+            res;
+        for (i = 0, ii = inside.length; i < ii; i++) {
+            var l = Snap.len(x, y, inside[i].x, inside[i].y);
+            if (len > l) {
+                len = l;
+                inside[i].len = l;
+                res = inside[i];
+            }
+        }
+        return res;
+    };
+    /*\
+     * Snap.path.isBBoxIntersect
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if two bounding boxes intersect
+     - bbox1 (string) first bounding box
+     - bbox2 (string) second bounding box
+     = (boolean) `true` if bounding boxes intersect
+    \*/
+    Snap.path.isBBoxIntersect = isBBoxIntersect;
+    /*\
+     * Snap.path.intersection
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds intersections of two paths
+     - path1 (string) path string
+     - path2 (string) path string
+     = (array) dots of intersection
+     o [
+     o     {
+     o         x: (number) x coordinate of the point,
+     o         y: (number) y coordinate of the point,
+     o         t1: (number) t value for segment of path1,
+     o         t2: (number) t value for segment of path2,
+     o         segment1: (number) order number for segment of path1,
+     o         segment2: (number) order number for segment of path2,
+     o         bez1: (array) eight coordinates representing beziér curve for the segment of path1,
+     o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
+     o     }
+     o ]
+    \*/
+    Snap.path.intersection = pathIntersection;
+    Snap.path.intersectionNumber = pathIntersectionNumber;
+    /*\
+     * Snap.path.isPointInside
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside a given closed path.
+     *
+     * Note: fill mode doesn’t affect the result of this method.
+     - path (string) path string
+     - x (number) x of the point
+     - y (number) y of the point
+     = (boolean) `true` if point is inside the path
+    \*/
+    Snap.path.isPointInside = isPointInsidePath;
+    /*\
+     * Snap.path.getBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given path
+     - path (string) path string
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.getBBox = pathBBox;
+    Snap.path.get = getPath;
+    /*\
+     * Snap.path.toRelative
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into relative values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toRelative = pathToRelative;
+    /*\
+     * Snap.path.toAbsolute
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into absolute values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toAbsolute = pathToAbsolute;
+    /*\
+     * Snap.path.toCubic
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path to a new path where all segments are cubic beziér curves
+     - pathString (string|array) path string or array of segments
+     = (array) array of segments
+    \*/
+    Snap.path.toCubic = path2curve;
+    /*\
+     * Snap.path.map
+     [ method ]
+     **
+     * Transform the path string with the given matrix
+     - path (string) path string
+     - matrix (object) see @Matrix
+     = (string) transformed path string
+    \*/
+    Snap.path.map = mapPath;
+    Snap.path.toString = toString;
+    Snap.path.clone = pathClone;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var mmax = Math.max,
+        mmin = Math.min;
+
+    // Set
+    var Set = function (items) {
+        this.items = [];
+	this.bindings = {};
+        this.length = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items.length; i < ii; i++) {
+                if (items[i]) {
+                    this[this.items.length] = this.items[this.items.length] = items[i];
+                    this.length++;
+                }
+            }
+        }
+    },
+    setproto = Set.prototype;
+    /*\
+     * Set.push
+     [ method ]
+     **
+     * Adds each argument to the current set
+     = (object) original element
+    \*/
+    setproto.push = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments.length; i < ii; i++) {
+            item = arguments[i];
+            if (item) {
+                len = this.items.length;
+                this[len] = this.items[len] = item;
+                this.length++;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.pop
+     [ method ]
+     **
+     * Removes last element and returns it
+     = (object) element
+    \*/
+    setproto.pop = function () {
+        this.length && delete this[this.length--];
+        return this.items.pop();
+    };
+    /*\
+     * Set.forEach
+     [ method ]
+     **
+     * Executes given function for each element in the set
+     *
+     * If the function returns `false`, the loop stops running.
+     **
+     - callback (function) function to run
+     - thisArg (object) context object for the callback
+     = (object) Set object
+    \*/
+    setproto.forEach = function (callback, thisArg) {
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
+                return this;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.animate
+     [ method ]
+     **
+     * Animates each element in set in sync.
+     *
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     * or
+     - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]`
+     > Usage
+     | // animate all elements in set to radius 10
+     | set.animate({r: 10}, 500, mina.easein);
+     | // or
+     | // animate first element to radius 10, but second to radius 20 and in different time
+     | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]);
+     = (Element) the current element
+    \*/
+    setproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Snap._.Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = easing.dur;
+            attrs = attrs.attr;
+        }
+        var args = arguments;
+        if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) {
+            var each = true;
+        }
+        var begin,
+            handler = function () {
+                if (begin) {
+                    this.b = begin;
+                } else {
+                    begin = this.b;
+                }
+            },
+            cb = 0,
+            set = this,
+            callbacker = callback && function () {
+                if (++cb == set.length) {
+                    callback.call(this);
+                }
+            };
+        return this.forEach(function (el, i) {
+            eve.once("snap.animcreated." + el.id, handler);
+            if (each) {
+                args[i] && el.animate.apply(el, args[i]);
+            } else {
+                el.animate(attrs, ms, easing, callbacker);
+            }
+        });
+    };
+    setproto.remove = function () {
+        while (this.length) {
+            this.pop().remove();
+        }
+        return this;
+    };
+    /*\
+     * Set.bind
+     [ method ]
+     **
+     * Specifies how to handle a specific attribute when applied
+     * to a set.
+     *
+     **
+     - attr (string) attribute name
+     - callback (function) function to run
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     - eattr (string) attribute on the element to bind the attribute to
+     = (object) Set object
+    \*/
+    setproto.bind = function (attr, a, b) {
+        var data = {};
+        if (typeof a == "function") {
+            this.bindings[attr] = a;
+        } else {
+            var aname = b || attr;
+            this.bindings[attr] = function (v) {
+                data[aname] = v;
+                a.attr(data);
+            };
+        }
+        return this;
+    };
+    setproto.attr = function (value) {
+        var unbound = {};
+        for (var k in value) {
+            if (this.bindings[k]) {
+                this.bindings[k](value[k]);
+            } else {
+                unbound[k] = value[k];
+            }
+        }
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            this.items[i].attr(unbound);
+        }
+        return this;
+    };
+    /*\
+     * Set.clear
+     [ method ]
+     **
+     * Removes all elements from the set
+    \*/
+    setproto.clear = function () {
+        while (this.length) {
+            this.pop();
+        }
+    };
+    /*\
+     * Set.splice
+     [ method ]
+     **
+     * Removes range of elements from the set
+     **
+     - index (number) position of the deletion
+     - count (number) number of element to remove
+     - insertion… (object) #optional elements to insert
+     = (object) set elements that were deleted
+    \*/
+    setproto.splice = function (index, count, insertion) {
+        index = index < 0 ? mmax(this.length + index, 0) : index;
+        count = mmax(0, mmin(this.length - index, count));
+        var tail = [],
+            todel = [],
+            args = [],
+            i;
+        for (i = 2; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        }
+        for (i = 0; i < count; i++) {
+            todel.push(this[index + i]);
+        }
+        for (; i < this.length - index; i++) {
+            tail.push(this[index + i]);
+        }
+        var arglen = args.length;
+        for (i = 0; i < arglen + tail.length; i++) {
+            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+        }
+        i = this.items.length = this.length -= count - arglen;
+        while (this[i]) {
+            delete this[i++];
+        }
+        return new Set(todel);
+    };
+    /*\
+     * Set.exclude
+     [ method ]
+     **
+     * Removes given element from the set
+     **
+     - element (object) element to remove
+     = (boolean) `true` if object was found and removed from the set
+    \*/
+    setproto.exclude = function (el) {
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
+            return true;
+        }
+        return false;
+    };
+    setproto.insertAfter = function (el) {
+        var i = this.items.length;
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    setproto.getBBox = function () {
+        var x = [],
+            y = [],
+            x2 = [],
+            y2 = [];
+        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+            var box = this.items[i].getBBox();
+            x.push(box.x);
+            y.push(box.y);
+            x2.push(box.x + box.width);
+            y2.push(box.y + box.height);
+        }
+        x = mmin.apply(0, x);
+        y = mmin.apply(0, y);
+        x2 = mmax.apply(0, x2);
+        y2 = mmax.apply(0, y2);
+        return {
+            x: x,
+            y: y,
+            x2: x2,
+            y2: y2,
+            width: x2 - x,
+            height: y2 - y,
+            cx: x + (x2 - x) / 2,
+            cy: y + (y2 - y) / 2
+        };
+    };
+    setproto.clone = function (s) {
+        s = new Set;
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            s.push(this.items[i].clone());
+        }
+        return s;
+    };
+    setproto.toString = function () {
+        return "Snap\u2018s set";
+    };
+    setproto.type = "set";
+    // export
+    Snap.Set = Set;
+    Snap.set = function () {
+        var set = new Set;
+        if (arguments.length) {
+            set.push.apply(set, Array.prototype.slice.call(arguments, 0));
+        }
+        return set;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var names = {},
+        reUnit = /[a-z]+$/i,
+        Str = String;
+    names.stroke = names.fill = "colour";
+    function getEmpty(item) {
+        var l = item[0];
+        switch (l.toLowerCase()) {
+            case "t": return [l, 0, 0];
+            case "m": return [l, 1, 0, 0, 1, 0, 0];
+            case "r": if (item.length == 4) {
+                return [l, 0, item[2], item[3]];
+            } else {
+                return [l, 0];
+            }
+            case "s": if (item.length == 5) {
+                return [l, 1, 1, item[3], item[4]];
+            } else if (item.length == 3) {
+                return [l, 1, 1];
+            } else {
+                return [l, 1];
+            }
+        }
+    }
+    function equaliseTransform(t1, t2, getBBox) {
+        t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+        t1 = Snap.parseTransformString(t1) || [];
+        t2 = Snap.parseTransformString(t2) || [];
+        var maxlength = Math.max(t1.length, t2.length),
+            from = [],
+            to = [],
+            i = 0, j, jj,
+            tt1, tt2;
+        for (; i < maxlength; i++) {
+            tt1 = t1[i] || getEmpty(t2[i]);
+            tt2 = t2[i] || getEmpty(tt1);
+            if ((tt1[0] != tt2[0]) ||
+                (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                ) {
+                    t1 = Snap._.transform2matrix(t1, getBBox());
+                    t2 = Snap._.transform2matrix(t2, getBBox());
+                    from = [["m", t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]];
+                    to = [["m", t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]];
+                    break;
+            }
+            from[i] = [];
+            to[i] = [];
+            for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) {
+                j in tt1 && (from[i][j] = tt1[j]);
+                j in tt2 && (to[i][j] = tt2[j]);
+            }
+        }
+        return {
+            from: path2array(from),
+            to: path2array(to),
+            f: getPath(from)
+        };
+    }
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    function getViewBox(val) {
+        return val.join(" ");
+    }
+    function getColour(clr) {
+        return Snap.rgb(clr[0], clr[1], clr[2]);
+    }
+    function getPath(path) {
+        var k = 0, i, ii, j, jj, out, a, b = [];
+        for (i = 0, ii = path.length; i < ii; i++) {
+            out = "[";
+            a = ['"' + path[i][0] + '"'];
+            for (j = 1, jj = path[i].length; j < jj; j++) {
+                a[j] = "val[" + (k++) + "]";
+            }
+            out += a + "]";
+            b[i] = out;
+        }
+        return Function("val", "return Snap.path.toString.call([" + b + "])");
+    }
+    function path2array(path) {
+        var out = [];
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            for (var j = 1, jj = path[i].length; j < jj; j++) {
+                out.push(path[i][j]);
+            }
+        }
+        return out;
+    }
+    function isNumeric(obj) {
+        return isFinite(parseFloat(obj));
+    }
+    function arrayEqual(arr1, arr2) {
+        if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) {
+            return false;
+        }
+        return arr1.toString() == arr2.toString();
+    }
+    Element.prototype.equal = function (name, b) {
+        return eve("snap.util.equal", this, name, b).firstDefined();
+    };
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this;
+        if (isNumeric(a) && isNumeric(b)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getNumber
+            };
+        }
+        if (names[name] == "colour") {
+            A = Snap.color(a);
+            B = Snap.color(b);
+            return {
+                from: [A.r, A.g, A.b, A.opacity],
+                to: [B.r, B.g, B.b, B.opacity],
+                f: getColour
+            };
+        }
+        if (name == "viewBox") {
+            A = this.attr(name).vb.split(" ").map(Number);
+            B = b.split(" ").map(Number);
+            return {
+                from: A,
+                to: B,
+                f: getViewBox
+            };
+        }
+        if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
+            if (b instanceof Snap.Matrix) {
+                b = b.toTransformString();
+            }
+            if (!Snap._.rgTransform.test(b)) {
+                b = Snap._.svgTransform2string(b);
+            }
+            return equaliseTransform(a, b, function () {
+                return el.getBBox(1);
+            });
+        }
+        if (name == "d" || name == "path") {
+            A = Snap.path.toCubic(a, b);
+            return {
+                from: path2array(A[0]),
+                to: path2array(A[1]),
+                f: getPath(A[0])
+            };
+        }
+        if (name == "points") {
+            A = Str(a).split(Snap._.separator);
+            B = Str(b).split(Snap._.separator);
+            return {
+                from: A,
+                to: B,
+                f: function (val) { return val; }
+            };
+        }
+        var aUnit = a.match(reUnit),
+            bUnit = Str(b).match(reUnit);
+        if (aUnit && arrayEqual(aUnit, bUnit)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getUnit(aUnit)
+            };
+        } else {
+            return {
+                from: this.asPX(name),
+                to: this.asPX(name, b),
+                f: getNumber
+            };
+        }
+    });
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+    has = "hasOwnProperty",
+    supportsTouch = "createTouch" in glob.doc,
+    events = [
+        "click", "dblclick", "mousedown", "mousemove", "mouseout",
+        "mouseover", "mouseup", "touchstart", "touchmove", "touchend",
+        "touchcancel"
+    ],
+    touchMap = {
+        mousedown: "touchstart",
+        mousemove: "touchmove",
+        mouseup: "touchend"
+    },
+    getScroll = function (xy, el) {
+        var name = xy == "y" ? "scrollTop" : "scrollLeft",
+            doc = el && el.node ? el.node.ownerDocument : glob.doc;
+        return doc[name in doc.documentElement ? "documentElement" : "body"][name];
+    },
+    preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = function (obj, type, fn, element) {
+        var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+            f = function (e) {
+                var scrollY = getScroll("y", element),
+                    scrollX = getScroll("x", element);
+                if (supportsTouch && touchMap[has](type)) {
+                    for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                        if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) {
+                            var olde = e;
+                            e = e.targetTouches[i];
+                            e.originalEvent = olde;
+                            e.preventDefault = preventTouch;
+                            e.stopPropagation = stopTouch;
+                            break;
+                        }
+                    }
+                }
+                var x = e.clientX + scrollX,
+                    y = e.clientY + scrollY;
+                return fn.call(element, e, x, y);
+            };
+
+        if (type !== realName) {
+            obj.addEventListener(type, f, false);
+        }
+
+        obj.addEventListener(realName, f, false);
+
+        return function () {
+            if (type !== realName) {
+                obj.removeEventListener(type, f, false);
+            }
+
+            obj.removeEventListener(realName, f, false);
+            return true;
+        };
+    },
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = getScroll("y"),
+            scrollX = getScroll("x"),
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches && e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            var node = dragi.el.node,
+                o,
+                next = node.nextSibling,
+                parent = node.parentNode,
+                display = node.style.display;
+            // glob.win.opera && parent.removeChild(node);
+            // node.style.display = "none";
+            // o = dragi.el.paper.getElementByPoint(x, y);
+            // node.style.display = display;
+            // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+            // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o);
+            x += scrollX;
+            y += scrollY;
+            eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        Snap.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+            eve.off("snap.drag.*." + dragi.el.id);
+        }
+        drag = [];
+    };
+    /*\
+     * Element.click
+     [ method ]
+     **
+     * Adds a click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unclick
+     [ method ]
+     **
+     * Removes a click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.dblclick
+     [ method ]
+     **
+     * Adds a double click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.undblclick
+     [ method ]
+     **
+     * Removes a double click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousedown
+     [ method ]
+     **
+     * Adds a mousedown event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousedown
+     [ method ]
+     **
+     * Removes a mousedown event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousemove
+     [ method ]
+     **
+     * Adds a mousemove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousemove
+     [ method ]
+     **
+     * Removes a mousemove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseout
+     [ method ]
+     **
+     * Adds a mouseout event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseout
+     [ method ]
+     **
+     * Removes a mouseout event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseover
+     [ method ]
+     **
+     * Adds a mouseover event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseover
+     [ method ]
+     **
+     * Removes a mouseover event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseup
+     [ method ]
+     **
+     * Adds a mouseup event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseup
+     [ method ]
+     **
+     * Removes a mouseup event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchstart
+     [ method ]
+     **
+     * Adds a touchstart event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchstart
+     [ method ]
+     **
+     * Removes a touchstart event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchmove
+     [ method ]
+     **
+     * Adds a touchmove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchmove
+     [ method ]
+     **
+     * Removes a touchmove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchend
+     [ method ]
+     **
+     * Adds a touchend event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchend
+     [ method ]
+     **
+     * Removes a touchend event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchcancel
+     [ method ]
+     **
+     * Adds a touchcancel event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchcancel
+     [ method ]
+     **
+     * Removes a touchcancel event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    for (var i = events.length; i--;) {
+        (function (eventName) {
+            Snap[eventName] = elproto[eventName] = function (fn, scope) {
+                if (Snap.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({
+                        name: eventName,
+                        f: fn,
+                        unbind: addEvent(this.node || document, eventName, fn, scope || this)
+                    });
+                } else {
+                    for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) {
+                        try {
+                            this.events[i].f.call(this);
+                        } catch (e) {}
+                    }
+                }
+                return this;
+            };
+            Snap["un" + eventName] =
+            elproto["un" + eventName] = function (fn) {
+                var events = this.events || [],
+                    l = events.length;
+                while (l--) if (events[l].name == eventName &&
+                               (events[l].f == fn || !fn)) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    /*\
+     * Element.hover
+     [ method ]
+     **
+     * Adds hover event handlers to the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     - icontext (object) #optional context for hover in handler
+     - ocontext (object) #optional context for hover out handler
+     = (object) @Element
+    \*/
+    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+    };
+    /*\
+     * Element.unhover
+     [ method ]
+     **
+     * Removes hover event handlers from the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     = (object) @Element
+    \*/
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    var draggable = [];
+    // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture.
+    // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from?
+    // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason.
+    // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start.<id> on start, drag.end.<id> on end and drag.move.<id> on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID?
+    /*\
+     * Element.drag
+     [ method ]
+     **
+     * Adds event handlers for an element's drag gesture
+     **
+     - onmove (function) handler for moving
+     - onstart (function) handler for drag start
+     - onend (function) handler for drag end
+     - mcontext (object) #optional context for moving handler
+     - scontext (object) #optional context for drag start handler
+     - econtext (object) #optional context for drag end handler
+     * Additionaly following `drag` events are triggered: `drag.start.<id>` on start,
+     * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element is dragged over another element
+     * `drag.over.<id>` fires as well.
+     *
+     * Start event and start handler are called in specified context or in context of the element with following parameters:
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * Move event and move handler are called in specified context or in context of the element with following parameters:
+     o dx (number) shift by x from the start point
+     o dy (number) shift by y from the start point
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * End event and end handler are called in specified context or in context of the element with following parameters:
+     o event (object) DOM event object
+     = (object) @Element
+    \*/
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        var el = this;
+        if (!arguments.length) {
+            var origTransform;
+            return el.drag(function (dx, dy) {
+                this.attr({
+                    transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
+                });
+            }, function () {
+                origTransform = this.transform().local;
+            });
+        }
+        function start(e, x, y) {
+            (e.originalEvent || e).preventDefault();
+            el._drag.x = x;
+            el._drag.y = y;
+            el._drag.id = e.identifier;
+            !drag.length && Snap.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+            onstart && eve.on("snap.drag.start." + el.id, onstart);
+            onmove && eve.on("snap.drag.move." + el.id, onmove);
+            onend && eve.on("snap.drag.end." + el.id, onend);
+            eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e);
+        }
+        function init(e, x, y) {
+            eve("snap.draginit." + el.id, el, e, x, y);
+        }
+        eve.on("snap.draginit." + el.id, start);
+        el._drag = {};
+        draggable.push({el: el, start: start, init: init});
+        el.mousedown(init);
+        return el;
+    };
+    /*
+     * Element.onDragOver
+     [ method ]
+     **
+     * Shortcut to assign event handler for `drag.over.<id>` event, where `id` is the element's `id` (see @Element.id)
+     - f (function) handler for event, first argument would be the element you are dragging over
+    \*/
+    // elproto.onDragOver = function (f) {
+    //     f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id);
+    // };
+    /*\
+     * Element.undrag
+     [ method ]
+     **
+     * Removes all drag event handlers from the given element
+    \*/
+    elproto.undrag = function () {
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].init);
+            draggable.splice(i, 1);
+            eve.unbind("snap.drag.*." + this.id);
+            eve.unbind("snap.draginit." + this.id);
+        }
+        !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp);
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        pproto = Paper.prototype,
+        rgurl = /^\s*url\((.+)\)/,
+        Str = String,
+        $ = Snap._.$;
+    Snap.filter = {};
+    /*\
+     * Paper.filter
+     [ method ]
+     **
+     * Creates a `<filter>` element
+     **
+     - filstr (string) SVG fragment of filter provided as a string
+     = (object) @Element
+     * Note: It is recommended to use filters embedded into the page inside an empty SVG element.
+     > Usage
+     | var f = paper.filter('<feGaussianBlur stdDeviation="2"/>'),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    pproto.filter = function (filstr) {
+        var paper = this;
+        if (paper.type != "svg") {
+            paper = paper.paper;
+        }
+        var f = Snap.parse(Str(filstr)),
+            id = Snap._.id(),
+            width = paper.node.offsetWidth,
+            height = paper.node.offsetHeight,
+            filter = $("filter");
+        $(filter, {
+            id: id,
+            filterUnits: "userSpaceOnUse"
+        });
+        filter.appendChild(f.node);
+        paper.defs.appendChild(filter);
+        return new Element(filter);
+    };
+
+    eve.on("snap.util.getattr.filter", function () {
+        eve.stop();
+        var p = $(this.node, "filter");
+        if (p) {
+            var match = Str(p).match(rgurl);
+            return match && Snap.select(match[1]);
+        }
+    });
+    eve.on("snap.util.attr.filter", function (value) {
+        if (value instanceof Element && value.type == "filter") {
+            eve.stop();
+            var id = value.node.id;
+            if (!id) {
+                $(value.node, {id: value.id});
+                id = value.id;
+            }
+            $(this.node, {
+                filter: Snap.url(id)
+            });
+        }
+        if (!value || value == "none") {
+            eve.stop();
+            this.node.removeAttribute("filter");
+        }
+    });
+    /*\
+     * Snap.filter.blur
+     [ method ]
+     **
+     * Returns an SVG markup string for the blur filter
+     **
+     - x (number) amount of horizontal blur, in pixels
+     - y (number) #optional amount of vertical blur, in pixels
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.blur(5, 10)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.blur = function (x, y) {
+        if (x == null) {
+            x = 2;
+        }
+        var def = y == null ? x : [x, y];
+        return Snap.format('\<feGaussianBlur stdDeviation="{def}"/>', {
+            def: def
+        });
+    };
+    Snap.filter.blur.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.shadow
+     [ method ]
+     **
+     * Returns an SVG markup string for the shadow filter
+     **
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - blur (number) #optional amount of blur
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * which makes blur default to `4`. Or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - opacity (number) #optional `0..1` opacity of the shadow
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.shadow(0, 2, 3)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.shadow = function (dx, dy, blur, color, opacity) {
+        if (typeof blur == "string") {
+            color = blur;
+            opacity = color;
+            blur = 4;
+        }
+        if (typeof color != "string") {
+            opacity = color;
+            color = "#000";
+        }
+        color = color || "#000";
+        if (blur == null) {
+            blur = 4;
+        }
+        if (opacity == null) {
+            opacity = 1;
+        }
+        if (dx == null) {
+            dx = 0;
+            dy = 2;
+        }
+        if (dy == null) {
+            dy = dx;
+        }
+        color = Snap.color(color);
+        return Snap.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', {
+            color: color,
+            dx: dx,
+            dy: dy,
+            blur: blur,
+            opacity: opacity
+        });
+    };
+    Snap.filter.shadow.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.grayscale
+     [ method ]
+     **
+     * Returns an SVG markup string for the grayscale filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.grayscale = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>', {
+            a: 0.2126 + 0.7874 * (1 - amount),
+            b: 0.7152 - 0.7152 * (1 - amount),
+            c: 0.0722 - 0.0722 * (1 - amount),
+            d: 0.2126 - 0.2126 * (1 - amount),
+            e: 0.7152 + 0.2848 * (1 - amount),
+            f: 0.0722 - 0.0722 * (1 - amount),
+            g: 0.2126 - 0.2126 * (1 - amount),
+            h: 0.0722 + 0.9278 * (1 - amount)
+        });
+    };
+    Snap.filter.grayscale.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.sepia
+     [ method ]
+     **
+     * Returns an SVG markup string for the sepia filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.sepia = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>', {
+            a: 0.393 + 0.607 * (1 - amount),
+            b: 0.769 - 0.769 * (1 - amount),
+            c: 0.189 - 0.189 * (1 - amount),
+            d: 0.349 - 0.349 * (1 - amount),
+            e: 0.686 + 0.314 * (1 - amount),
+            f: 0.168 - 0.168 * (1 - amount),
+            g: 0.272 - 0.272 * (1 - amount),
+            h: 0.534 - 0.534 * (1 - amount),
+            i: 0.131 + 0.869 * (1 - amount)
+        });
+    };
+    Snap.filter.sepia.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.saturate
+     [ method ]
+     **
+     * Returns an SVG markup string for the saturate filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.saturate = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="saturate" values="{amount}"/>', {
+            amount: 1 - amount
+        });
+    };
+    Snap.filter.saturate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.hueRotate
+     [ method ]
+     **
+     * Returns an SVG markup string for the hue-rotate filter
+     **
+     - angle (number) angle of rotation
+     = (string) filter representation
+    \*/
+    Snap.filter.hueRotate = function (angle) {
+        angle = angle || 0;
+        return Snap.format('<feColorMatrix type="hueRotate" values="{angle}"/>', {
+            angle: angle
+        });
+    };
+    Snap.filter.hueRotate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.invert
+     [ method ]
+     **
+     * Returns an SVG markup string for the invert filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.invert = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+//        <feColorMatrix type="matrix" values="-1 0 0 0 1  0 -1 0 0 1  0 0 -1 0 1  0 0 0 1 0" color-interpolation-filters="sRGB"/>
+        return Snap.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: 1 - amount
+        });
+    };
+    Snap.filter.invert.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.brightness
+     [ method ]
+     **
+     * Returns an SVG markup string for the brightness filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.brightness = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>', {
+            amount: amount
+        });
+    };
+    Snap.filter.brightness.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.contrast
+     [ method ]
+     **
+     * Returns an SVG markup string for the contrast filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.contrast = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: .5 - amount / 2
+        });
+    };
+    Snap.filter.contrast.toString = function () {
+        return this();
+    };
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var box = Snap._.box,
+        is = Snap.is,
+        firstLetter = /^[^a-z]*([tbmlrc])/i,
+        toString = function () {
+            return "T" + this.dx + "," + this.dy;
+        };
+    /*\
+     * Element.getAlign
+     [ method ]
+     **
+     * Returns shift needed to align the element relatively to given element.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string
+     > Usage
+     | el.transform(el.getAlign(el2, "top"));
+     * or
+     | var dy = el.getAlign(el2, "top").dy;
+    \*/
+    Element.prototype.getAlign = function (el, way) {
+        if (way == null && is(el, "string")) {
+            way = el;
+            el = null;
+        }
+        el = el || this.paper;
+        var bx = el.getBBox ? el.getBBox() : box(el),
+            bb = this.getBBox(),
+            out = {};
+        way = way && way.match(firstLetter);
+        way = way ? way[1].toLowerCase() : "c";
+        switch (way) {
+            case "t":
+                out.dx = 0;
+                out.dy = bx.y - bb.y;
+            break;
+            case "b":
+                out.dx = 0;
+                out.dy = bx.y2 - bb.y2;
+            break;
+            case "m":
+                out.dx = 0;
+                out.dy = bx.cy - bb.cy;
+            break;
+            case "l":
+                out.dx = bx.x - bb.x;
+                out.dy = 0;
+            break;
+            case "r":
+                out.dx = bx.x2 - bb.x2;
+                out.dy = 0;
+            break;
+            default:
+                out.dx = bx.cx - bb.cx;
+                out.dy = 0;
+            break;
+        }
+        out.toString = toString;
+        return out;
+    };
+    /*\
+     * Element.align
+     [ method ]
+     **
+     * Aligns the element relatively to given one via transformation.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object) this element
+     > Usage
+     | el.align(el2, "top");
+     * or
+     | el.align("middle");
+    \*/
+    Element.prototype.align = function (el, way) {
+        return this.transform("..." + this.getAlign(el, way));
+    };
+});
+
+return Snap;
+}));
diff --git a/web/pgadmin/misc/templates/explain/js/explain.js b/web/pgadmin/misc/templates/explain/js/explain.js
new file mode 100644
index 0000000..6efbdee
--- /dev/null
+++ b/web/pgadmin/misc/templates/explain/js/explain.js
@@ -0,0 +1,688 @@
+define (
+  'pgadmin.misc.explain',
+  ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'backbone', 'snap.svg'],
+  function($, _, S, pgAdmin, Backbone, Snap) {
+
+pgAdmin = pgAdmin || window.pgAdmin || {};
+var pgExplain = pgAdmin.Explain;
+
+// Snap.svg plug-in to write multitext as image name
+Snap.plugin(function (Snap, Element, Paper, glob) {
+  Paper.prototype.multitext = function (x, y, txt, max_width, attributes) {
+    var svg = Snap(),
+        abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+        isWordBroken = false,
+        temp = svg.text(0, 0, abc);
+
+    temp.attr(attributes);
+
+    /*
+     * Find letter width in pixels and
+     * index from where the text should be broken
+     */
+    var letter_width = temp.getBBox().width / abc.length,
+        word_break_index = Math.round((max_width / letter_width)) - 1;
+
+    svg.remove();
+
+    var words = txt.split(" "),
+        width_so_far = 0,
+        lines=[], curr_line = '',
+        /*
+         * Function to divide string into multiple lines
+         * and store them in an array if it size crosses
+         * the max-width boundary.
+         */
+        splitTextInMultiLine = function(leading, so_far, line) {
+          var l = line.length,
+              res = [];
+
+          if (l == 0)
+            return res;
+
+          if (so_far && (so_far + (l * letter_width) > max_width)) {
+            res.push(leading);
+            res = res.concat(splitTextInMultiLine('', 0, line));
+          } else if (so_far) {
+            res.push(leading + ' ' + line);
+          } else {
+            if (leading)
+                res.push(leading);
+            if (line.length > word_break_index + 1)
+                res.push(line.slice(0, word_break_index) + '-');
+            else
+                res.push(line);
+            res = res.concat(splitTextInMultiLine('', 0, line.slice(word_break_index)));
+          }
+
+          return res;
+        };
+
+    for (var i = 0; i < words.length; i++) {
+      var tmpArr = splitTextInMultiLine(
+            curr_line, width_so_far, words[i]
+          );
+
+      if (curr_line) {
+        lines = lines.slice(0, lines.length - 2);
+      }
+      lines = lines.concat(tmpArr);
+      curr_line = lines[lines.length - 1];
+      width_so_far = (curr_line.length * letter_width);
+    }
+
+    // Create multiple tspan for each string in array
+    var t = this.text(x,y,lines).attr(attributes);
+    t.selectAll("tspan:nth-child(n+2)").attr({
+      dy: "1.2em",
+      x: x
+    });
+    return t;
+  };
+});
+
+if (pgAdmin.Explain)
+    return pgAdmin.Explain;
+
+var pgExplain = pgAdmin.Explain = {
+   // Prefix path where images are stored
+   prefix: '{{ url_for('misc.static', filename='explain/img') }}/'
+};
+
+/*
+ * A map which is used to fetch the image to be drawn and
+ * text which will appear below it
+ */
+var imageMapper = {
+    "Aggregate" : {
+        "image":"ex_aggregate.png", "image_text":"Aggregate"
+    },
+    'Append' : {
+        "image":"ex_append.png","image_text":"Append"
+    },
+    "Bitmap Index Scan" : function(data) {
+        return {
+            "image":"ex_bmp_index.png", "image_text":data['Index Name']
+        };
+    },
+    "Bitmap Heap Scan" : function(data) {
+  return {"image":"ex_bmp_heap.png","image_text":data['Relation Name']};
+},
+"BitmapAnd" : {"image":"ex_bmp_and.png","image_text":"Bitmap AND"},
+"BitmapOr" : {"image":"ex_bmp_or.png","image_text":"Bitmap OR"},
+"CTE Scan" : {"image":"ex_cte_scan.png","image_text":"CTE Scan"},
+"Function Scan" : {"image":"ex_result.png","image_text":"Function Scan"},
+"Foreign Scan" : {"image":"ex_foreign_scan.png","image_text":"Foreign Scan"},
+"Gather" : {"image":"ex_gather_motion.png","image_text":"Gather"},
+"Group" : {"image":"ex_group.png","image_text":"Group"},
+"GroupAggregate": {"image":"ex_aggregate.png","image_text":"Group Aggregate"},
+"Hash" : {"image":"ex_hash.png","image_text":"Hash"},
+"Hash Join": function(data) {
+  if (!data['Join Type']) return {"image":"ex_join.png","image_text":"Join"};
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_hash_anti_join.png","image_text":"Hash Anti Join"};
+    case 'Semi': return {"image":"ex_hash_semi_join.png","image_text":"Hash Semi Join"};
+    default: return {"image":"ex_hash.png","image_text":String("Hash " + data['Join Type'] + " Join" )};
+  }
+},
+"HashAggregate" : {"image":"ex_aggregate.png","image_text":"Hash Aggregate"},
+"Index Only Scan" : function(data) {
+  return {"image":"ex_index_only_scan.png","image_text":data['Index Name']};
+},
+"Index Scan" : function(data) {
+  return {"image":"ex_index_scan.png","image_text":data['Index Name']};
+},
+"Index Scan Backword" : {"image":"ex_index_scan.png","image_text":"Index Backward Scan"},
+"Limit" : {"image":"ex_limit.png","image_text":"Limit"},
+"LockRows" : {"image":"ex_lock_rows.png","image_text":"Lock Rows"},
+"Materialize" : {"image":"ex_materialize.png","image_text":"Materialize"},
+"Merge Append": {"image":"ex_merge_append.png","image_text":"Merge Append"},
+"Merge Join": function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_merge_anti_join.png","image_text":"Merge Anti Join"};
+    case 'Semi': return {"image":"ex_merge_semi_join.png","image_text":"Merge Semi Join"};
+    default: return {"image":"ex_merge.png","image_text":String("Merge " + data['Join Type'] + " Join" )};
+  }
+},
+"ModifyTable" : function(data) {
+  switch (data['Operaton']) {
+    case "insert": return { "image":"ex_insert.png",
+                            "image_text":"Insert"
+                           };
+    case "update": return {"image":"ex_update.png","image_text":"Update"};
+    case "Delete": return {"image":"ex_delete.png","image_text":"Delete"};
+  }
+},
+'Nested Loop' : function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_nested_loop_anti_join.png","image_text":"Nested Loop Anti Join"};
+    case 'Semi': return {"image":"ex_nested_loop_semi_join.png","image_text":"Nested Loop Semi Join"};
+    default: return {"image":"ex_nested.png","image_text":"Nested Loop " + data['Join Type'] + " Join"};
+  }
+},
+"Recursive Union" : {"image":"ex_recursive_union.png","image_text":"Recursive Union"},
+"Result" : {"image":"ex_result.png","image_text":"Result"},
+"Sample Scan" : {"image":"ex_scan.png","image_text":"Sample Scan"},
+"Scan" : {"image":"ex_scan.png","image_text":"Scan"},
+"Seek" : {"image":"ex_seek.png","image_text":"Seek"},
+"SetOp" : function(data) {
+  var strategy = data['Strategy'],
+      command = data['Command'];
+
+  if(strategy == "Hashed") {
+    if(command.startsWith("Intersect")) {
+      if(command == "Intersect All")
+        return {"image":"ex_hash_setop_intersect_all.png","image_text":"Hashed Intersect All"};
+      return {"image":"ex_hash_setop_intersect.png","image_text":"Hashed Intersect"};
+    }
+    else if (command.startsWith("Except")) {
+      if(command == "Except All")
+        return {"image":"ex_hash_setop_except_all.png","image_text":"Hashed Except All"};
+      return {"image":"ex_hash_setop_except.png","image_text":"Hash Except"};
+    }
+    return {"image":"ex_hash_setop_unknown.png","image_text":"Hashed SetOp Unknown"};
+  }
+  return {"image":"ex_setop.png","image_text":"SetOp"};
+},
+"Seq Scan": function(data) {
+  return {"image":"ex_scan.png","image_text":data['Relation Name']};
+},
+"Subquery Scan" : {"image":"ex_subplan.png","image_text":"SubQuery Scan"},
+"Sort" : {"image":"ex_sort.png","image_text":"Sort"},
+"Tid Scan" : {"image":"ex_tid_scan.png","image_text":"Tid Scan"},
+"Unique" : {"image":"ex_unique.png","image_text":"Unique"},
+"Values Scan" : {"image":"ex_values_scan.png","image_text":"Values Scan"},
+"WindowAgg" : {"image":"ex_window_aggregate.png","image_text":"Window Aggregate"},
+"WorkTable Scan" : {"image":"ex_worktable_scan.png","image_text":"WorkTable Scan"},
+"Undefined" : {"image":"ex_unknown.png","image_text":"Undefined"},
+}
+
+// Some predefined constants used to calculate image location and its border
+var pWIDTH = pHEIGHT = 100.
+    IMAGE_WIDTH = IMAGE_HEIGHT = 50;
+var offsetX = 200,
+    offsetY = 60;
+var ARROW_WIDTH = 10,
+    ARROW_HEIGHT = 10,
+    DEFAULT_ARROW_SIZE = 2;
+var TXT_ALLIGN = 5,
+    TXT_SIZE = "15px";
+var TOTAL_WIDTH = undefined,
+    TOTAL_HEIGHT = undefined;
+var xMargin = 25,
+    yMargin = 25;
+var MIN_ZOOM_FACTOR = 0.01,
+    MAX_ZOOM_FACTOR = 2,
+    INIT_ZOOM_FACTOR = 1;
+    ZOOM_RATIO = 0.05;
+
+
+// Backbone model for each plan property of input JSON object
+var PlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plans": [],
+        level: [],
+        "image": undefined,
+        "image_text": undefined,
+        xpos: undefined,
+        ypos: undefined,
+        width: pWIDTH,
+        height: pHEIGHT
+    },
+    parse: function(data) {
+        var idx = 1,
+            lvl = data.level = data.level || [idx],
+            plans = [],
+            node_type = data['Node Type'],
+            // Calculating relative xpos of current node from top node
+            xpos = data.xpos = data.xpos - pWIDTH,
+            // Calculating relative ypos of current node from top node
+            ypos = data.ypos,
+            maxChildWidth = 0;
+
+        data['width'] = pWIDTH;
+        data['height'] = pHEIGHT;
+
+        /*
+         * calculating xpos, ypos, width and height if current node is a subplan
+         */
+        if (data['Parent Relationship'] === "SubPlan") {
+            data['width'] += (xMargin * 2) + (xMargin / 2);
+            data['height'] += (yMargin * 2);
+            data['ypos'] += yMargin;
+            xpos -= xMargin;
+            ypos += yMargin;
+        }
+
+        if(node_type.startsWith("(slice"))
+            node_type = node_type.substring(0,7);
+
+        // Get the image information for current node
+        var mapperObj = (_.isFunction(imageMapper[node_type]) &&
+                imageMapper[node_type].apply(undefined, [data])) ||
+                imageMapper[node_type] || 'Undefined';
+
+        data["image"] = mapperObj["image"];
+        data["image_text"] = mapperObj["image_text"];
+
+        // Start calculating xpos, ypos, width and height for child plans if any
+        if ('Plans' in data) {
+
+            data['width'] += offsetX;
+
+            _.each(data['Plans'], function(p) {
+                var level = _.clone(lvl),
+                    plan = new PlanModel();
+
+                level.push(idx);
+                plan.set(plan.parse(_.extend(
+                    p, {
+                        "level": level,
+                        xpos: xpos - offsetX,
+                        ypos: ypos
+                    })));
+
+                if (maxChildWidth < plan.get('width')) {
+                    maxChildWidth = plan.get('width');
+                }
+
+                var childHeight = plan.get('height');
+
+                if (idx !== 1) {
+                    data['height'] = data['height'] + childHeight + offsetY;
+                } else if (childHeight > data['height']) {
+                    data['height'] = childHeight;
+                }
+                ypos += childHeight + offsetY;
+
+                plans.push(plan);
+                idx++;
+            });
+        }
+
+        // Final Width and Height of current node
+        data['width'] += maxChildWidth;
+        data['Plans'] = plans;
+
+        return data;
+    },
+
+    /*
+     * Required to parse and include non-default params of
+     * plan into backbone model
+     */
+    toJSON: function(non_recursive) {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (non_recursive) {
+            delete res['Plans'];
+      } else {
+            var plans = [];
+            _.each(res['Plans'], function(p) {
+              plans.push(p.toJSON());
+            });
+            res['Plans'] = plans;
+      }
+      return res;
+    },
+
+    // Draw an arrow to parent node
+    drawPolyLine: function(g, startX, startY, endX, endY, opts, arrowOpts) {
+      // Calculate end point of first starting straight line (startx1, starty1)
+      // Calculate start point of 2nd straight line (endx1, endy1)
+      var midX1 = startX + ((endX - startX) / 3),
+          midX2 = startX + (2 * ((endX - startX) / 3));
+
+      //create arrow head
+      var arrow = g.polygon(
+                    [0, ARROW_HEIGHT,
+                    (ARROW_WIDTH / 2),ARROW_HEIGHT,
+                    (ARROW_HEIGHT / 4), 0,
+                    0, ARROW_WIDTH]
+                    ).transform("r90");
+      var marker = arrow.marker(
+                         0, 0, ARROW_WIDTH, ARROW_HEIGHT, 0, (ARROW_WIDTH / 2)
+                         ).attr(arrowOpts);
+
+      // First straight line
+      g.line(
+        startX, startY, midX1, startY
+        ).attr(opts);
+
+      // Diagonal line
+      g.line(
+        midX1-1, startY, midX2, endY
+        ).attr(opts);
+
+      // Last straight line
+      var line = g.line(
+                   midX2, endY, endX, endY
+                   ).attr(opts);
+      line.attr({markerEnd: marker})
+    },
+
+    // Draw image, its name and its tooltip
+    draw: function(s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer) {
+        var g = s.g();
+        var currentXpos = xpos + this.get('xpos') ,
+            currentYpos = ypos + this.get('ypos'),
+            isSubPlan = (this.get('Parent Relationship') === "SubPlan");
+
+        // Draw the subplan rectangle
+        if (isSubPlan) {
+          g.rect(
+            currentXpos - this.get('width') + pWIDTH + xMargin,
+            currentYpos - yMargin,
+            this.get('width') - xMargin,
+            this.get('height'), 5
+          ).attr({
+              stroke: '#444444',
+              'strokeWidth': 1.2,
+              fill: 'gray',
+              fillOpacity: 0.2
+          });
+
+          //provide subplan name
+          var text = g.text(
+            currentXpos  + pWIDTH - ( this.get('width') / 2) - xMargin,
+            currentYpos + pHEIGHT  - (this.get('height') / 2) - yMargin,
+            this.get('Subplan Name')
+          ).attr({
+            fontSize: TXT_SIZE, "text-anchor":"start",
+            fill: 'red'
+          });
+        }
+
+        // Draw the actual image for current node
+        var image = g.image(
+            pgExplain.prefix + this.get('image'),
+            currentXpos + (pWIDTH - IMAGE_WIDTH) / 2,
+            currentYpos + (pHEIGHT - IMAGE_HEIGHT) / 2,
+            IMAGE_WIDTH,
+            IMAGE_HEIGHT
+        );
+
+        // Draw tooltip
+        var image_data = this.toJSON();
+        image.mouseover(function(evt){
+
+          // Empty the tooltip content if it has any and add new data
+          toolTipContainer.empty();
+          var tooltip = $('<table></table>',{
+                           class: "pgadmin-tooltip-table"
+                        }).appendTo(toolTipContainer);
+          _.each(image_data, function(value,key) {
+            if(key !== 'image' && key !== 'Plans' &&
+               key !== 'level' && key !== 'image' &&
+               key !== 'image_text' && key !== 'xpos' &&
+               key !== 'ypos' && key !== 'width' &&
+               key !== 'height') {
+              tooltip.append( '<tr><td class="label explain-tooltip">' + key + '</td><td class="label explain-tooltip-val">' + value + '</td></tr>' );
+            };
+          });
+
+          var zoomFactor = graphContainer.data('zoom-factor');
+
+          // Calculate co-ordinates for tooltip
+          var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft());
+          var toolTipY = ((currentYpos + pHEIGHT) * zoomFactor - graphContainer.scrollTop());
+
+          // Recalculate x.y if tooltip is going out of screen
+          if(graphContainer.width() < (toolTipX + toolTipContainer[0].clientWidth))
+            toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH*zoomFactor));
+          //if(document.children[0].clientHeight < (toolTipY + toolTipContainer[0].clientHeight))
+          if(graphContainer.height() < (toolTipY + toolTipContainer[0].clientHeight))
+            toolTipY -= (toolTipContainer[0].clientHeight + ((pHEIGHT/2)*zoomFactor));
+
+          toolTipX = toolTipX < 0 ? 0 : (toolTipX);
+          toolTipY = toolTipY < 0 ? 0 : (toolTipY);
+
+          // Show toolTip at respective x,y coordinates
+          toolTipContainer.css({'opacity': '0.8'});
+          toolTipContainer.css('left', toolTipX);
+          toolTipContainer.css( 'top', toolTipY);
+        });
+
+        // Remove tooltip when mouse is out from node's area
+//        image.mouseout(function() {
+//          toolTipContainer.empty();
+//          toolTipContainer.css({'opacity': '0'});
+//          toolTipContainer.css('left', 0);
+//          toolTipContainer.css( 'top', 0);
+//        });
+
+        // Draw text below the node
+        var label = g.g();
+        g.multitext(
+          currentXpos + (pWIDTH / 2),
+          currentYpos + pHEIGHT - TXT_ALLIGN,
+          this.get('image_text'),
+          150,
+          {"font-size": TXT_SIZE ,"text-anchor":"middle"}
+        );
+
+        // Draw Arrow to parent only its not the first node
+        if (!_.isUndefined(pYpos)) {
+            var startx = currentXpos + pWIDTH;
+            var starty = currentYpos + (pHEIGHT / 2);
+            var endx = pXpos - ARROW_WIDTH;
+            var endy = pYpos + (pHEIGHT / 2);
+            var start_cost = this.get("Startup Cost"),
+                total_cost = this.get("Total Cost");
+            var arrow_size = DEFAULT_ARROW_SIZE;
+            // Calculate arrow width according to cost of a particular plan
+            if(start_cost != undefined && total_cost != undefined) {
+              var arrow_size = Math.round(Math.log((start_cost+total_cost)/2 + start_cost));
+              arrow_size = arrow_size < 1 ? 1 : arrow_size > 10 ? 10 : arrow_size;
+            }
+
+
+            var arrow_view_box = [0, 0, 2*ARROW_WIDTH, 2*ARROW_HEIGHT];
+            var opts = {stroke: "#000000", strokeWidth: arrow_size + 1},
+                subplanOpts = {stroke: "#866486", strokeWidth: arrow_size + 1},
+                arrowOpts = {viewBox: arrow_view_box.join(" ")};
+
+            // Draw an arrow from current node to its parent
+            this.drawPolyLine(
+              g, startx, starty, endx, endy,
+              isSubPlan ? subplanOpts : opts, arrowOpts
+            );
+        }
+
+        var plans = this.get('Plans');
+
+        // Draw nodes for current plan's children
+        _.each(plans, function(p) {
+            p.draw(s, xpos, ypos, currentXpos, currentYpos, graphContainer, toolTipContainer);
+        });
+    }
+});
+
+// Main backbone model to store JSON object
+var MainPlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plan": undefined,
+        xpos: 0,
+        ypos: 0,
+    },
+    initialize: function() {
+        this.set("Plan", new PlanModel());
+    },
+
+    // Parse the JSON data and fetch its children plans
+    parse: function(data) {
+        if (data && 'Plan' in data) {
+           var plan = this.get("Plan");
+           plan.set(
+             plan.parse(
+               _.extend(
+                 data['Plan'], {
+                   xpos: 0,
+                   ypos: 0
+                 })));
+
+           data['xpos'] = 0;
+           data['ypos'] = 0;
+           data['width'] = plan.get('width') + (xMargin * 2);
+           data['height'] = plan.get('height') + (yMargin * 2);
+
+           delete data['Plan'];
+        }
+
+      return data;
+    },
+    toJSON: function() {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (res.Plan) {
+        res.Plan = res.Plan.toJSON();
+      }
+
+      return res;
+    },
+    draw: function(s, xpos, ypos, graphContainer, toolTipContainer) {
+        var g = s.g();
+
+        //draw the border
+        g.rect(
+	        0, 0, this.get('width') - 10, this.get('height') - 10, 5
+	    ).attr({
+            stroke: '#FFEBCD', 'strokeWidth': 1.2,
+            fill: '#FFF8DC', fillOpacity: 0.5
+        });
+
+        //Fetch total width, height
+        TOTAL_WIDTH = this.get('width');
+        TOTAL_HEIGHT = this.get('height');
+        var plan = this.get('Plan');
+
+        //Draw explain graph
+        plan.draw(g, xpos, ypos, undefined, undefined, graphContainer, toolTipContainer);
+    }
+});
+
+// Parse and draw full graphical explain
+_.extend(
+    pgExplain, {
+        // Assumption container is a jQuery object
+        DrawJSONPlan: function(container, plan) {
+          var my_plans = [];
+          container.empty();
+          var curr_zoom_factor = 1.0;
+
+          var zoomArea =$('<div></div>', {
+                class: 'pg-explain-zoom-area btn-group',
+                role: 'group'
+                }).appendTo(container),
+              zoomInBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom in'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-search-plus'
+                  })),
+              zoomToNormal = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom to original'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-arrows-alt'
+                  }))
+              zoomOutBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom out'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>', {
+                    class: 'fa fa-search-minus'
+                  }));
+
+          // Main div to be drawn all images on
+          var planDiv = $('<div></div>',
+                           {class: "pgadmin-explain-container"}
+                         ).appendTo(container),
+              // Div to draw tool-tip on
+              toolTip = $('<div></div>',
+                           {id: "toolTip",
+                           class: "pgadmin-explain-tooltip"
+                           }
+                         ).appendTo(container);
+          toolTip.empty();
+          planDiv.data('zoom-factor', curr_zoom_factor);
+
+          var w = 0, h = 0,
+              x = xMargin, h = yMargin;
+
+          _.each(plan, function(p) {
+            var main_plan = new MainPlanModel();
+
+            // Parse JSON data to backbone model
+            main_plan.set(main_plan.parse(p));
+            w = main_plan.get('width');
+            h = main_plan.get('height');
+
+            var s = Snap(w, h),
+                $svg = $(s.node).detach();
+            planDiv.append($svg);
+
+            main_plan.draw(s, w - xMargin, yMargin, planDiv, toolTip);
+
+            var initPanelWidth = planDiv.width(),
+                initPanelHeight = planDiv.height();
+
+             /*
+              * Scale graph in case its width is bigger than panel width
+              * in which the graph is displayed
+              */
+            if(initPanelWidth < w) {
+              var width_ratio = initPanelWidth / w;
+
+              curr_zoom_factor = width_ratio;
+              curr_zoom_factor = curr_zoom_factor < MIN_ZOOM_FACTOR ? MIN_ZOOM_FACTOR : curr_zoom_factor;
+              curr_zoom_factor = curr_zoom_factor > INIT_ZOOM_FACTOR ? INIT_ZOOM_FACTOR : curr_zoom_factor;
+
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+            }
+
+            zoomInBtn.on('click', function(e){
+              curr_zoom_factor = ((curr_zoom_factor + ZOOM_RATIO) > MAX_ZOOM_FACTOR) ? MAX_ZOOM_FACTOR : (curr_zoom_factor + ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomInBtn.blur();
+            });
+
+            zoomOutBtn.on('click', function(e) {
+              curr_zoom_factor = ((curr_zoom_factor - ZOOM_RATIO) < MIN_ZOOM_FACTOR) ? MIN_ZOOM_FACTOR : (curr_zoom_factor - ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomOutBtn.blur();
+            });
+
+            zoomToNormal.on('click', function(e) {
+              curr_zoom_factor = INIT_ZOOM_FACTOR;
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomToNormal.blur();
+            });
+          });
+        }
+    });
+
+    return pgExplain;
+});
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index 4e08baf..c748900 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -217,3 +217,9 @@
   background: #5B9CEF;
   color: white;
 }
+
+.sql-editor-explain {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index 3e9bc5c..6d777d5 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -1,9 +1,9 @@
 define(
-  ['jquery', 'underscore', 'alertify', 'pgadmin', 'backbone', 'backgrid', 'codemirror',
-   'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection', 'codemirror/addon/selection/active-line',
-   'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator', 'backgrid.filter',
-   'bootstrap', 'pgadmin.browser', 'wcdocker'],
-  function($, _, alertify, pgAdmin, Backbone, Backgrid, CodeMirror) {
+  ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', 'pgadmin.misc.explain',
+   'backbone', 'backgrid', 'codemirror', 'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
+   'codemirror/addon/selection/active-line', 'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator',
+   'backgrid.filter', 'bootstrap', 'pgadmin.browser', 'wcdocker'],
+  function($, _, S, alertify, pgAdmin, pgExplain, Backbone, Backgrid, CodeMirror) {
 
     // Some scripts do export their object in the window only.
     // Generally the one, which do no have AMD support.
@@ -160,6 +160,12 @@ define(
         "click #btn-auto-rollback": "on_auto_rollback",
         "click #btn-clear-history": "on_clear_history",
         "click .noclose": 'do_not_close_menu',
+        "click #btn-explain": "on_explain",
+        "click #btn-explain-analyze": "on_explain_analyze",
+        "click #btn-explain-verbose": "on_explain_verbose",
+        "click #btn-explain-costs": "on_explain_costs",
+        "click #btn-explain-buffers": "on_explain_buffers",
+        "click #btn-explain-timing": "on_explain_timing",
         "change .limit": "on_limit_change"
       },
 
@@ -218,10 +224,53 @@ define(
             '</button>',
             '<ul class="dropdown-menu dropdown-menu">',
               '<li>',
+                '<a id="btn-explain" href="#">',
+                  '<span>{{ _('Explain') }}</span>',
+                '</a>',
+              '</li>',
+              '<li>',
+                '<a id="btn-explain-analyze" href="#">',
+                    '<span>{{ _('Explain analyze') }}</span>',
+                '</a>',
+              '</li>',
+              '<li class="divider"></li>',
+              '<li class="dropdown-submenu dropdown-submenu">',
+                '<a href="#">{{ _('Explain Options') }}</a>',
+                '<ul class="dropdown-menu">',
+                  '<li>',
+                    '<a id="btn-explain-verbose" href="#" class="noclose">',
+                      '<i class="explain-verbose fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Verbose') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-costs" href="#" class="noclose">',
+                      '<i class="explain-costs fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Costs') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-buffers" href="#" class="noclose">',
+                      '<i class="explain-buffers fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Buffers') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-timing" href="#" class="noclose">',
+                      '<i class="explain-timing fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Timing') }} </span>',
+                    '</a>',
+                  '</li>',
+                '</ul>',
+              '</li>',
+              '<li class="divider"></li>',
+              '<li>',
                 '<a id="btn-auto-commit" href="#" class="noclose">',
                     '<i class="auto-commit fa fa-check" aria-hidden="true"></i>',
                     '<span> {{ _('Auto Commit') }} </span>',
                 '</a>',
+              '</li>',
+              '<li>',
                 '<a id="btn-auto-rollback" href="#" class="noclose">',
                     '<i class="auto-rollback fa fa-check visibility-hidden" aria-hidden="true"></i>',
                     '<span> {{ _('Auto Rollback') }} </span>',
@@ -378,7 +427,7 @@ define(
           height:'100%',
           isCloseable: false,
           isPrivate: true,
-          content: '<div class="sql-editor-explian"></div>'
+          content: '<div class="sql-editor-explain"></div>'
         })
 
         var messages = new pgAdmin.Browser.Panel({
@@ -770,6 +819,79 @@ define(
             self.handler
         );
       },
+
+      // Callback function for explain button click.
+      on_explain: function() {
+        var self = this;
+
+        // Trigger the explain signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain analyze button click.
+      on_explain_analyze: function() {
+        var self = this;
+
+        // Trigger the explain analyze signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-analyze',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "verbose" button click
+      on_explain_verbose: function() {
+        var self = this;
+
+        // Trigger the explain "verbose" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-verbose',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "costs" button click
+      on_explain_costs: function() {
+        var self = this;
+
+        // Trigger the explain "costs" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-costs',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "buffers" button click
+      on_explain_buffers: function() {
+        var self = this;
+
+        // Trigger the explain "buffers" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-buffers',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "timing" button click
+      on_explain_timing: function() {
+        var self = this;
+
+        // Trigger the explain "timing" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-timing',
+            self,
+            self.handler
+        );
+      },
+
       do_not_close_menu: function(ev) {
         ev.stopPropagation();
       }
@@ -834,6 +956,12 @@ define(
           self.on('pgadmin-sqleditor:button:download', self._download, self);
           self.on('pgadmin-sqleditor:button:auto_rollback', self._auto_rollback, self);
           self.on('pgadmin-sqleditor:button:auto_commit', self._auto_commit, self);
+          self.on('pgadmin-sqleditor:button:explain', self._explain, self);
+          self.on('pgadmin-sqleditor:button:explain-analyze', self._explain_analyze, self);
+          self.on('pgadmin-sqleditor:button:explain-verbose', self._explain_verbose, self);
+          self.on('pgadmin-sqleditor:button:explain-costs', self._explain_costs, self);
+          self.on('pgadmin-sqleditor:button:explain-buffers', self._explain_buffers, self);
+          self.on('pgadmin-sqleditor:button:explain-timing', self._explain_timing, self);
 
           if (self.is_query_tool) {
             self.gridView.query_tool_obj.refresh();
@@ -1083,14 +1211,28 @@ define(
 
           // Show message in message and history tab in case of query tool
           self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time);
-          self.update_msg_history(true, "", false);
+          //self.update_msg_history(true, "", false);
           var message = 'Total query runtime: ' + self.total_time + '\n' + self.rows_affected + ' rows retrieved.';
           $('.sql-editor-message').text(message);
 
-          // Add the data to the collection and render the grid.
-          self.collection.add(data.result, {parse: true});
-          self.gridView.render_grid(self.collection, self.columns);
-          self.gridView.data_output_panel.focus();
+          /* Add the data to the collection and render the grid.
+           * In case of Explain draw the graph on explain panel
+           * and add json formatted data to collection and render.
+           */
+          var explain_data_array = [];
+          if('QUERY PLAN' in data.result[0] && _.isObject(data.result[0]['QUERY PLAN'])) {
+              var explain_data = {'QUERY PLAN' : JSON.stringify(data.result[0]['QUERY PLAN'], null, 2)};
+              explain_data_array.push(explain_data);
+              self.gridView.explain_panel.focus();
+              pgExplain.DrawJSONPlan($('.sql-editor-explain'), data.result[0]['QUERY PLAN']);
+              self.collection.add(explain_data_array, {parse: true});
+              self.gridView.render_grid(self.collection, self.columns);
+          }
+          else {
+            self.collection.add(data.result, {parse: true});
+            self.gridView.render_grid(self.collection, self.columns);
+            self.gridView.data_output_panel.focus();
+          }
 
           // Hide the loading icon
           self.trigger('pgadmin-sqleditor:loading-icon:hide');
@@ -1816,16 +1958,11 @@ define(
 
         // This function will fetch the sql query from the text box
         // and execute the query.
-        _execute: function () {
+        _execute: function (explain_prefix) {
           var self = this,
               sql = '',
               history_msg = '';
 
-          self.trigger(
-            'pgadmin-sqleditor:loading-icon:show',
-            '{{ _('Initializing the query execution!') }}'
-          );
-
           /* If code is selected in the code mirror then execute
            * the selected part else execute the complete code.
            */
@@ -1835,6 +1972,17 @@ define(
           else
             sql = self.gridView.query_tool_obj.getValue();
 
+          // If it is an empty query, do nothing.
+          if (sql.length <= 0) return;
+
+          self.trigger(
+            'pgadmin-sqleditor:loading-icon:show',
+            '{{ _('Initializing the query execution!') }}'
+          );
+
+          if (explain_prefix != undefined)
+            sql = explain_prefix + ' ' + sql;
+
           self.query_start_time = new Date();
           self.query = sql;
           self.rows_affected = 0;
@@ -2153,6 +2301,66 @@ define(
               alertify.alert('Auto Commit Error', msg);
             }
           });
+        },
+
+        // This function will
+        _explain: function() {
+          var self = this;
+          var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          // No need to check for buffers and timing option value in explain
+          var explain_query = 'EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE %s, COSTS %s, BUFFERS OFF, TIMING OFF) ';
+          explain_query = S(explain_query).sprintf(verbose, costs).value();
+          self._execute(explain_query);
+        },
+
+        // This function will
+        _explain_analyze: function() {
+          var self = this;var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var buffers = $('.explain-buffers').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var timing = $('.explain-timing').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          var explain_query = 'Explain (FORMAT JSON, ANALYZE ON, VERBOSE %s, COSTS %s, BUFFERS %s, TIMING %s) ';
+          explain_query = S(explain_query).sprintf(verbose, costs, buffers, timing).value();
+          self._execute(explain_query);
+        },
+
+        // This function will toggle "verbose" option in explain
+        _explain_verbose: function() {
+          if ($('.explain-verbose').hasClass('visibility-hidden') === true)
+            $('.explain-verbose').removeClass('visibility-hidden');
+          else {
+            $('.explain-verbose').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "costs" option in explain
+        _explain_costs: function() {
+          if ($('.explain-costs').hasClass('visibility-hidden') === true)
+            $('.explain-costs').removeClass('visibility-hidden');
+          else {
+            $('.explain-costs').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "buffers" option in explain
+        _explain_buffers: function() {
+          if ($('.explain-buffers').hasClass('visibility-hidden') === true)
+            $('.explain-buffers').removeClass('visibility-hidden');
+          else {
+            $('.explain-buffers').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "timing" option in explain
+        _explain_timing: function() {
+          if ($('.explain-timing').hasClass('visibility-hidden') === true)
+            $('.explain-timing').removeClass('visibility-hidden');
+          else {
+            $('.explain-timing').addClass('visibility-hidden');
+          }
         }
       }
     );


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  2016-05-09 15:19     ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
@ 2016-05-09 18:26       ` Sanket Mehta <[email protected]>
  2016-05-10 06:51         ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Sanket Mehta @ 2016-05-09 18:26 UTC (permalink / raw)
  To: Ashesh Vashi <[email protected]>; +Cc: pgadmin-hackers

Hi,

Please ignore previous patch as there was an error in it.

Error:
Tooltip was not getting disappear when user moves cursor out of image.

I have attached a proper patch with this mail.
Please consider it for testing.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Mon, May 9, 2016 at 8:49 PM, Sanket Mehta <[email protected]>
wrote:

> Hi,
>
> PFA revised patch according to Ashesh's comments.
> Please find my response inline.
>
> I am currently adding minimap feature in graphical explain.
> I will send a new patch for the same.
>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Mon, Apr 25, 2016 at 4:36 PM, Ashesh Vashi <
> [email protected]> wrote:
>
>> Hi Sanket,
>>
>> Please find the review comments.
>> - Please add the missing 'explain.css'.
>>
> Done
>
>> - The application should be smart enough to handle conflict in options.
>>    i.e.
>>    Buffer is not a valid options without EXPLAIN ANALYZE.
>>
> Done
>
>> - A statement having EXPLAIN keywords with different format should at
>> least render the output in the data-grid.
>>   i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
>>
> Done
>
>> - Please use the keywords used in the EXPLAIN statement in capital.
>>
> Done
>
>> - Explain should not work with empty string.
>>
> Done
>
>> - Font size in the tooltip is very small.
>>
> Done
>
>>
>>
> - Smoothing the zoom functionality.
>>
> Minimap will be added and zoom functionality will be removed. So it is
> ignored.
>
> - Arrow marker is hardly visible.
>>
> Done.
>
>>
>>
>> --
>>
>> Thanks & Regards,
>>
>> Ashesh Vashi
>> EnterpriseDB INDIA: Enterprise PostgreSQL Company
>> <http://www.enterprisedb.com;
>>
>>
>> *http://www.linkedin.com/in/asheshvashi*
>> <http://www.linkedin.com/in/asheshvashi;
>>
>> On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> This patch includes the patch sent earlier for stand alone graphical
>>> explain.
>>>
>>> And also "horizontal lines are not proper" bug is fixed in the same
>>> which was reported by Dave in previous patch.
>>>
>>> Regards,
>>> Sanket Mehta
>>> Sr Software engineer
>>> Enterprisedb
>>>
>>> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
>>> [email protected]> wrote:
>>>
>>>> Hi Team,
>>>>
>>>> PFA the first patch for graphical explain integrated in sql editor.
>>>>
>>>> Below are the few things which are different from previous patch which
>>>> was sent for stand alone graphical explain.
>>>>
>>>>  -  Now user can select Explain/Explain Analyze with four optional
>>>> properties (Verbose, costs, timing and buffers)
>>>>
>>>>  - Initially graph will be scale (according to only its width not
>>>> height) to fit to screen so no blank space will be there in case of very
>>>> large graph.
>>>>
>>>> - Along with zoom in/out button, "zoom to original" button is also
>>>> provided, by clicking on which graph will be scale to its original size
>>>> (not same as initial one which is according to screen size).
>>>>
>>>> Please do review this patch and let me know in case you have any
>>>> comments.
>>>>
>>>>
>>>> Regards,
>>>> Sanket Mehta
>>>> Sr Software engineer
>>>> Enterprisedb
>>>>
>>>
>>>
>>
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/x-patch] integrated_graphical_explainV4.patch (484.3K, 3-integrated_graphical_explainV4.patch)
  download | inline diff:
diff --git a/libraries.txt b/libraries.txt
index 9fcf755..ad1c004 100644
--- a/libraries.txt
+++ b/libraries.txt
@@ -27,3 +27,4 @@ backgrid-filter     01b2b21     MIT          https://github.com/wyuenho/backgrid
 backbone.paginator  2.0.3       MIT          http://github.com/backbone-paginator/backbone.paginator
 backgrid-paginator  03632df     MIT          https://github.com/wyuenho/backgrid-paginator
 backgrid-select-all 1a00053     MIT          https://github.com/wyuenho/backgrid-select-all
+Snap.svg            0.4.1       APACHE       http://snapsvg.io/
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index f461cbe..33c693e 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -6,28 +6,59 @@
 # This software is released under the PostgreSQL Licence
 #
 ##########################################################################
-
 """A blueprint module providing utility functions for the application."""
 
-import datetime
-from flask import session, current_app
+from flask import url_for, render_template
 from pgadmin.utils import PgAdminModule
 import pgadmin.utils.driver as driver
+import config
 
 MODULE_NAME = 'misc'
 
-# Initialise the module
-blueprint = PgAdminModule(MODULE_NAME, __name__,
-                          url_prefix='')
+class MiscModule(PgAdminModule):
 
-##########################################################################
-# A special URL used to "ping" the server
-##########################################################################
+    def get_own_javascripts(self):
+        scripts = [{
+                'name': 'pgadmin.misc.explain',
+                'path': url_for('misc.index') + 'explain/explain',
+                'preloaded': False
+                },{
+                'name': 'snap.svg',
+                'path': url_for(
+                    'misc.static', filename='explain/js/' + (
+                        'snap.svg' if  config.DEBUG else 'snap.svg-min'
+                        )),
+                'preloaded': False
+                 }]
+        return scripts
+
+    def get_own_stylesheets(self):
+        stylesheets = []
+        stylesheets.append(url_for('misc.static', filename='explain/css/explain.css'))
+        return stylesheets
 
+ # Initialise the module
+blueprint = MiscModule(MODULE_NAME, __name__, static_url_path="/static")
+
+ ##########################################################################
+ # A special URL used to "ping" the server
+ ##########################################################################
+
[email protected]("/")
+def index():
+    return ''
 
 @blueprint.route("/ping", methods=('get', 'post'))
 def ping():
-    """Generate a "PING" response to indicate that the server is alive."""
-    driver.ping()
+     driver.ping()
+
+def demo():
+    return render_template('demo_explain.html')
+
[email protected]("/explain/explain.js")
+def explain_js():
+    return render_template("explain/js/explain.js")
 
-    return "PING"
[email protected]("/sample")
+def sample():
+    return render_template('sample.html')
diff --git a/web/pgadmin/misc/static/explain/css/explain.css b/web/pgadmin/misc/static/explain/css/explain.css
new file mode 100644
index 0000000..49a4bc4
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/css/explain.css
@@ -0,0 +1,56 @@
+.pg-explain-zoom-area {
+    position: absolute;
+    top: 5px;
+    left: 5px;
+    opacity: 0.5;
+    cursor: pointer;
+}
+
+.pg-explain-zoom-btn {
+    top: 5px;
+    min-width: 25px;
+    cursor: pointer;
+    border: 1px solid transparent;
+}
+
+.pg-explain-zoom-area:hover {
+   opacity: 1;
+}
+
+.explain-tooltip {
+  display: table-cell;
+  text-align: left;
+  white-space: nowrap;
+  line-height: 10px !important;
+  padding: 2px !important;
+  font-size: small;
+}
+
+td.explain-tooltip-val {
+  display: table-cell;
+  text-align: left;
+  white-space: pre-wrap;
+  font-size: smaller;
+}
+
+.pgadmin-explain-tooltip {
+  position: absolute;
+  padding:5px;
+  border: 1px solid white;
+  opacity:0;
+  color: cornsilk;
+  background-color: #010125;
+}
+
+.pgadmin-tooltip-table {
+  border-collapse: collapse;
+  border-spacing: 1px;
+  top: auto;
+  left: auto;
+}
+
+.pgadmin-explain-container {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
\ No newline at end of file
diff --git a/web/pgadmin/misc/static/explain/img/ex_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bfe75d909f5092c4d3ba8978c75fbc57d7f606d
GIT binary patch
literal 574
zcmV-E0>S->P)<h;3K|Lk000e1NJLTq001%o001%w1^@s69zTe&00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10lP^=K~!jg?U_w#!!Qs=pNrR76nc`JAiEyfyPUu$F?5lg!9`tE
zrVdorjI6O#*B=N1Q4~GTk7uODImZ$7QhEcqbb{2T!+^AsNlnvqz}0v!OZCpVcg+u)
zSl03oH-ylcGy!)Fj09u=UN>$mMIX+&H|b<ajP!gzp{f<N2t38e1(}OYz(X)^Z9SDm
zaL$Pb&;cXx85twc3D+9}YYwWtX(n61tgLAZVhA&A0ZDox`m}f_o&;Lp=3^|TZAm4?
zB8D-uw2Hk&77xL~GD+H8Yh{L+-D~onRU64N$mC{z9Z`Z<4$%uyDn(tUuBBqiTE>@*
zne6>YDVVIT^|Y|u&2%+YKxQ4H!ZKN8+Uk0kwJKPjW&<(>@$PjAe4RCOnSn%Nr0(=P
zYi|fJ04V_hnL$cH0K3&%;sz_V=BgQD)cm$)2vvhs6@*_isdrBfc8kD{yg=7gktITF
z+PGFu2!0OeLWgu>5LFp3D9xourL!bQu%a?wd{rRqFIvi++{=Q!&>aaV%KTa{dS;2c
z$5o3IhEOTyT37x61pK30-JX4KbAS7Pk<5;R_SRus>jbGyCrEAj0!#X?PWurOy8r+H
M07*qoM6N<$f>VU-$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_append.png b/web/pgadmin/misc/static/explain/img/ex_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..017a2068b735f13cb89a3bcb2832e5880d17df56
GIT binary patch
literal 1162
zcmV;51a<p~P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(LcQrnzUg(&?|IPgRl@0p)bM7=>}tvEZ_4YI+3|bM?Sj$mrrz?7)b5hj
z@2}zW1*hRn!RZ^W<6Fh+V#n(^x8+d2=WEC4a+aaNk2du1$nfm9@9MSh>9q3g#qH&>
z?c}iR<FN7S!|>?B>));G-mL1~tMBB$>*k;D>$d6On&{t|?&h=S+m`0ol<eWI<I<1f
z(2eQZsp{jL=-rs*){^1Oi|N^?<kgYn(~#oMjOf><@94Ab;k?JHb@1lE=hUR;)1&O&
zx!}r)-o=I9!-U<zg67eo-^hpM&!OhgqTIiL<j$Y(=D_CCqwCqW<jtPr%%0=Rp4+{D
z+PZy)sbo@(8Kui)sL*Ju)oiZXakAicwdHz}mC<Hv(aX&7yuR$dzwOS>^3l=q!NTst
z#P8G8^VQY#$jI=?$??j|@!8t--ro1u*Y)Ay_q@IAr>W+ss^`DI?yaxsud(UH#qY<+
z@3pq;x47%Lx$M-`^vB2WiI(>{00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0zpYcK~zY`?U3tJ5>Xh&?SzGeqJ$LLD=fGO%SB*ek((>YO)i2W3vOk#CSYi4
znRfSIZ+9mfuyZIJXZldjhj-?kd4K0Q&oeUeU#e~(MZ}3i&`phI^cK3U)oRDk*qyt&
zaWp=m*H5C!DTCo2!Xg@@(Kw2xO(xTQ^uTO3>(InQtyYhwW@dB-G{|N8&s`p=e<n^G
z0<#v2W%goPDar;m`yB0nd8dnU0~WD(JRXZWy+HYV3x2Q%f<YFXp-`9`j6@<<8Ch7g
z109S;oxp^{vG^dw8;L|H@Gk}eG_cV`k^ych7US{Aj}#QOtfZjT6pWn09q0KJ7I_Sc
zh!gMP^;&1a=J(qj;9yzj3b;8go`O_5oyp=qCa3T%U!+JRLvo5(EXPASzgj5bk-lP+
zO0n@=u9Sw%YN1djA(xBgOQn1U)(VwM6_42b_BpvF*6CW8Q^eI2nT;%D%hhV_+8T4v
zEISV?48yr0#q(+T{k3Ab2DQz)fOi2p8cn$56iaG~e0~Fpl}e)u^=7jv;1M>FxKwPp
z(r9dgKt_How%TaO#{-achU4Ux__UIuSXNThl@v8W5U#DUt7}W#B5ow&NzYaPMkJm-
z`+3#B5v1H~KNqnZ(M8-Adt7=qvQOWu;_p1vqZcA^BYthzl84euNfB}45NYSt?ruwJ
zclP#POWpm0H;2+;tKB}5j=J6Bw-Oe4cXIOnRO+0aefTJS`uyeVj?_BsblTFl^ZkS4
zzljW=<qD1clll(R-{b2g;$(6F001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@z
zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUh
cIxsdXFf}?bFv^!ztN;K207*qoM6N<$g1#AbUjP6A

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_and.png b/web/pgadmin/misc/static/explain/img/ex_bmp_and.png
new file mode 100644
index 0000000000000000000000000000000000000000..64d5869dcfcf9d94d031fa068cff93ea929180b2
GIT binary patch
literal 1006
zcmV<K0}=d*P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004)P)t-smD2kF
z003rd(R6flb#--ic6N7ncY1nyHi*G{dwYC*e13j@fPjF4f`WsCgM@^HhlhuVh=_}e
zi;azqj*gCGmC%omkC2d%k&%&&j=z$UlEIHQm6er`lHGcqshOFXoSdAVo}QbW#Gjv^
zrlzK+r>D86a;T`NPQK>5s&lKWtGldpyRCMut*u(Y=dP}<ys&q#udltad8@4GzOs6W
zx!ba`vSh{Rx3{;mwCZuo?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?Zd;v-NJ*w!S2S!
z#*5VM-^PaD$cK{E@5aaP;L3^0%F2}3@Z-y#<IJAq&7R`WjpWXs%+2x5&hq8Zq2|$|
z)6>(Y-ty(vljhW<)z#JK)upZ9^VHPy=+~y`*Qe^(w&~ia>DsB<+S=*ds_NXT>)W{8
z+}z#W-Rs`0?A^KE-rnrst?b~g?cclJ-uCR`ui@e0?BlTF;^Ob*zvboS@aDkq=fUUa
z=jrL`@$1Cu>gw?D@bU5S^78WY^Yiuf_4fAm`1ttw`T70*{r~^}C9&%N00001bW%=J
z06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0q;pfK~zY`?UY$l6G0S(b8}-Di3kcP
z+Oo)^h$4bwltoqvL?DD{Mjf_nBqD~O2?A~ymixDzo*9a*Gm})MC?7Zv{c`K8Gv{>Q
zvDq$ko~o9(s?QF(wL!N4k*52GJqwax@WJEtiUu~Rr}eShD?&VOcaK*pj!Vugb=sg#
zfY{%@e)DeKes;iqrqk&yCPD_D!r-C^Nk4<GWMRlx_z0E==`xVDE_fnFj<OJC>PwuJ
zQ#!=9lF84TBBa*NRjVOi9LP1IA^nW2-@@fKw*1L<;8opaGaip`h_l>+MlB1G6SG9S
z=+u$;BX}4UBk&Ro<O=>E`h+(O1ZE*><<f@Dv{HsEI<ou#?nSH``|ZN(SURV-%r>Ht
zNXoo1qP+&hYdgl><kJ{stMHBkrzooyKsJ^Ng_cqlS=#YAjp3zO3@bPinuH0(v@xPG
z-th~(T!n2MS(<=x#ngpg%P%#>ef9pot6A9mcrN5H3(-xi$?R{pAeQfQB&8_Is%c|H
zG5v{QDc(Jx{2HTgO)iJ4;r-tV>{PR?%CelW*cUn`^~2;*7z$c$rLkbz!Q>%$6)bF#
zgDMhW1^r<X!9XBkv6Uy4*H)f(3HbeC^EY-H@%KXSjQLiI5MN;~GdYo*S;9V_FI=R?
cF7zMiA0?@qIju>9W&i*H07*qoM6N<$f<qfLO8@`>

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png b/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png
new file mode 100644
index 0000000000000000000000000000000000000000..2657d8c39328bfc80751c60db9d3ab4341c0de35
GIT binary patch
literal 1106
zcmV-Y1g-mtP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s00000
z003rd(Nc{WHi*Gol_FY@Wn`8)38~@|tKwvp&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPGu5^~NHbTAWN5AP#zUE86=}y7vTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q^_*?AyJGyuIvu(eH!N?S#?pz`^c>
z)9|d;ZHv_Ik=5>!)$gv_amL5*l-Tf>+3~XAcCFUw%+2wj+wtexE9lxP&Cc@b+bZna
zD(>4V@7yZ!+$!?iD)ihcx!dfw=Y00vDy`r1`Q0h{-6_=6^RD3Y``#)1-YNdxDZbwA
zx$A$x-|oWT?z`@S?c-qY<6p($@9^Ybyzqqb<X`jTU&rI{$m8(y<zMvWU&`a~_2pmp
z<zM*aU(4k2-rn~3=3n~eU(Mz6{N`W&=3mk0^3&+^^6H1w>GRa-^z`e8*6H;2>xcO3
zhu7-$`s|1O?1ujAhT-q`mEcQQ00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94
zoEQKA0&7V`K~zY`?Ud_N(?Ar(NfQ+@uBZsUQR*A(3k7^uiA_xlwb7<XX$T2Wl46zw
z3Z<omf1TZggd}Y;GR`=D;C$H3?B=&;&b@cGr{_ffM12%iU&sC(Yu~pzN7RRBuO_}z
z9SHSg=<4;C)SHp}k3Ldg#wI3zy8lp*uid=+whJ=U+k5r~HGFAc;3+jWJaTW6`U{t^
zEElbP-|8I2KEHnd^>^&(-vmu3&<E7Y<#G+wL{Y4Rx+F<;;36;16PJH@5PE-~#z=W;
ziYWt;VNw#1SeBJN2=S2cA-lUa!Z3^o#8j#a5_H+w@gP!)Wi2G_L4sKv#G9fn%W}g-
z`eRt9$y)*BIl%I*K9?(GoOF@xZQI4ZUy^0CDl3(yC(CW(0vV5!j_A!z8h-}~mT*$6
za2%)l5JX@-7#$sd_le%vpcj!ymO#PfbULko3dd#C2p5DnE;2^GRe;K6G8zcaf)Jd=
zPSH>*C`D7%v}T}UXUKCdvcCQ&74!AQMsP2b2D)EWk&C8PTM^wqM7$}qY%Zrq%-GtR
zg(zuUST%!@YA%<95iYB%7Gn28&1ADxp!<=IEX&Il;!V+l5Vj&_Y-#D(Gq$jB=z@FE
z)OFo<X@_?I6h_E^1__=pLT79oBQrC<=I0lWMDj$zh?ucbsf$a&>(Y@0AyyV$hfa#N
zVHoX*8Jn9s_Kewxb3cRzb}`Mpu<oOWZ+aR(egNk4c?ck1K*3ExD4Jn2MsLj~le1g2
zh42s<1wlYavFJkrE~R9$WV`UZ0SrM9%pelTlE5>D9%WyOJ=2@Tu2_G^GagZ~6a9ZW
Y0Mz-{B6#`Cr~m)}07*qoM6N<$f){{JGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_index.png b/web/pgadmin/misc/static/explain/img/ex_bmp_index.png
new file mode 100644
index 0000000000000000000000000000000000000000..23b9733b56792533e4db25ca9890a0a0140c6ff9
GIT binary patch
literal 1172
zcmV;F1Z(?=P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004@P)t-s000{R
z003rd(Nc{WHi*Gol_FY@Wn`8)45{LDla^$a&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPPu{MmZbjzYxL%irozv)iC=1#%sTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q|V#etORA+r5d~zJR>F?0eDg-NAx`
z(d~rM?%l(Kz`^c>)9|d;ZQjL&i`4Gl$A^*C?%>FXlGX37+HuCm@8HUbl-Tg$%!-%U
z@#4;m<IJA2;C8Ln>CDaX<<6kv(T?ZZE6vXG=Fy>}-SXtrk>=B+^4u!t)TFuF?6>E9
zt>5$1)bp<3^XA!==+~zI-YMzWr@r3px$A%D+?T-L?&;d7!r<=c-k9s!x9Z%g?c-qY
z<6p($@9N#Gyzqqc<X`FGo9x}X$K&wG<M7Jk@a*8N%jEIi-uC(CU(Mz6?BlTi=3nmO
zz3t_((dY8+=Caf1^Y7)q^6H1w>GRa-^w#P0@aMtr>9p7C_3`P${_KYG?Z)Bn_g>Mo
zbpQYW0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_00N9jL_t(Y$L*BsQxZ`a
zhh26nvhB^vO1oLo-JmFv5|N5XBA|_nRxB8D7XcMR7F@v#0{+$QVK1_a?Bq1l^uhPT
z*_pHZ%x|7^ezOAuC-YBckU%rovwx?vFI&Y|#D{0EroRzA2=QcObo3?hX8iu6kHpu>
z>6stWH^k%XH}AeZ0vXY2wKs^dOT)uYiOI3?do#per1Wz++u7&Wi~K6S(tLjX{>v}T
z;kSB{)N>E0r_<>=v>S~^8`><(wn0K(oX0MI??T9f0}>%=uh*M~Mn0c!0Gmiet6d28
z5R)PM`wD~wHX4nVRZ@0$Wk@2yLNyu+bs<U@5fNNE7R_?GyeA<;8Z@WzTMFbFpyAi&
z{3=ViitBI*+1Zh$RI5#B7K_EbE|=Tb1ze}Y#UZ!0Nc6mdd9k!$QS|wISsB6+XdX;V
zOuhR=Zf=sx+~8h})31g?q2dvUJcUEVlnj&w#N$ape-{qnT{4-vU{TAaQZ>biox#sZ
z$i~K>oS5uhcm(RXT&@m##cZ|)wNxxtQMr8q$pr#|oL~`iI2^P}$JW;+qy$+HkJ#<B
zO3K=rgfNiu%+AIjVz=9ZDji#0?I5jEsnmj63|UlYnl7kxY-Q!x9a~xwQW&NMl}LoH
zKp`0P7y91*DTMI1AI2EY!p2zyEfzD?w_{TXnV<i;jEAu>1GLR%4T9fnRv_|@#p7Km
zwAE@sh{;5$bc{nfE(~(vEeaGxB~?1MxOnW2@ran>r>FuX-EMcX-|cfhzPUN{^8+Rv
z=Ja_Bx6xp5_UjnAz2I^!Y?C5FnM@`(xD9edkrH>g;)f}e$!P3B6fSzyF}>u%TO^%M
mr}D&xdVb?7Cw4Ob-~0w&gyX$CR{j?N0000<MNUMnLSTYde~VB6

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_or.png b/web/pgadmin/misc/static/explain/img/ex_bmp_or.png
new file mode 100644
index 0000000000000000000000000000000000000000..c22fc31eee32e53abf634278f9d57751181f15c0
GIT binary patch
literal 685
zcmV;e0#f~nP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700030P)t-s00000
z003B6SY~U{Hi*GwmC%ijzrl|-kCNSbo~fFenwy-&xu<eYzUI5CbGxi`yRCLw!RNfN
zcfGNBtE}k0vU-WR+hoP)v$X2Ey1H@8?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?cKtI
zz`^c|)b8KLhTq7ClGX3V$M4|EiImvz<IA4o%%0@Up5oAr<j$YW&GF67^3l=J<<Oz#
z(W0i_^5xc(=G3I;)upZ9^VHPy=+~y`*Qe^(w&~ia>DsC3+^Xu_s_Wah>)x#F-MQ@G
zt?b~g?cclJ-uCR`uk7Qn@8rMa<mB+?!0_k6@$1Cw?CkIF@AUNa|NsAqxAFG?0004W
zQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkPM@d9MR7l6|)NN0KU=#*mj$o+0
zXGv;SRwxw`6%>^SK@x>8z5V~+lCW40^I>$hY<=+l#CCS=?A!_rGtXZp&xOfP4=T~1
zLLuDg&VhK#Q3h9{B+&*8S6f~eBpML~p(b&^vn6@U$0T2m#b{8Z5cd4&_~MEE7O~-9
zf*=_4G_tn|`*$&UE0u;Z3AUi@Ws_kpcNvpsxCSJ7EW-w!ByJ(e*z+DnG*V#06sAdo
z57R(x899zKpx?3pi_}}3HCVOj1h#=r;0$csmirZ0vT%(JY|HXz-k5KiT_1Ogc>-+%
z*I2g=Ed#gZrj<t0Z!rv`Kl8@=x~{vp_eDR1riLU<*hLa;LR4I1uBNJPc4P0=>MO1>
z^3%t=s-pBVfBn$JPrOoxdMEQgMkXTi54I4blS&e|kfbNeaxb$nGU<)Y^N;cg@O4qi
TF88uo00000NkvXXu0mjfLbGwY

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png b/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..e99f57478842f61cf0582433b659d37f0c532d5c
GIT binary patch
literal 334
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_@3?!2S6O@1yXMj(LE06{PXIHn!e64$cEXI-`
zzhDN3XE)M-9L@rd$YLPv0mg18v+aP48c!F;5RLO&CtC9zP~dS+JaMn}cL58V&ewnH
znS#1o7niJ>^>*TX*`QzgMdD7POzPaTo+w<9uwpLL)1F%W#!IqdhoMSxhwp~0@cf02
zhkMjmROfZc`EZ)w`S;|i<*e@Qr8gQ^@9R11|6Rc8|AQ9IRLjSmUOFeWtM)%%ZozPh
zRpl$&!*g?h?ocgpjVMV;EJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0
rRpn4L<mRVjrd2{T7+8WefK*!<m_an0njX3asDZ)L)z4*}Q$iB}@40B!

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_cte_scan.png b/web/pgadmin/misc/static/explain/img/ex_cte_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e1d779372f7fd212d2096e7701b0f1af05a5297
GIT binary patch
literal 1955
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_delete.png b/web/pgadmin/misc/static/explain/img/ex_delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca051cd5d01ee2f3ac17779b362999d82a9285b5
GIT binary patch
literal 1129
zcmV-v1eW`WP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003^P)t-sKLD^>
zTU+Qs2lh=8Hi*I5b0^MyED5RN(19;<mZ6f8lEIHQ*N;guwdHlD$Lf$-LA>b8qFCae
zX-dH9$EtPXqi$Wq>SM<0Ysctl$n0**>~+oT+Pi++y?@)iiQK+`+`oW((eK>Bf`rlT
z-NS=~)9~KJgx<x4-o}QE)b8KMhu_GEk=5?t$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc
z&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+mlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#
zr{>z0>DZ^~*{J8-m+9K6>e;sG+p6o@x9HxP>fNgB+_>rBn(N-I?A^NS->vN4y6oVs
z>f@a4-@EMLuI=Ev>g1j5;;-%DyzJw!?d7rW<i73ZvhL=y@8!Vo=D_dhv+wD(@aV$u
z>B8{rw(;x4^6ka*?Z)%)$Yy(c-~a#s0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkV
znw%H_00Om1L_t(Y$L*8bdl^v_#f8bG-PLL%nhMc~q#`mQ2qBD7)FomPgc62MQMdg6
zk3DnFFeXNthkjrCko~Zp{O0Vn_M*|e%s-g7lsHRO4WY~igJFl-+ccRDsDn>t^C@+@
zx4(Z*ogcZ~Uny0@u)e--pf-`3sE>NR-c0Qve4jen-Zs0bzx2v0yz^33L*QXJ96n67
z-|vrMEC|8~Mm*0Cfcyr)IFAhP?`wTY`?t5ZJrIhyTpRdgGF}BjOnw=S3Z#aS8bOKz
z2)SI2hh{Q9MZQla-3-V`zlMK|wO1@6q@DYg<e?sq#VQh#$r!5>kiFpjXD)tV7tH|-
zs+Ce#@?tf9p@%c(J&3b~b$3yv?^<i0Wd?NOY>az?FosdP&5vMEuU5Oz<v6Z0{jh=o
zomQ*WZOAT~qKnHCa6!L6FYiCcNQh7@l3<f{sKzcYIN<z#Z+<Q&%wTg<1P9CGU@I5F
zAO{7nm(GGl$rxFfd0BMP$!D`Im_IsL#iN4e&4xICNNma=3fVzdXx0akb}J=xi%ub*
z??5Y^PBlQ;M@VO0uV|$Gl`d%HB7Jh~V&PGT0ag%FkH=9lh;qT8zW{lfrqhr*b~zs-
z5-3Jml9U9SrB6;$kZ?Mkj5>n$=YC2mx@aL6sYC-BEW?yl5z?^_b4}Y?mYDns#}ztj
z_&TfPMvVl^-a$DU3E!wMhWB5?9atqzKrRj$h&b%_kUD~kp+H#Yp(6@myWO?~!I3Hu
z=Fs5?L&$Ek1=NvFHk)oKyp$u6@HJeA0)gu%65GN}d$-s()mK`n1iAvzJBEpt;U*Lc
zp;rPv-<A4eq!>gQ2X<i-UHB{(%b7ZoaEZkr8pXlNh({e^(G{YSM(iRIu-Pm=I9ra~
vF;TQX(RmkF9*^hhiNtoF^RIlF|7m^$H0#Fus?kFU00000NkvXXu0mjfhwW63

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png b/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..acba49c4fbfd9b871444c9e8d528deda3a37dda4
GIT binary patch
literal 1607
zcmV-N2Dtf&P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70008nP)t-sG*MJ9
zGc__jJu5&&G(JByKR_-+M?XVFIz>f0Mn*qMOEFGSGEh=NO-@KmPBl_hNli~gPftZq
zP)koyMp05tQdB!wTT50~PFY$|TU$Y2VNhILLSSN1U0qaQU`J$UR$*dSVq;iiWJzag
zS!88eWoBDuXIy7!VrOYyX=+bxa9?U`N^o*kZE<F6ZD(t4VQp?xadcvCZ%%Y}V{dR|
zaB*dEa$s|EW^!|FadcC8dv0=dXmoXNb9QNUb!&Hbba;Drd3JAmdn|~-V1I&kdwy7h
zihX~4b%B9*gM(g*ka&cIdWMF3hlhNKh<J;Neu|2OiHrcK<9Cjd0IB7GjE#bhkBE(s
z0IuVSj**0rkpQseg^`kdl$eK;lx>#JkCT>(m6nQ@mx7s{gPNXfp{9hJpNE^8j+&cS
zs;iQopNycNF}3BKo|}xLs5H0djijkIxapRrteK{!prxpqr>9E1=S;omo~x`)zUio{
zr$EB&TEON}!tH~&y<5TOt*@+H!sxEBt+2ALpSHV;x!GUE>anx0VaM!Z$nIpx@3ptI
zYslzq$?2uN!EMUwZp-VczrLuz#c<8-qr=8>&hDkb=5)~Uea`TO(Cop(zlPE6!o<Oc
z((b^;$gIrIiq!DM$Ha@(@y5u-kk#z2&(g=q#*x<T#mdc+*Y2><)Rfrov(wg=+VQm1
z*R|Eys@Lnz(9E{h+0W6=x7XUx($Bcq+rrn@r`_<<)X}Kk@w(gI)78?t+v~gB-@M)6
ztKsy$-r>aD;l<tJzTWQG+Sb3{@5bNbz~J%6;N-#K^2p)l#^UYC;^)WX?#kon$mH+O
z<m1WZ@Xh7w-{IcS<>b-k<<aHw(&y&V=JMg>;M3^m)93Ws=jYVu_1oy_<mThm>Gs^|
z>gDI;-RkSs>-XO4?BDF|;O*|=?(gdC>EH17@A2;O^YHZd^Y{7o`uzF)|NH*``~qhH
zPXGV_0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_00Rd}L_t(Y$L*BaPZLoT
z#&O?J6r$p;DDH~8qEXZl6<5H-4YlIFiyMf_bypH`m%5;WOI&KDg)~)+G`53GhY*HR
zXIO`XSSW}nIe&q()Y=)^2NGj^;5?jpxxf3}@60*J%c~{+bzH>w@R;2p*zMT3xMb{Z
zLc-Zc*we(s>u<2P_wJ{>$3CPzfB6Y>i^N7oM#f_&*G5HM!V>o#Jahv~jz4ue1xrgx
zy80UXODemCH+#&Ivr2_R$$d~A%d&Msu0^%ZY)5vhm9`@ZqNuM|@Ca+MSXji8B<Y$N
zWF-g_EkmlSJtKq_(I!(9q^jySLP`;aAP8DOEbbARixS=iP1hhBO>^q*kaWa{$1FuE
zD+QHW7Zp3hFq{fa(>7$IC`zv4K-gX3n^yE+e0X(qc;6{gFYFB*dh<Zwq~qI<?Tk^A
zGNhtHM0x`kk3+cB@FaBU7C13&IKY_EmtlOWC!`y|hC%a30n8ou4A#tOEDH$*i0JjD
zv8zgsl$MHK`~dch4GRDWU$_}Q&v^ws&o}%y*BKrTc_;T$Tr5_s4gau=E&wy{OoX4_
z>mc~snJeFC!=lMeDkdU=N{IJJe}E%PA|S&z2tGpaJjepSTV%ORrKm{s(gPqG(E|aN
zFL(j7Ms)lN(4jj(+4NMM+)H7hfG`69)+1V|*n^;zF}_D3!@o}!?BAx-5i+EpKt$%W
zY8!y)9qn3oYCmD)HQh3weuKBJ8s-z0nwjYVsp0;$1+iRqHYY*g4A-1E!MH`5)nobj
zP8AzsDazUW<=niK2qPd@$;%TEel93P>GgUNk$OE>B~r(`AT*kET!xs<4#a4n_`^J+
z({TuqDIn?To)C+{U?63PMnfRnGs04&TNhRw$E`9XH@Ddp6A_n1QI1N4`x)VWPDQ#d
zxW$*9%`ZO}7lgzGu2!pzNHWGbT5S$D%6|~Cuo|34)HpJ!R8mB#Fl)7DQH8G(DHc}a
zxL<8TMmfSUT*D|GUgR$LrDri3jk49}SmVv9SWEn0@e4;h=nHVhTAcs@002ovPDHLk
FV1m**ONRge

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_gather_motion.png b/web/pgadmin/misc/static/explain/img/ex_gather_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_group.png b/web/pgadmin/misc/static/explain/img/ex_group.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d5de31bc129a9abcf4a73e19a48739139f66271
GIT binary patch
literal 1228
zcmV;-1T*`IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=(EA5f2CVejTwfiWOA0Fgt11U#9qOVHuUhw@a(tm>9z9h#qH&>
z?c}iR<FN7S!|>?B?BK2I-mL1~tMBB$bEG!w=AY@{n&{q`?&h=R*_GtekmAse>DsC4
z<DBHxlHtsY>Di~^&Wz~SrlQAS@94Ab;k>5IWyh*@@aDkh)THLqqwL+ecBwj^wTIx!
ziQdJ9-ou35!Gh+|q2I`d<<Fq3)@|IrfaJ}di@s3r=D_ROwzcJZ<IJAp%bvUMgW9`&
zd$dE9%UpG!OlO!lj)kjcYthWj@w~q5zQ66y&+^gH^1{OI!^Q8@)brKV^vKBY$;t7`
z%kkOT_1@n1*Vpyo;rG0~?5C;bsH*3`zwW@o?yaxsud(UH#qY<+@3pq;x47%Lx$Mi#
z@y^cjz`^d+)bz*4@RF6$yGLoX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0)t6JK~zY`?UZR(+CUVB(*d`NQfXa6^~z!}1Oy^sF^V-vQ6PniB6gWpu~n<K
z*4lmj>zxTpNrZFE={Y_9;C#4z;o;8n<_!#Ueg5^if#~TvJ0ZwowOV^nZ(o1^02;Vy
zu?(WYUYl(Q4Gr7vw@^pKIx;e1MSTv3<2Jf8Iy!1WJp{L*;juA`9sTu|@d=i7GIzB*
zcbx`ea%zfYVA|z!O*g08?e>7jJHz>4miG$*&>@5fk}cqxo11erraKf0N5CtJA`kxg
zXjIZ8>{QT6K3YgD<|aru>;+DY$NeBgqlu&uVVO20Mv+K3EONld<MRSY3yFkmKo;vr
z%VLRIaMZ#t5Q{)kmQw~~SwrptJzZxz;S5eXn_Jw|;E6=MKJf88zXAbCN~R#CDCt$u
z5zCqTb(dHyv_vdKafVpTQ;S3*D}%DOp38&oS{ZE#LxT<JgH2|$1qIUUxtyxol6lbB
z@+Vln5v*J&tfe8J%N0vHeP`xj-Nh5}&d_Jg`!|S1QnN@Ci&a%El?{n)noX>UfRE$&
zSy&MSDF7*1R><$?^J*E&rLBr_%XS^%;`MqgXclXlMUhx+RjbvC5!ul?)~H8Yuxhbb
zEkWfnGVy48hc@|gu%#mXatCCbFywBqAxIKQNmeuqm0E03i*lu6bQyolvg{M)DW<3E
zG`wPq%cPn3+4J4qJzU%0fAJC@93H(o#>acL+6g{6I5>Tc4doBgcOSnwJ9~SM-(6h1
z|A0S!`uyc9{&xO-zlMMOJUluzB3ffiAkFe$9ch*S>MpJF|I(7_Iy;r$L}JbIn@VhR
z$7Es@G5cI-#kS2oefq~wwp=~2>+`>z-(ue+J2o$;LI3~&C3HntbYx+4WjbSWWnpw>
z05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppn
qF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTX@`Go)g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash.png b/web/pgadmin/misc/static/explain/img/ex_hash.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f35c76538c3a41920a7b93b94bdf97443df7aa7
GIT binary patch
literal 1169
zcmV;C1aA9@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001n
zsbo@(8Fi+|Hi*HY#9nXB?oYw#V#exs&h4h&@<F}n38~^Nv*by<=84npY=q5bYthWj
z@xa0E#>el?&hp;g_M^vPyuIzKtmw0}>eSTprp#qVzv+I_?y1pfi`DRv*YI-8?0nAc
zpxg0|)b5kj@2}wVOTX!?)@^0T>qEQdP`>AE$LX%xagvqMa+aa8;C8`}HuUky@a(s=
z<azV%$M5K~?&h-b>%{Eiu<YTk?BK2N=fUgdp10_I@9DMZ-I(RolH}8n>fNlm?Sban
zmF(iL<I#@k+^W3rgyPSQ>Ds94;H~fF!0zF_pG#z^7Z=B>b%2f~b#^4RQ$FKkW936b
z-9bUQ>wu4ZIPc`Y=+~#`)}`#;yM|jA(>_1o$cW#@hUV0yu-$XX5E0BgJmJiW-o%9F
z(xSc&4#hh=;K_*H!-U<zg67eo%w1jR)~4*;x#rQL<<Oz)+qdxO!sgSY<j$b#*|yug
zf7-fz<IJAizk%e<p5x1&-i$@K00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0!T?jK~zY`?UU<U5<wWn@pV8EQt5^jN;fSkZ!Z^txZea>#3ciXMG^ue5lb*`
zBLBL(BnC@M_{lzaKFl*S`~03W=gh1~bf??Z7>o&i8z-E`2MDn+LnITE(kU`Ph=my<
zli!=3;UOHsGB<N!4<_g`tKc-t-v&Vskw|g>!NW(76$6NJ2r~EN>D;qx<f=ddDj?lC
zzfvATB(nK=DOmtDJSQ(+N?*MuQ;UmBZ^#ftCYQ_Kk_phjJ0f{M{b7O3OfD}k^&$OP
zSQlBxWkuL!wbR&Yce&O%`5J+BAl-Rq6m~(aPN&nN7W801!-mmlLX-KU#frjNP-nB*
zbf^P6IMC>HI?ZTyx!j(ABM$Jw28;N7n`m*n{ee-CEij_l=W|)m<M#(C9G=C&reF~^
z#ik`>#q9t^g?o`5n$u2q89~HPuuHQD{TVq$evdr}9gW5IqV%<k!Qp^7d|{C&ZS{Bp
z2@Eq#a=+Jwj-`OFxEFRI^_BMcgKP@sAf3+iBOq2Pl`555t<kJ}Ti5ID_PBQove_IR
z2<Gz)Cew$9g+65OuP&+V_amCh=Zi_q93GcW`jBWYcl6^=7L(b`W$A2&nV)Qm)AC8>
zY=p)*2{p#QmtU5UXB!g~#e{J`Q!Sk0S*=#954-)4+s{4^DqlQEV)eLGs$i|rXr2#4
zu8J*k#g<F0Rt+y2&1QQT(pzk$)oRr6yvav+SrQb(rKlFlofH>!thd|kzAU`IGSb`9
zXpM~`UY7p|xp@{|V|TiBT>>)4pzcQhBc}iW03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfr*~9W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2a4e9369fa78c6fa19bf5c7814f8a586aed5565
GIT binary patch
literal 1571
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
VFfhuORjdF2002ovPDHLkV1kgxQ(pi8

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..0051f996783873a4a27db4b971135e95cac8d744
GIT binary patch
literal 1452
zcmY+CSx}P)6on&<ilC9wDWY}j1EW==VG&s^MyMc>VE_dIWsf2%OOQ>VP^3a>#ezbi
zQ$WfpN-;oW2^b&=k^l*WB?JiB_w`TyKVY?Ov`?Km=bn3K?)P@ef&wq>vfO70gTZ$B
z`}qW$(($7$%uVxaUUo4IW>yn~4)ryiwS?^8pZAsxthkhpJ|kUEnO0vS%3>*s%xPsS
z2k<7yY&!WTdW3!iaT!ycQNDmlo}HT35l7XO3C;8jz~MlDOvDm~m7Oo7J-u?muxfr@
zpW4Z+8|Svr5=g76*J7xhadTK|9F-Zn1z?W=#EU?ha)Ykg7?VO|88jh>7B!m<YlhB+
z+ihIKOFme=tgB-K^{c?37$i!dE}kC8*Y^qZlX7DhJFTT%m|NeG{<NMuQHSGZ&NB!^
z;v%k3I!Km}R=5~+lUn8L%?)9nD0kB+*n|W|{W4?{z}f(LBJsvL7vc+yV)2Gr{Z1in
zFoNuUS%J~8tXEIz)ik||2I-d8IN-v9etFrj#(`wAO|24dG_VI%MS2w#P*Oqdl0sB3
z7yho1^%ArX0Ocg0m;^P88U<b>@6$+o03~@7;%^vuMm@JJ<aWoNJuAayg^%p>Gmnsy
zuG1*Xb4;BYgOU392vO(P&!Kp6@p=_SEyk&Y-2;lk%6vCay<jj&HE-0dOV0b0N7*Ii
z>@A6~WuE;;C#7g56s?$SP%?QaR1+n-c`~kT8uNVa*2|@o?lo)=KN~N}8`2cd*w{>1
zTTgj<YDFZyg1O@D{UE}YyxV#N4ySl{7?kr`5m_T5X@ui?`P^!3EZxhC>gh@L@R+)G
zO(PhQvf3nzE#>#mKw1u{SyOTb#j{N!#xvnegJKyEY1bgl8X%hmqzq8Kx;j$0LMUJl
z<qK%_8X-v|7+3Q~)m)Q^fP}#wDqs)hYxrZTbs`|9t4#~%jZRFLv+$sL4Uo=)sui7h
z#-L;~yHiyh($Tl5Qd2dzWCRC>z`VsQjOo~s>UR}uO21G3b~5>_sl;f1pL3yp6Y(*2
zTg|QPYVFdf4n2i1ryt-~R&&EyEH~GIaAZa|e{p<ldYm><(Ym$Sy#1kbWFdS!sl4#8
zg+-&46R)?p=v<IrxKAkB`h2<TL4-|Z@SI~R;xX*W)A-68l_8JKw;U~tw3<);5q%L&
zO+WVhmV94&T?E^GFg?51+bpQUzhTmQhVhm~3Z0o+9h<ZCwR#@S#fJ;<<Zb)oT##J|
z)~QSg4M#(dpu=3@PP3E!chc@*v$C?VU<{A|;Ea$C)Mvphc3Q&ID5^bSMg}7&YYEw!
z+G=rs{cXF{PQvQnc;JJIsEn$r4gNJx`JpL<IafAY`AhBA`O0bGjYCz{Rlhy-wHk~D
ztSrl;%8Mf2s{-cqz(t1-!B^mBiO9Ppot??{A(!2P0&-5fl5P>dx>DZULg*TK_Nrt5
z-2iOB@sKY=Jc0s3a*{6G2s_h?5P1!wrHzP^j*5=&P&pEke^pz|yOz9TpDUboGQjBx
zMo!1otB@1eyV;Rc&(=*V+v==K^@N)awupf0xSm8APIR0a))g^~LD(V`iz%%B9lV<9
zNW9qx+^!S-`8sx=M=9LFn)pSWRENVZslD!B8wv}&N}w0~Q)Iq<_h*6eL%R>T<xbtB
zHVKX_vJbiD$PIhPc3|ksSjnS-lh?8Q_gnrdZaWp#?-x#cg*}W&rboNyF#et@q1F6p
z9T*>0alj*U0b~~`wx_Vx`_n~Y1o};*=weXmz#wPa!-WHU)K7MW;+k{KB^hrTOISzk
zQem*}7uv?l*3EgQg4t*IhG!(iW+b}Drze^M=7@AaA{-nMNQY2Iq`R||yR-9g<Y{*#
t(zVw9;QxR-DG5oKoc{o%+b7`Tw>#41KY&_u`GpC9`TGX?bfIFt{})E(L^%Ke

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png
new file mode 100644
index 0000000000000000000000000000000000000000..76c546a4dadd7fdae310dafcec2ecdef9649d4c5
GIT binary patch
literal 1377
zcmV-n1)lneP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006>P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZJydFPPaY
zjqWG6vC4&kzm|uj!nL^a^ZV4*@~N!lj*;EL!|c+nY?{_9i1sFWzH5(?-O$nT?A*cg
z?Z)orvhC!s?BcKR=fQ#6N$usa=Gm3y)|2GZkm}s3cd&fiz>e(VuH@B`<I<1f&W!2U
zr|sXy;?RxZ&5Pj5iRaa&`u6Vc<G$$Fr{~tC?A^KS-n!+`q2$h=>)E#4zJS}je%iZz
z<IA4fyMEfbeQRKz9{>OV0d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U00K`*
zL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U6fJ!G0)j%qd?aZR
z5fu}c7>rs-_lqPS2uKb<h(XgGuGEK33lp=Hw2Um1oV<df5)(cx$|@?VY7i|P>Kd9_
zOxilSdivP3!2P0PU|<N*L%<e9h$*WW0rh~yjKRJz!Ik=uLrmGoKm|mbnweWzTAAVs
zOBC;`SVOd!+t}LKo8n3rs9GE#S{$96U0jLO;s(*;?&0a>jav({i+z0kltEhjT>}Dx
zaQX!$jRpIKgaY-1nSww#aj8!^Gy>=})5xgkm{?PyQeSXrL>$l-)A)qMq-30aK}mc*
zDRE%Gq^6~3Wa89<QYfipK|(A$CpRx2w-)50Po)5&rLZW!xCEzPP;Ds%YatL~kR)gf
z39qtp6+&Uk08WDD=4PfK4Ju{j6{^H&F|VwwG&cpi3aCSkSS=P1TU3bB0n$=sQ*DYQ
zQd3)3UtibI*wozI)Y6L8FP3fY?MPZWI=i}idV2f%Crq3;Y4Vh*m|7qK8ivp@ZTgHE
zGiJ`3J!e`6kUM)8rXJI|^X4yD2)CuC6QpC&l-Udnb2@84dZu8w&2;gSrOTGXwbXV2
zb<9{X8N^;$3nC}CU}#yjdd=E(a4mJ+AT3RkK<xE(AaYU@hL*q$8#ir+YpL%6X=$Da
zVsEJjkrSISv~1nBJ$na9TF^p_D2MG#-nDxVY2M$pci;X4qy_TsgNF_wv=9}Q2M(cV
zAtJ$;mK->6poFAkiJr!&3jm~}-@X0I9(w=)03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfh#BXQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..ba24ed16aeb0d93bd133902b0b2218e674f84528
GIT binary patch
literal 1402
zcmV-=1%>*FP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006^P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YN)&~
znAt0h?kBgg%7uZymWQOmwYc*0`_$F)sjTIWk=?<=?9#1ln$|0b_9lA1YmbuM(9!Yi
z+`;qh#_r~_?c}iR;;-=M!GYRI?d7rN*_GwiljPHo>fEY#uzcLWj_l&D<kgYm(vRZK
zjOo~??cc`Y(2e2Ei{Q$M=hdb9_U`ZFzUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyHkc~n00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~
z0%b`=K~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4
zBxw;56%&^jj9OrRk(88VkYoTMLO_ZS1f)P}iPb`eEpWepZ2==#y2F+Fuz8<}Sz1O`
zj!9lYQAwEzpB5EWRW)^p77h(fEo~+pT|IpRY+B&4uWD#$1kppl7I=sOZ7~Mw0g0J_
zePN0#^&zJ*6=OqH5N&2|VQFP;hAZ`<cwf~9qQ%12&fdWcSGqve;t0{=<m}?=Mx+*Z
zh!zh|FK-{*T994r>*ucm(h}en7!-`tFNhEWf)M}EFrc1rGZ2VCmcb2F!XklAGmDCj
ziH$QOD!+z=MaBbdF-u5HN>0J)7nH>3n;H-HOImtHW)@B@D20-0HYCJya`W;FaBD#>
z`cw-cT8fGbN=kA11=W@^uoeO#21$Y@knk$6P$d+m4B#YaVPS3t(x6&iQK?3Z7K^H?
zDho5PtAIMxiPd5Wu|<_A9Uv{$wl!u*BDHn(4Gr~;P0cMW&8=-%{bJSb(Sf9;v#YzO
zx3{l<!o*3FCQq3<4O0swK*JF_rq7r;bLOnsbLLL(1ajxh#?)grZ~lUXi{Q4@c7b#(
zo;rttVQyC~NY7LZx0x+jx@`FhxR$zZppKa<r-0b2>Oka_Rtznx*Q{N)9<HUn2c)HW
zGKjsQ9z;%V#?TV9ant54a4ikJAT2GEK<upzAaYU*hL&yHcjW9ONefzt5#_L5DZBUV
zCC&T0_w7G$khDPFbLjA4gchQr^59_<Ekq<3v(keH50;XYEYZ^#bpZet8{@o}32r3-
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f<D*lWB>pF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb536b11b68fca6c2feda14a8011003678204d62
GIT binary patch
literal 1389
zcmV-z1(N!SP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006~P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZLlAj_u#Z
z`u6U(vC4OVr-z4&!nL^a^ZV4*@~N!lj*;EL!|c+nZ0y{@kCNSe%Uhb(E6~yL^X<m&
z=CbYNu<YWm@aMsZ_9pG+vF6#8<<^tr(~#=is&%M_sJt)i;;!V?k>k>j;?9ie*r$Ha
zR+!l<;?RxZ&5Pj5iRaa&h3hnp?kDf#zUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyg@M18hosTq4gLTC00DGTPE!Ct=GbNc000SaNLh0L002k;002k;
zM#*bF0006~Nkl<ZNXKJfAR90;fdC^ZT9}wwSlQT_NYcW=$;Hh>nikS+;RXAJmlQ30
z`~reP!h9rY5fK#=ml%v%NcW2*9|%YeK!`!p9j?@eO$!sVl(dX2lbpPQq7oB6Ey^k?
zs%j7|9O@dHT1?tHx_bK9w7~tMVqjnh(L=x%M2IP?7y<Qw#EikdFu|4jkV8z_$Up@|
zo0^$hSX!Cl3QH94t5`#{nA_Oe*_+}@7pPhsAX*%qoLyXr)Zzxw;_l(;<&9elvWtCu
z{ggpk{9OYAgK+u<C5;99g@gk2gqeasIB}^@IWz+3G}Fka=$KejqEcUQXha;)7Ss5I
z#H3`LenClmJ}Gfvzoe$6XJq2kf>J1{WI;kKJ0~|UAGa3dqEDp&qNT7XzqkaaUr=o+
z1#2M?Vvr<g3<<BYauq^h$^cG+=H_OmAPp*I<rS*LXfdy>tTZ<Ty9%g7jaV%f5L;A;
z(gD&^Wm9d6BvMmbS6^S((Ad=6+|<&F)i0K9?(IlgIy$?$dwP2N`X@}BIBD{fshC<I
z0UCzTF>N}KoH}FXtZ5xU#_SoGdQ9icoi~30+?JY7kdD?VGZ!wL)ma15GX=wKri&IY
zS-K3arM3%1wk%%=1S@Jm?8z+{T2`)Fy=E<3OI<gJY+47>vc3+)p45b)C2+&WO`G9b
z>U%(B^A?bnt@R-G#AXaF+qUn>-bs=cv=Af8VY`xd@7YV5_jm8xf8Zc#fxPF?;ll_m
zL`CJn!zfx{3FgRAoC(IX<lw=BB_t(F^fX3Y007N*+;lfqX*B=<03~!qSaf7zbY(hY
za%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0
vW_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfuI2JN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..0018157f64a5ae601a2db08fb1e4a553385c33b9
GIT binary patch
literal 1417
zcmV;41$O$0P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700072P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YTUq%
z?cc`w_U^Z_%6EXLhlh*8wYc*0`_$F)sjTIWk=?<=?9#1l?A*bRlHGmFTbkA@(9!Yp
z?Z)orvhC!s?BcKR=fQ~fChg_1=Gm3y)|2GZkm}s3b*P4@yf5tHuH@B`<I<1f&W!2U
zr+&{?nAt1h(2e2Ei{Q$M=hdZ!>okq-C-39F=-8*{)}`#-x$NG$<<Ozz&Y$bqw%ope
z+q{0-yM5!!p4z*9+PZy(fxniAqyuE0`2YX_0d!JMQvg8b*k%9#010qNS#tmY07w7;
z07w8v$!k6U00L`CL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U
z6fJ!G0)j%qd?aZR5fu}c7>rtAevy=vWRPS4Awock4+Nw@YKhfChAnWvfNcRISh~ZN
z`mlMQiCJ1kR*p$tK~YJW37-}fRaG^0h!zeFO)YIE9bG+r18iF0v9D@qXavziz!rFj
z0c|k`>H&$FfPG<#EA=6#F%@G&RS<1vZeeL<ZH6oLp?F`_2BO8n*3RC+3|G29)#3=z
z;^ge&>PDm%cZe1bPcLsD+**)b?Ca;R0@4!T78n$a(=Uh+1A-9$&@iB$a5E5yK$gJ`
zRKg;GPBV*&j){#kBPzd!ghj>!Z81woOiE6{=@*p5=bIW2_DfoNMrIaHEhvSOYBnUq
za&q(X3vg>eF8WjpAzF%x3rb3H`UTaNGO!i`AqGi;CXnzduTUivrVQXDXklS)2GXEf
zUQww=j24TkswxXJu&aPN)QQz%39&_$C><az)wVTeNFud$^$iX6jZMuhEzPZMSp8zv
z?$LpyrL(KMr?<DSf5OB`lO|7@It^0`BtXLvI;PJ6lGA3+nmxS}$e1$|Q;*r)dGi-6
zgxgZv1=7(rb=IOqv%6|RdZuEy&1~_KrOTGXwbXTk$kr8$fM8`Eh&`nhL(8hwYu2uV
zYpL%6k<IHtS~k>!*pr(vv;=M3w0R3$OG7V+Y}pFZvaJEcp45V&W&4huIlD;Gf)-*#
zIc#^zp1u39dcPZ&_xJ2SaPSamfxP$dks}B#L`CJHBPd#63FhcAoC(IP^w6P0r6eUw
z^fX3Y000U{;7!~3Lt6j<03~!qSaf7zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4Fh
zG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bN
XH99ab%9mBF00000NkvXXu0mjfCZzUl

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a78fa6a1d35d8b2666ee3ffa5583db9662a67e4
GIT binary patch
literal 1490
zcmV;@1ugoCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70007fP)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j_U_>5)wA5djnuJo)yKTSzu&2=<&>D;#mDaG==ksIwesx6?A^lL!H?z8tMcd6
z`}p$9v%Rr~oVtpQ(zUVm_Wa-A^}oUFs;%YG((&EGk@4lw_t?(XP%76v4Bcit{POSI
z-Sn%j=JV~w?&h-X<go1Gukh!=<GQ)mQY+$cP1;Eu-)lwf<+0}3mF3ox<kOJq+^Wd6
z(8ivy+*T~?;;!V?k>k>j;?9ie*r(rPJ=a7P;?RxZ&5Pj5iRaa&@3EfMdPDEyzUbJe
z=hmg{-MRG3zUrWd?4*wR<=^byy5-QJ<j$Y#*|yxifZM!&+Pi(@%bwc1e%iWy%Ued<
zo^8}&Lerv`vZEDM00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~0*y&T
zK~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4Bxw;5
z6%&^jj9N(dizFWiNDe@VLDL<s)Q3$A6SI`Gj4YF!yn><<6Fx1<Dk`dK5G@?)8k$;6
z+B&*=`q;F<{i0%EU<lDez!pS^DXSO(^?<~T!M-rTmHLoFOxegl1w@;gnOj&|nc@md
z6z{88L$sLN*xK2f;z}2&S{xu+9G#q9T#3};2GQc~;pyd#TMM#_eSH0tL0bG>0|J9^
z`UNG81^b1B0`-KMf<QQNsZTjH0_Zf;$f)R;SW}`>UvOwd9MBfi_=LoyWSo9MNqjyj
zabUlsrln_O;?#mtD5+#YLM%HcH!mNz7UZH&r2wL(uqeN{1gBq6Z7Bt7ArNAaBxno?
zud;F#LSf1PPJ-s<W~LwwDrMyrs>En9udJ*zHwC*2s6&ldEfx@4REW|6(o$tpZHgpP
zQ(ISGU)Rvs)ZE<E(u&nDmTm6sNLo5NySjUNdi(k(Oq@7r@|3BVS|9-$hR`u>Is?Ou
znX`ak_8bO=x$`jfn9g6YaM5D8Ej67$9SlomEnT*J)~ppk=G-Y5ZZloEYW146a4of6
zAadP$28KBsX3YYzC%0f|*|=%*maT9tb=@GcX%djwHfuYGJ*f#pOW=;3yLQ91)c1hM
z=7|gpduGkr3t~@f#?Z2F|AFj-7+MZ(n>FijJ$5Z<AqI2Uky*2jcGux>*s<i}Cr+Yy
zzhyE|3qw~e4(}g7b^6R%v_Nj1GM9m2dS?xeKt6Hq{CR{HNK`URy)bLm#SR7>QF-<}
ziWX>sxio9mWn2lywB+pBvn3=YOY}5GT>t<le%ij3CAg3P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f^-`z-2eap

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7764b74f5e5e5af1a205b2ecee9ed184727e1fe0
GIT binary patch
literal 498
zcmV<O0S*3%P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70001NP)t-s|NsAV
zbaXa|!Dg1x!H+h2o~cd1>4~}9VaDrk&F*&1?7h9crNQLey@{>X>89TD^V}-D-R=M0
zDZt<E!r<=3;qS-e@W|uv%H#0M<nj6DU(Mz6|K?xO=kn9&^VI3|*6H>3>xcgAhT-q`
z!N@d;00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0Ut?3K~zY`?bO>2
zf-npQ;7O;V0?Nb_&J(=<J35L^UDqjzCdSySWyu#<`)@-0>*s42VGTQwXf%l9@iclL
zNdOXeuiySypCM4!;O@y&X{wSOIUXq~XQs-}6QvYS4hKrA@L4=0l~^f9_IpA^Zz9B`
z$BhkkJMf8s@sL*VhLWpb4Q#i-2+rIQ+9-eP?noQ0lYBNH*ld8!i7ZVDly=E_-I=Ub
z0iwNRxdislWu;A#vn&>!J9Z<@Ox#J(okv2`=*=e-1+Z!K=krEr8ku~;d=B=YX>j2X
z3`^5Z>~y=h^w%!z!<XmH<A-BxricT_g}zMDF0MB}&$8Uyetc9c(E*Y`k!1ybMZHLj
oF&RsOM{v$!pYbjo``5SC8S!5pi4Z+;&j0`b07*qoM6N<$g5#I=_5c6?

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..d44eff429fa9a3409776ea88a11421582f6f4598
GIT binary patch
literal 1298
zcmV+t1?~EYP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001q
zx!X2~!Eep(M7`;C&hL59?^VL-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUOPl=ya2odY-AJ!Q^I^(ZP>4@#=@|<6!gf$nfm8
z@9DMj?Zxiqv+?W1?BlTT>B8af_vhLx>g1j3<DBW>n(yed=-!#<+?Va;vF6#7<<^tz
z;H}o__3YxW=GvC*;jZM>lI!2C?dG!U-mLKEz|`sV%c5A@y@~4Gs_*2!)9CZ$)RE)T
zkK)jc>DsB$=kny!kmAma@8e(T+p6Kris{*?_Uni3;k?b|^7G_h=+~#{*QV{@yYAa6
z^V}-n$%)^{h~LMD=G3Ii<nj6DU*5)s%H#0o)urasqsZg%{_KYD<-qLSy2s=2#o_P&
z=3ngIy5`cN=Fy_&(V^_zxc}ZM-o%96!-L$xg5}Vm+`fRq;O^wkpX=JUz~AoV&7SJn
zw!Gc#+Pi+^%$}{*>Ep|u5BdaA00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0?A24K~zY`?UdPD5>Xh3ZKAN;Q_C#*KwubLStTT}1_fLIkws7h8A(MzWmN>>
zU-!(C!g1n>o1QNGE)F-m&yV-_9Zp~0%lzwkMX>Y7Qw$+qOC*vv#9OIU+E4Vqdp|Hp
z3=Rzsj}RlHV`Co(QA9F6K0ZNAOioUIBtA_|O$`u3pQX}aVsv_XV2tRNGV%8*U%21Q
zpI@J!9p9ai$!5?8$Q25O9A=eD<t)sqR4O&7Ns`onhN37fbRe?1IT?#6=I7@Xpj=p3
zRDo(~X=xcqonCJM%4js1F=S<>16kw|9SNjfucyFbG<8Q*i!8FN(;>v5|7N7XYFb^j
zi6er`Z;S7og+&Wmv(4_nkhL`y`2p<k=g;S-7$T&omq}6$8iRq-fYxd@JHSEHv=d8N
zUvG1Hvaqlg8+M>wZnp<RHaFW4Qm5mKHCn;Ey0JwA?RIZ>L3V^<DXv)CmdizhcYE9C
z7f0lZot<3~3#Z5L4`9gNUeAs32*`uMAWESuRxLPecC-tp*W+<QAQTEmL{lKRzaI@^
z7CvMViX9xrG34k-K%&tED%OOq2kmmXz2Ncrd?5%Q942w(_?SncEW#FR7mD@!VzDr!
zlF1~4AtxssvYSXG(yRsji?awIi*PCekvPMgV#wK9&yAg*b1CUWCX;4&;YGXf`$GYU
zq~h@lIL+qrmmrE{uCA^!xWzRCxk9m6>W1*wBNU4rq#&NWE@UBJES7PkQfWgTi$%L&
zt_y`cl#1nYRUAoYSeFiqTrPhDH>GOz219Oddv1(Jgp@!yg?4elFxgYMER{+qrCO`q
zwWpwmuU=;#Kf#5+9!!C=D6<x|Mx$}xj>y*P&1N0_`voL*$X<^uSL_qg>P)FLo0X@O
zINC)Pazz%YmdlMA+~Wvh&>fq3bh%)%n8jVI)w=6!OnAn7Y3ybGuXzBr6b33x=w6}#
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f@j>KSpWb4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_insert.png b/web/pgadmin/misc/static/explain/img/ex_insert.png
new file mode 100644
index 0000000000000000000000000000000000000000..862d837277c99e17d2b66232de79fce010752e7c
GIT binary patch
literal 1065
zcmV+^1lIeBP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003yP)t-sBmgK|
zTU$1W!3nA2a+aZzl9IuXHmaF8ou4@}wdHlD$3eX4%c58k;}QztMoPfx$EtN*#Oh<l
z>TAd7XvplT#d&Va>__Y%vc!A9!GLwm?AyJ6+r5e0zJT1nfP2yJ+`)o`(eB;DgM`!Y
z-o%97#f9F+hK$ti-^YjF$cT~E?%>IZ;mnGc+417ejN;IZ<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp;FQ>)N;I-kIv%s_fjj>EN2{-mL81y6fMq?B2TU;H~Q8obBJc?BTBM;JfPNo$TVT
z?cu!a<FM`JvG3%*?dG!X=CkkR!0_h4@94Ad>9z3a!tm+B@a(qn>%{Wy#q;gP^YF+M
z!Lnrl0004WQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkQhe<?1R7l6|m0NSt
zP!vXK&`lBB2CJaOTA)a|q(Gri2tkO1TjWv@3IQV^xBmZM;7QKm5)32GIHM2U59@)+
zO7{1iGlRkKH2+{;P|ED2o<i9y7RwoRabmSzQkSo7wrlG8!sWW5Zmt4>Pn51=F&d2)
z>IBhBy)v84HtG!NF6!#^)E1!r(pR3TJMW~P0*}+_^l{1Ycsz%>EXy;PNs=@H{0soS
zsZ1XpT4Ta-wOSp4TqzX#AZ4@ZCSYUngQhh~OI2D}smLG~3Iz$e+4NHRx>yYJuvW$`
z^<kBxN(CwX!so06?Q|;DAWA+@Hy5zZqVB&+@l8$9oxr5os0~RL2mO(muAG;k%==v=
z?{&03tR0w-WO+_F>-VJ@Oxn%n5Qd^C4wf(0IAs5IimsC4t_ET>94@Z+-*8Z+6;vrE
zDmL4OX6o)%1Th>A6*p>99W@-O6jM@*ZQMqqB9uZQMgW7>p*i#m+5guRgQ6hxVDr$y
zp&Tmg*VxiQ7K(#Tq1&Di4jVOe7K2i;IDj6{=Q^OhkHF`j9~j8~+7t{*!3X_54z}(v
zzyVU)=`^|+m|`><E<urD7#?zdpLbg!qEcy%5Em;j!C($DUayzcSJ3|AcS0pa52eUu
zI?&-*wyvxEL1XK$h+=6#I1mKB2b~PZ-9tT@h=0*v4DY{{!oxzs?R3WU6?`!;`C!nW
z0nY1jCn4^1p6^g_rK@7mS+E|rI}S1D`FTWN!TW1cBogs5lw*-d`L~U|Gby8??pJ!b
z3_1msrXytzkCh?RRq(}7X*#mo?MOj9Ce&3jKILMl+*jeyDI_uXRqokjvW~fDmd(cA
j94hm6lTUy1|Lgn&xg@`Em^A9100000NkvXXu0mjfS$;eB

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_join.png b/web/pgadmin/misc/static/explain/img/ex_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c391233c449bdc5a0d52d50f522bd3e35c649c36
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_7<@W^2*R&GEgz?Z3h8!ou#;)brTc^v=)n(b4kM)b!xs_i~n@
zTZg#3z3iu{=c=sduCM9E#_!9_@xH(9#m4Z~*7U)THuUky@a(t1!S2S#@6FEg-rn}>
z=AWyq=(DuyyuIzz)bo;+(T1sHQjHmZrCYPXrlG`Mg|bG+s&$Tqt8$|?qQ_wH?6#)N
zW$Wdh>g1j8>9p_bw(sb(^6bTgsA6`iJD#<NuG(>Uu0Heb#_r~_?d7rT;;-=M!L{Ui
z=GvC!*OTPakm}s3d$U61)sf@UkK)gb>DZ@@!Bgwrtm4p);mwQS%8BRHr0(Lq=+~y~
z-MP8zf4c61<<X(!&Y$Yow(R4uyzqqFzJS}je%iWy<IA4v<DBW?o0iO7=--*_;H~M}
zsqpE-@8-bX#f9C$g5=GfwL|Xa00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0r^QpK~zY`?UUJC(?Ar(V^BbpRmDM2l-2b%E>$$61+gq;DJ?Apx>_)8HEF30
z1qv<yI!QY65i<h=Jn+JOn3sINdveZqt{_~`MdpTR4te<yvEk;e#wM}(cFUc+BHuEx
zwXMCQQ|!8T|3SCNw~VdAT=visCNG}~>Uq@L_ZU6tfBI|y*^ohN&5&~$8Xg&a{sO&x
z)%W_19RZglNiNj$_TBprh!c_AZnuPnKYkkfd})N~g^7GY=8#tqWQ@OlJIjk*1$#W>
zgqZ+yzW-as^djR)gE?nn@}vi!H!=L<1mfjN3{M>+(=)SYCw3BX`FwNp6fe$M7+;i6
zP0Ts|#ifABKyWz}Ug1&^<UqvyH3+S)tw$+doagEfL}HdoIG#wRD2Vfr7JgcU0O^g*
zUkVLzoGXry#4@R@lB0NWs^H)F^p4ffmArN24^hdfs+Omy80GW&f-oN{gjc}IE4D=t
zwfMKBQ@nt8X1hFR1{zO;+2q*P4r#JhD$x<&c6oOh3TyF16wJRei2A*N&O&C(yQ>A*
zTTdh_V86<|uIp4UMlA~aa1gDeGGHqKGqL&y^4bu#%6@n+eE^xPqU7u<Y%fNWHY$*+
zR%<FRl1?Nyv!GOKHH{aECXMoeS}PV0|3jEwtnxul(+=~OPV9QFW{v><WVgBso0Wk8
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f?exDGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_limit.png b/web/pgadmin/misc/static/explain/img/ex_limit.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc3efd59d70436349e5413f8c5d2673200cd74f0
GIT binary patch
literal 1237
zcmV;`1S<Q9P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(M7`;C&hL59?^MF-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUNiI=UKt$YscuhV`Hfo7w~d&?QwDGaB$~uaOH1r
z;cjl-ZEe|XY}0FNhN)yyjTym@HuUkx@a(tm>$dan$M5O1^X<m+?8WZpv+d@x?d7rY
z>%{Ehuk7Kj?BK2N=)$7LVC&_c>f@a6>b2?Mn&;e?=i8R-<FMt|ljYTt>))-W%w_1_
zndaG*<kXSm(~#=js_*Et=-rs+*p%bakE+va@8-bo<iD=jaL1~3vEFs<<gn}Btm)gT
z>Ds97;k~rudA8?#xaxo6&Wz#Ai|N>>x$A)A(2n5BiRjm+=hmj@)urv<yUl57?A^NN
z(xc|lqU_wb-^hpG#)jt9q}{@U-NAz8(4pSMh1|e_-NS_2zJTP;pYP?s>)N;G)1&6m
zq2$e;<IJAw*|yrdedEiX37wJX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*pySK~zY`?UU_O5>XV!X``@E)R3}Nf|O*B$m)u`ByS4@xQIz9FPadbCWy#_
z5QxIR-d(t0;9^epp=NwO+}WKuzd83g&$%MegZ^zjB6tlvg%IMgSS)@*Je5c!L&VUt
z=h9(fctj=}B}T`_$6pYFh*&O{i-`$^Lh+J#H90vcB}QJqnUWD>)6>#%;<hdmlBqj%
znNi+NGuMdnZJ$&sm0G1%znh(%otvA}Xf*GU=I537>Y^pJA`vX;bh-upT-1Y}qNpWU
zrdJHBuxd1!Er6>!B0rD_(XFkm>CkDl+JLg#?KIF1r;`Dr%kB06uIeHmuaQNo)k-16
z>!rcq^!bcncKg>^z*Rzst%U@<-VFmVK3~uTX8-!;P2>~V_R@9{;9VHHF1A>f4FRs|
zBA?OE7vMGU#8sVnk=rDx1^uFp(!)}Krk7!LhhZ#WF?+&W5Q;>jF~C)w9^1QnUrFAD
zodO!W2)fMJMPxr7PXMm!blKhehPqHzyFCE(hQql7Owi>vLwGZpj6gIVPo)7@b#KOh
z^I4$5t~i`N223uuKMZVge?JO|R4R1<xT-gO?1Jfc5lbH&9`&dHC!2h8X_3C)(ET>K
zCEkTm$i;CclRW{Uhm5oEtZyb+ez5U09OO<<^N=|!6gXkT=yIVYXK@xOl*Qp`v6z8u
zp;Q`xnA|;C<PITUES_h9E0xL>VZ`DIvp*md{nbSlxu|lLN>vc~?IsI!LGL0Gf><J*
zK7ylszIF^J7aUiGYNdX8C48}77w26U^+u!76h_)hp3ddaU~A`Rg$v-yWrSP~M%qh`
zvMAR0EYM&p<#MBb8`9%~Yq_ZNF0Pu*=GEVt*n|Fa{RGa817HRj*Pj3Y03~!qSaf7z
zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6W
zZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjf-+GHC

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_lock_rows.png b/web/pgadmin/misc/static/explain/img/ex_lock_rows.png
new file mode 100644
index 0000000000000000000000000000000000000000..41c1148bb185c87898b3fddaa76be36a15703336
GIT binary patch
literal 1520
zcmV<M1rPd(P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700078P)t-s1prKL
zYin<9ZE<dHb#ijIWKo23T)1UXxMxwgX;FK4c)Ds)x@%E3h{3vUQGR@Ux^Yo}etx`j
zQG<Yhyme89f`YwxQHX|yzI;)Nh={*{QH+X;38~`0f>Di(jE;<qz=Tnbj*fDcp^%S{
zz=%=8h*6S~l9Q8@!H+hYm3+dEQI(aIm6n#4mzS5An8cD%#F$YswdI<go|~SYb*9Ih
zp`pT_QO2H8o};6lq@>5AQ9-=u%c5Axr%}nMQA)t+%BxY=s7}hQQOB=S%&$?Wv9Zju
zQmC`DUBv4BtpTdFwW_wZW5(*uwpDA#=xE67&bnBwy1LG~S+BgjZp-YjzP@$M?Ap72
zw8FyMy@}(!bKJgw|GhAK(eJjz#N5GxgwgKZ!-Is=@ZQ9P-o}QE)b8KMhu_GEk=5?t
z$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+m
zlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#r{>z0>Dj2~+?VOvsp{Fb>D#L7+PCQ5nd;rD
z?A*BN;F{~+tn%Ha?A^NS->vfAr|jOk^xmoL;H~Q8ob=zR?BTBM;JfPNo$TVT?cu!i
z;i~N8u=L}t?d7rb<gf4KzU}6+?&h=a<-qpkvhe1>`Q@?i=(G0cv-an-@9DMq=d<wX
z!uaU6@a(qs>bm&qxbf@6{pz;(?7H&p#rf^N`R>2@?#1))$p7!Z`SHN{@xlA?!~gNV
z|MAlO^v(bC)c^F+|MtiK_R0VD+W+?3|M<<SyU{8D0004WQchC<K<3zH00009a7bBm
z000XU000XU0RWnu7ytkRE=fc|R7l6|lj&CyQ5eRxeH+_N%WSus*<!m=*`Bu8B8iHI
zC~mlcpojwm1gMN_hN04AG^HkyMp;_fzWiC;xpx#ij-Jk`bLxZdhi7KancsQe=eh3<
z3Yw{ZPzMOMrc^$J5DEl>R3a-OF)@e8xgr!66UABC*%d@ZUSZ)af)^1)L_`RP1gMF`
zrP$b5A(0C4Y$7i?Sy)K?L6_Y-qqanb<M+*I<|&mUcAZbVnRe~SO7sCn1_lO3#?<5S
z41pnrVSK<x)3g_0A|~ZR%)0Q%xVS|K8R+b6_i*ZIY;5cUjNNMO2DH(rb8yJniyPR-
z(tX=Gq#Ysc7_l>ev0AM(=rZcYkf@Xxj4t0ln?*eB=x3yTb~{A6t+$OdaO!GmS~0Te
z24{TE!C>SC%|Gtq5pvPx1ztyMQx8I}?^v_?^qCXK;t$OVo);YybP}+Yfi!)xvdGo<
zqo2Nf{PF$s@L+%cR@fzzEilx_FhjuC-A(&|*XeNdfF6pX+^Eatcca5>4G#A9K3UHq
zNl8hW0WP{&7u8LGQYjQRgq(YWkWm(S7&eYDb~_H%=md_M>PiYw3PmYKPQCUc4?_J2
z3YJE}wpOZC6sRjLm0J*U{FNWMzhd0WWEXCm#o|Ip{0l#FH{`bxJN%qQMiFvnG3?@_
zyPRH5ds>^|S-9(LHaBpY&8802W$)8xFF$<!`R#4*z2@cx5W(|LbRmZ>%tZzHdW7tW
z-nMaVSm>%{Axjp{p9>MEP6rnQE2UB?>VQoymz#m9AYY~jd{+!b+Qcai)*>$|G6923
zCL{R~I6w86Q`UtGx-c0!K!=_rTX>P90f}WyPLIC}Mb){v)4|$d(Cgbki&j(Bz&{wC
zAMXHG=pvT{8mUC0=11^i1R{D_u2c)EBogry2#z!mA#<hDDxj8##Y%p}ZLyeL6TGx&
zG*$JWUaeHt|Fy(iCIdVLtZgKzZ2=8xwHkI&mY3J^4~7>5D=9@T#H@>Qkw{d=j~MdV
z#lWgX!TOOBenbyXLCdK|!XiqsSX2(mrbgOGEu0@~po`j)l9JkK5=)=%R?gJ_Sib;}
WuicyymX%Qe0000<MNUMnLSTYGrAb2o

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_materialize.png b/web/pgadmin/misc/static/explain/img/ex_materialize.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd0bb90dd2ef1cc1baf7f932817d1b6bdf463b
GIT binary patch
literal 1221
zcmV;$1UmbPP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001^
zyUc^E$aSX2Hi*HV&EAa1-IK}Ps?y-H)ZnGh-mTK#zS!b$%<V(H=}o`rQ^M+n)bNzp
z@OsYdhtuw@-}7s{*>k?y5Ub*3#pqna>5|p&pRKHao1$`-p~$a;;^52R-owF<HuUhw
z@a(tV+qd1?w&2~u>*k;8<eu;8wd&)Y-PyJ2;hXR1v*6sp^6kaUy`#sfb>G{;;oZva
z=CbkX#N5}k+|{$*+Q8@Bm*&}&?BlTA*tOf#vFYHN=--*_;H}=+zU0-B;M>TCsbo@(
z8Sv)7tJG}W*1h1`$FJLRv*CB}=)&sVtLfaT?&7`L(XZLhuG!A5qQzgO%Ve<JbGPSw
zxa)u8(U0QJjOp2^+S0Jx)VsRuf#J-G=+~#%%&gqgy5P!*=hmj**v0JKy4%va+tIk@
z)THLqqu0r)-^hpF#f9C$g67eo*vzZg%d6JMsM^oB<j$bnzJT7<!|U0$+RnGy&9~&u
zp5x4(<IA4a#irW2ecQc%&=gc%00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0(?nCK~zY`?b6p*8bK5XU_ww#jGAI1rVXt0A|?yUN>Pg78bgs7LkqpPC|yAQ
z^~}hc;|y@}z&Sa2@IKs^*>CRt?k<n_Q2(eN@lkOdJ9#WX#r69K{Ds9u;S&GJ)1~ES
ze2fTIR@a`t5WIY~zW!Q(k;OL~8$t+$3(Evq6^TTff|a$mo9_g3PI><U71uHH@iqn5
z?;ilML?RJ$9V671R4U!#Iz}WinM{JZTtN`2QmK@pu2N4Wa``NxQfX!ot#$^{Xmr~+
z;j$<c`Wac&>g^qjd;+Mrj*-t4Dy|b#6bd*+Pt%5pDQfi=wPQ3IcZn=8g&xp*CKE$u
z(db|b<Nm(cf)j<-YNY{fGTBI?)*T!gAu*fn1hHE8Xuz0kHU~)#4k59a%}#=dX_Ugi
zI2^7!a^yZjWSnBxZnt~>x-s`LGP5^kU>L>)TrA6;0C@jxeL01O+O=3LUn!5*>+|C;
z#@J8_HgFbX0nh#I{QUU%`{~6GH;+r<4TZxIl0>5{V9$cV*q<pEKhYGs)9Lh5zHmIA
zz=<Oo2tX1{CR2Cu-AEu33i+r=Je|(qWSk-!OQo{J#ynmhOyN&tGP$|`u@jb!WkEKd
zFJy^N>4`)VekxZimGCKCihQ|TE)WE!sAQ;OwN|U+B$oVjnFob(`Km#XM7mNfQuSJ`
z*2GEb@)F)(xVoAknM$=<f~3_V2ujfg?M9=~CJ0PX&rzLbv)P$@W7$Fh-rw$adlOUY
zb$Ea0x<A0Da4EXO;jl-LdJCoK-`v~`aMFOJ3wl#Xvo#uBQ-i4lK0K9-M)2V&WSpYi
z>rG_o^ihh=U@(|_V-NMe)o+jt(ePu5AK?H103~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfVJ3t*

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge.png b/web/pgadmin/misc/static/explain/img/ex_merge.png
new file mode 100644
index 0000000000000000000000000000000000000000..3fd8299fdcb72604d0c97816d38ee25ec51699ff
GIT binary patch
literal 1127
zcmV-t1ep7YP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001z
zmC<Hv(ap~CyuIwd!0pk|^3&Aw$H?!;$neU_@!Q+=*4Opn;rFJf=Bcaa!NTsv#qYJY
z>$$q@!^H2!#_)!zWKxY8p~PNwrpGpj!Eep(LA>Zn!0CI@?}XFvW5()e$n0**?3dZ`
zpxg0;(e8}Y?vd5*uHf?tsp4J4>N2(Eb<ON+$LOQSVRDwC!H+ic@W}A&w(se*^X<m&
z=CbYOvGMD~>g1j2;F{;$m*?A-?BlSe%w_81oao+}=Gm3(;;-o4nC97(<=2z!;H{?6
zXYAmv@aV$Fs&%W?Z0p~x>fEaD<-p_8kK@pe>DsAbl`meHCBewL<kgaJokg0;U3jiO
zp0$U%?SbUekm1dYi>qviyiJ|ZV|uVayzqqY<iF|Ir|#mtzsI+$-f_6=fAHqO=+~#`
z)}`&=yWz}=;K+#I$A{+Bq~67a=F_9#$%)>?h26u0=Fy_;-MZ$|qU_wb=Fy?$&!Owu
zx7@#g+r59}&Y<1Fg5%7dI?;o_00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0v<_3K~zY`?bGX5(r_3DaGDn~EzQc+f-=ht?ef4F_ZT1uwF%R(Lb^=WWfKN0
zFq@$K>;1-ob697`-_$p~@OkmP`<&<Z{qD@+xE#k7!Wh%W5n^n7Vq%hTUY)u&O-wtT
zGuH_$8NV?*J4f7{pT9LmOy0gTHS@oavu8O+V;7hOa^b9myh~n)+@p*!Z5*NQyWH*v
z)WgL`OOL51%THIHQCQ;gcsy=ub#2Y{oLXL8e6fNj_O84fA+P%HH*FjNug~Z6f|sUg
zKlm9Y5Ckmog+d`3=x{j9027HuW0vIgKO}AtmSrFiO(c>wNIZ@R!?LLW#1iZ2j3wDH
zWf`OhpcKJmHj~4WH(-ouTamY;SNt@?_#qgJC3BGDIDQkbgke(=lwv)NQgHcvL9ilQ
z8{6+3!+!vnR0I{9Os9(+@P&dk*?IrrBQj*kFsRrlDmGgz?(&F~%a-KR=PzHkNs=sy
z$c&`egFQj0NLJ(<GT(ocN}?!|HXB1`XA4~s`&V+=4EGZ@c|njsl4WJz>dLS<v7aT=
zPa%}c)e6WpRjr>w290sM`GP^JRT&hu(P*AR2H%Cx8^sD$O;e!W_|<CT$uNb2QXDjY
zw-2$zW@Dx+l2DN)*ss@{2XOdD*N^ZIt7uwnm_qM#y2p4TYc*7Cv(-A$5$Se&c%rB(
zDz@D^>F99WwM7OgbR)%4uh+w8IY(ocV>y2TWnrZACmaDB0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1n&OPyYY_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9aa51fce9d775f169f70d115761e9817541c48
GIT binary patch
literal 1599
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_append.png b/web/pgadmin/misc/static/explain/img/ex_merge_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..12fc55d76f504140935d5fa422f9a5075431bbc3
GIT binary patch
literal 980
zcmV;_11tQAP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005JP)t-s00000
z003rd(Nc{WHi*FosNxW-;$)T3a-%gGuH(UvHg>8zdY-9xu0FMyetNM%gs???v_por
zNkF{mMZW2fwrozm=1joprKsemspnI|>RQ3)jlolwyLFkoc&e=DiMiWd#p`6n=&rBn
zp~POI$6%JsU2)6oq{?G&&F-hoW_r%-+rEF>y@|ZN?A*VB+`xgozwN%i?cKqGgVF8X
z!h^rT?cT(M!ou!_)bQTLg~G${i`4Gl$A*&C@5RRNy3wP>#_+D%ag^Bb;mnHS&yCB>
z@#M{(<j$YW&GF^VpySbw<<Fqw(vQy1^0ec5<kOJl(V^tjk>=8)<kgbV(emchr03M6
zw&#51*OTYfrLEud)710S)b!}rrs&tF=Gv9%*r(Rk^ttPQ>Dj2~+?VOvsp{Fb*xB^D
z?t<yus_NXT=--*^+qmiAn!NCY>)x#C;hXH;y6fMq?B2TS;+*W@t?c2h>g1i+<;(2i
zukGQy?BlQO<FM`Hu;AeL?&7`e<gxDKzU}3)?&h-Z<-hOd!0+g@@9DJg=)v&l!td&~
z@9Vbl>cjBtw(#t?^6bU)?Z)%($Mo^Z1Vq=200001bW%=J06^y0W&i*H32;bRa{vGf
z6951U69E94oEQKA0kKI$K~zY`?UPkg!$1^;v)KUULUEU1#ogWAouV!7?ykih{xC_%
z08P8uzzo9+=i$EWd~@#Ev&%65`jNspQS#J=aPdC=LHS%|Vph=rrxEdXaCDB~V*P?6
zOSoTiagtKVQ(H*3K0T*!kezPn9y(&R*|RBuD5{jL$;c}oWwnJQBj0x+hE!(ZG4|b2
z56J|uqU6ai*kJH@XNdN;E;@msASoyeeLdlI2K-Hg!O>Vfiyb6RLfk-<JSpwt1POuH
z$A{Y&cse`Wy#}=#6BO#@!i&hmHd?uav*Et3WuQFNGM5Uyz`)%Gnzz7yUq|yS4PkhF
ztFRheEux77*lcU89;dqKTir#MXUoaK4P4Akj_d*D?}vtngvyQCK3LpK>BaV2;4y5!
zFcp^>3gRlIkf(HCo=X%&Q8JG`mtuU?vnWc&GXk+=|5<z>)+K-bO1R(MTt0_mbAQjO
zbuYw{VGA{*NiY=`WVb62QpvD}s*xFtx!GwA)=08cuRN`gIaG!Ep))fzsaD}aS{Y79
zOvZIsXsWB7QXr(2iS{Zh6GdK`j1X5QioEi_{zPxhzn!H%4w>%&0000<MNUMnLSTYP
CgEgxF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ce4839e02fd3f17b5fd8e358ff204046c38d26f
GIT binary patch
literal 1344
zcmY+CSx{347==TqEn;1Yb)kzRwvO5s8xTS1=pdpEBQS_s6{;)(R?#BJSj4Jq3RJBX
zQDkYPh!_@AR0IrxkdTEC5=cT4vWF~}EH}AX?oB|$Ort*a;rwUL%=w=FQ<a$T<6{5M
z{81>>;#h1<lGj7u%Fow(H%%9op-|{MiE#(_c;jQdE<1-e#<7zI!9lWxOtDPx9D+$F
za|9Y;+1PBmM2g7e2w&iwoP>oUczW7R8US1I0KR>uy>o^_wbJQO_e)bxulW@b<ZvB4
zo@1B>(HS=8h)pa;>PUr2w*vp69j|&N3$LV{>k!lrt7&peF2@=_WK3t6FL1zCrK1;c
zH7nuAD!5GzQ;lwx*-Zivior!Ux<t@i6v-ISq51BG4od>61lETNxJ+QH5ZZ2v>;w(c
zr$;)~&Sx6uh{@f|NG-y(<=?}pne`Kl{^u_ZM7rq_Go5lf9JaG;VDH?l&W*_Qz^qE`
z(rDcPFl)8GZeeAZfrp4=T%kmSLZ?ImE0ivyaqclYvz3#LAOEF|e_A$BCFnXQ9V7Dv
z_73408`uMxdz=u{jY#L*+Na`NlkRVmw$Z4rw*oI<I~TTdU}((ckeqDT`iPf(toCb@
zT??6djMEJURo#rD7SwjRVJU3mIH578m5tcNm+*=8qsIyfJBhqUufSr+@WP;cFeAGU
zOx1wur*5ajX&H5xSqLO*8Y-zE#x-)&i@KtYKUhz+mi9wsb~6oB6U@p^NJpMUrV*>q
zZCA}es>*=`951s&nb&8&N(4(twsIRVWR$lXrFet*iAB}#cBo*>Bn%3iW`PS*7^Q7$
z_8+Q|3MKs-z-xxVNk~tFw8K^n)nS-0h+9>R>&oGB1*KF!-sCj%Aq~|!O|hzn95ds<
zWQ&4YCLb!14P4c6a1JBSs-{>}Ll!034sgw~K0UWdJBBk&^&l3R0~ojKxv*KPW#gp8
z%b<J^8ZM=HN9j|LlyCqQ1x#go!@_geqvySjeW$&woSohR5F4}WAeMO|ZS{hWZ^o@H
zjt<~cX=Bt;2A4)*(K*93HuL+nBQc3L{_3j4ufE$?-cl2G_hx4!iJHcXCZ>+~h4>dY
zSFdYJuBo}Vk>A|xk3MZ$+vF3p@YH_cBwxfkBosto!jdAwL$+=?ya7+A@<*k6g(9w!
zvR@G^meB8Yt<;>!+s)T3aowoc;8%FDtaxd7@E|tX<MBnKJ@e?Y(o*z}vfa_c)PZNG
zs-i>z%K`%f)vG>Tv0{ZT@XO_&ueyC`AKI845G$^ZA*NQ}>28@ViTY+6jXxB0|Ihe+
zAGQbA9hF3-<mczxr>ZM7wVqPyI&zbynjb2^vgu!ZXGXE!his~s5{t;!8Lhxdd2+H$
z`a^M1P2{d350QAO=#A%f!-lxV@)Wji(}5@Phm(@(Lzd&2xj)T=+t1GfFXQUTsNU3-
z3CFt&S^TUm*_%@dF`1P<NcVl;vpy!2^m&H9U6|t$5mLzMU;Ubr=F|HHSR3Eadqx5D
zUS@KS_2<1=;NKL~a}pYy|H!jsP2}&ojk|&;3b$YI&{zkqJQ;7?@f+?)@T$!?Vu1u2
z6*|R)<?x~pmIY>P?D=^xPs_+6o5h4r&fZYnYKX{H+WIe;MNe~RudnS)#<~{;28H`~
zp;7y0eV6*_*%98?uP@khq#!fBAS?1jewNozp_mX%a7bt{Cgfl!CNgYGWLVg@n5~f*
t%(lAqYyJnE&CNWOeepej+5QfA+m6J9zXwKZ4|RG0RP3IF7(#Sf>3<opDS-e0

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested.png b/web/pgadmin/misc/static/explain/img/ex_nested.png
new file mode 100644
index 0000000000000000000000000000000000000000..15c47316d5d4163d97b95cda99b2e3a2049341c5
GIT binary patch
literal 1108
zcmV-a1grarP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002N
z!qAqP(SL){b9d2ZYtf>m<h8fzyT0th#P7ht?ZCnA!NTss!|uez@5RRN#>el+$nVI>
z@W{&W%FFS~%<;_4^3Bfj&d%}9&+^dF^3v1u)YS9U)%4fb^{cMtuCeH+s^-7I?YX<_
zv9sxtmEUxE)|i{&!NTpPsL;d2(7e9v+1d5IzwF1wx2vUWos>nDi!Y6V8N<SK)!C8X
z=A7p9r{wXW<M5#4@SfuCp5pDD;q08@?3>`~o8Rb}-shO$=$_f!mC@3Tzr1kL*o)26
zebU;8+TxSc+KR}^cFoU%&Cc<&uw0mqEU~Ov&d-F$$aBxniISDkwXlcC#=6<xjNRgy
z+TW7e-jUhekLvTc$jR@iqGZ|JkFBog*xQf2zwOuBjJLS!xVh`s*o@`wtmEsW*yMoL
z*Nf}&z3K0|)z^yM=aJRciPhDL-sP6m)QFdjE7;qU%*}nTs8!L=iO9rx!oF?G$b{0>
zhS%DTytZrH;g{Fej@H)nL!K(600001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0t-n*K~zY`?bYj3+CUHha0E^Sm0C-!f`UTD2bF{cA89RMMd}k&#45_8X`@6D
zMDcCwzpmmq!zFho^0nPBncU28_IB=WSEKnK57DI6=@B#ZI&Bj!nhh;RQ)^pWyU}cZ
z)A6?RoyGFL>qB>s)oQibdVBlq-DU@7w9Wkk{RS54bh=CqZ4J>G20jkDS^IJi4ZF;`
z8lrbO21iC&#g2{)nR@g!3&`nlGsS~(TRx#$;_)EO3xVtkGWPWA<#-<`!pC#oDzTG@
zm-7e4Cnl!^LHPV-`fD&4425UD%|;##g>e44==XU{gcqWVOA$^@mY1u)d>}fv62tcu
zi?6P&^D2@+DDn7(31MX;iI<(+h;RM?6?qPr6k<u`VluY5#Uwyj-DZ&~GD!iwmYp3E
zNvTN!?e11h8X!#~86`=e_H5Spl!p|M$tel4d9vA`ebie9IZ>Sj{rZg<Sws=27lu-c
zC{alo_Vx${Bq~Y6em=jiHXz9~0tdl-K6I!$8ITL~EO2y0s0Io}dif9vlTN=_Vvz7L
ziImF>GJZ_B{3m6ZPJE}8xcZ5m`Oke+!p|<Q#uX3w<m}?|il?`W$kJNndTTqC$=!&O
zB+G?jsp7x9xaAm4DY72;gNf4L^Q&7%Q+ieFtE0i*;iR56euwqsx`u@l8x{?Wqy`3G
z1LL)UA^xw%J#C$lG-cRqEC2uiC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$c
zGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLe
aHY+eSIxsNGmsP9)0000<MNUMnLSTY)!c1@g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1c0763337617f9ffe8cf13ba0f47f4cbc228c5e
GIT binary patch
literal 1741
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0e8a17d409343ea937e43ebb76e8ebce3cbee49
GIT binary patch
literal 1679
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
ZD=;-WFfhuORjdF2002ovPDHLkV1g+E(G&mx

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_recursive_union.png b/web/pgadmin/misc/static/explain/img/ex_recursive_union.png
new file mode 100644
index 0000000000000000000000000000000000000000..66952ea454e3703dcb08251bd2273193aacaeb28
GIT binary patch
literal 1224
zcmV;(1ULJMP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005?P)t-s0001q
zx!X2~!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_`zG-s;qsyLGzJqh@Q-%+2w=zwN)l?ZU(F)710W+4RoO^3l=q
z)YSCg;P-*9LUN@xnY?(sz3iu{=c=sduCM9E#_!9_@xH(9!ou#w#_-nG^mC;*r_X10
zsycS7JFwk!QjHmRt30ycc6zZujKETQu|cJ%<dC*(*yYQi#9oB3MRKDxqQ_vQ%44U^
zW`?#&cB(tB+HrWUKD6U`wV8gwk2a0LQ}pr4@a(tn?6&Xfw)5}D^6bU!<+1JKu<YWm
z@aVy|=X~nqo$BM9>EWC2=(Fb9mF3ry<kgbv-mJOnfA8zI>f)T}-<j^_vh3rp<kXSl
z(vRufs(PNOy6%GS>9pzKn&{q{=iHa<<FM@Dt>n{?<I#@d&yDHXsC=|Tyzqqc?Z)lo
zvGC}^@8-bj+^Xr?sp;6K?cuzZ%w5~PiSg>g@8!Sl;=SnCr{~qB=G3I@-MZ}IuH@B`
z;?R!h*QVdchTg@6-NJ+A(V^?#t>e*;;mwQZ)TG|TgxtV^<<6ku&Wz#Air~qK=F+3x
z!Ghesf!n@+<jtP$<G$?Py6fAx<<Fqx&Y$Ypwpcc+1poj50d!JMQvg8b*k%9#010qN
zS#tmY07w7;07w8v$!k6U00Ih0L_t(Y$75g^1>^xnCJ=y<#A#t-W?^MxXX4=G;^tu@
zQVTC1zknc<kg$lT7_nMNw?!Q63n)p*ixQGjKvG&pR!&|)T2V<^MOB)R<&tXZ8bB?Y
zTG~3g(t7#^hDOL*uzFF-*u+!=rxxs9)G#x*z@-Jd7o{w%tTk}?1-r1MjV;hGcG4gK
zB^fj<EbJY?TCiG<EyOGw9i6~>aJZKN(>M(aXP_REv;f1z+0g=bbYa!PVBrGNg6c&y
zE$GHsxMJD@@iq)#7-!*Tfnhl$q2L6P?j9KF2$E260x1tqFN|~qPAIs6hL^VmnqMFZ
z1!u;P^700z3Ljq(fRfOJf-}iV`hlc<{R0Anf_+0m!@?tc(LxL(V^~CjT<see6&(}n
z8yXj%km!r%atvE6lE7M$Q({uneADAIGPBUNWP^PHCBa&9z*=(i@(T)mi;7E1%h0u;
zIvN;W<rQEpm1$Ll)xI^gb@dGhEf7DUI$Fb`yrL15lAFL`1tk&D3{C~8j<(1F>Hy^z
z-{$b5#1`Myw)T!rge{O%fa+*?uJrBd&g`l4?d|KIFcHZw;8cL!(G0$mCYMfW^PM_v
z!t@zPwt!Osc1JV#*3O(YyWMxr+<EgCAhbYI0d_|-_%5vPow~?(@sgzrmSKb#c1Po|
z9FL>%0_={)uLY~4@dA8~9tHFV07F>Y9b_N@c>n+aC3HntbYx+4WjbSWWnpw>05UK!
zFfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GK
mIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTYtrHx7e

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png b/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_result.png b/web/pgadmin/misc/static/explain/img/ex_result.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfd7b5904f9b304709ac6aab37e24dcc06d233c7
GIT binary patch
literal 1320
zcmV+@1=sqCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002L
z$<UU#(67SKeXh~4!_bAX(Osq0Zn@x(wb8-K&}gpOjmqed%IIXO*vQS$YP8;l)bMe>
z<bu!aVX4@+#?YR;&}^sCf579B&FEmJ)}GYufxh8%tI?>y&|Rq4YRc_yz2s%c>~px_
zSfSIA(d>o8<yxZCm(uK6qSJ1{=25}wjkVEryyK13?q#joj?L+~#?YVI@LHwSTcFW`
z#^!Uh-Idtykjm$8yW%;y=Uv3<cDmqo&Fy`{<dN0xWTeqv!RRu#=5@;IZp-X#u-cN-
z?P09iK)mRBz~p+$>VwejipJ$*z2zga<UG3PRl(?g&g_)c?q;&xVZi4cujDnj=ViCx
zTEpph#prd#=v=(zEVboDyXJq$=zz%RWX9`=(d|CD<!s68Q=QRq$?2xQ&}6XOaJJrj
zx!-lf=SjZld&TESz2|<->y*ywR-w~O!0LI->S@2`Yq#KKzve-^=xegvb++DV$m?jj
z<BZ7Wg3arCyWyqU@K3knEo+Lw00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0^dnQK~zY`?N#Yh+CUT!A%sLMsaGh-LM_OZl!8#apdwKLmBRyy7FwkURz=05
z^{DN?-f#qVLv*HJI{o0AVTQN6zxUqnUBR$t`S+s2aTTPh2@R$Ze@_Uhr6?^eX#=!w
zkidu+dYpM2@p6b79vK~LYpmgw(Ik4&kDGzz5roD1dV(B&W5e4B`=q1wly#1~+^jML
zcsL#Z*85J~R5W&<&+Tu6-BT{7A|X|P;{qx^*y6$v?}zk~X*x7LLn!g#XXoaFl0t_g
zAIP46<ARx1fJ7H#9?<#k*9uEIb&ut;cg5uICm&DDQMi=5<os$BHaEGpPRal*X-K6r
zalBmum3AqcgrAV>8=EE-ir5<7j^*+nDazi&@X2h!2ImzwkBpdc1Oj$;KNWK&&*v{K
zHc_Fp2T>6_XL(<6MG=Sv0DC5#0-#mAvC+Nq*9v^kIK2l8vdFM%Et8knvxnJo1c6|c
z&4c}KvKHW}Vy0H@L~i3sM*z8wP6~1|m|;789O!rkC-wRX3IT({=^1*&oHufd5<hTM
z>Vx<$E{y1rNF{wq{1pVhD#p6C2Rn5oh%7L0oleB1N|4Fp`jMQ=#pw)z990@QuA5do
z%zYPv$i2ZQ&zL%%$z6BtR(}-AOZ|1ZsiIV3#aX_aKKpI?Rtwj<S$$bw@^{6#TF;1K
zr{3RVX9kpAD%Y_0MwLA*6vA-iVkfW3ieG7@w;>~pUC1Wk{1EpWn@(i<$E=y+6>NkK
z&qpAySa4-uHvH^XzT03OH><a{o{ehAyMj8w$h-WttQ&NZpV96kV4My?L0ABbMGMDq
z*$yScYY)=&)1fH`v<WoU-7VJ2U7}9J&`8I$z8yYm`gKnZK)LWFQpDw+wQo`T$%m=d
zzQj%A4ia8s<V50kDpFI}g$%88-LN9{Um<<~F?K%Tp#n<Vv$^~qaIuPR*#L_H0A7VD
z3nOkq=7|r|v-~&m2P92<JOAsn6#xJLC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@u
zGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<
eG&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTXtOrt>n

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_scan.png b/web/pgadmin/misc/static/explain/img/ex_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..396dfb4feabdd85e081bf8336304129058633c5c
GIT binary patch
literal 1320
zcmY+CX;4!K6or#E43U(&OiX7G0Tmtd5DXYmgR&F@acWZxiv&wwL>3hgrBoq~lr@it
zLJ^duAZWq5fw&ZkC=h`X77>I3W&snjFJvdI(x|^WbI&<*@7(X#z04qTpskIa4Gade
zJw!T4v82aGA-}XZR*;$vgTXU`4u=peEwhE|na4_cJ>&OMPHdC^GN?%#(geLygmo&i
zN3_g;wJ~hf>0VV*pQ>a+SI*I098_lwt84kXyOa8Y(V1&wnnwbI)t>@VyL>~#^s#Q`
z-ez%7n>4gTN@YnI!|M1RSrS`*eOyDod9RKoioG$+D)iJTAoVnDW}2o~Kn4Y*nFci?
z(4d5jN=PdLbt2HDg!CeCRtX6-W+}%-_OSp37f^A587`pV0tPWSt}w|tfRYQSEfXG~
z<pD-9I3_n5EVvjnE1@|h#C^#el^bWp;E2qi;{kdeVBi5}F*q#M8+pK-7&KX20?sR;
z1r;<X(GQCC^Ad1T#TXFlOnku12j=+L1qs+M(#`ULMG4q9t)1s%*%ntpODaaswDupN
zW=R4rs~Ft^&Eh0>X%f3E0nHYQkNqG4U%j7Mp2U7oF*>GZTDYnYlh_prI4U=^O{(k1
z<RH8(z;Xk43WXdD^Oa1*TAEd7Nz|V$N&2LK-gs{q?2D;G2mL}wJ?au}Fvk}SCEl-Z
zn!Q~=7IE=$y}e(yGh*kDXdI~~U{jVK(F*Yxuqnx~>1&_l=Y&sWTE`Nqyi*-+eA9Md
zK3zI6YurED-VlgxD2VgQq4T)o<{Uc52dBM4_ns4qT@<Dvt#*S#&6df$?z_JA^7Nd&
zsc>|3T=BIfA3pq|mf@Ygu#{_8Ub(bzb>X|nwg*!@UBbMkgoVpZr*Tf=$$yMQS7cHv
zJl|yI9uG%_?<;ufhl(nz-b3s9yRngm7!eBB**@c+iOV>**WUgtxir4qH!Q58sBY^X
z+t^lqCrqiOuN6j!FCm^Z-Pw}A4cEdyv#acY!)jFJxKh;d#_(duq1<!#u|_inUhRb7
zZuWs4?RmPM3Lb48C_72VNCv(-0Y~+p3u~M&y#z-hvq<OPO#~HPL|HeT-JBnDa|-Q?
z%<Wv4nUHu7scz!F?nr$`t8(+1oBKW|Pe5|!z>mbS2==EN8$unCzu)=|NpXJS+8k(y
zsf-o0Hs=vz(jedS7h1^)Nk6Egj#I}j#|fGRi^DszZaa6=0t<S~OgC$5;PxXZf91sE
zOWU(6NzTED<doV?9Qs3{X(;P@)9MkAuHxQkhHpV(D8foPn!Wy&AP<pDsLJyP%3oh&
z_cAYs?`!v|IYy!w<2r@-;@--2?03|0W>xir$OP}|H5oNdm)-MvQ!!rm(9c)!#r=_M
zBHea|)}|e>!%!27`lYDuS>)H4O$vGq?bkY|V%O_yDBIEKFR-P%ozXV=^u|^833oyO
zaZUL45Q3B6k&=}B<!$*W;*kcVXyLiC3y+ARgakaz>j~!U7+UU*(%Cey-DHB+7FHqH
zPj|}S-*ndrRToh|R<X8I9r-f-LcEN{x??i2OZ4-b(JqNaSpSxvc22t`6N*Rz>U)~O
z3XiIz)B{0V)^^ow^e^-{bxVdh`d7qN(nH;q3&j)u%b;lk?cGMHsP*lX<>$Ai5W`cV
zkEg^CP9(=z0_K5p$GN(DxZ>PHJa7d3E&?9!g4<2N;r5nqb@(5Um=t{~HtjQj+xH3h
axJ|%$eg^tVLmyfI*dZeMV8gextp5Q0yRj$$

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_seek.png b/web/pgadmin/misc/static/explain/img/ex_seek.png
new file mode 100644
index 0000000000000000000000000000000000000000..130fbd8f53ff5c2c757c8173eb62a1b604555f34
GIT binary patch
literal 1326
zcmV+}1=0G6P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=yH~!hN)yyjTym@HuUhw@a(tm>$dan$M5O1^6ka$=CkqY#O&j+
zp~PP6<(}%}obT$j>EN30=(FhFndjV>?d7rN*p%$xt?c5j<kgbw;jZi7t?lNr@aV$t
z=D^3Qb@1lE>fEaD<G$n4kK)jc>Dj2{(~#oMjN#0S=-8+2;k;g#B*DnK#F|j0%w%+@
zIG(kKuGw+%>WA&)VD95x^XP-`;92wMfbif}@ZVJQ=6#RAQ?cH30002z+A8STDCyWG
z>eeFb)Es@XL#xzmv*LH^)gSEB81mgwaGgbAl`r+=dg#}t=hUR^+_~n_qTtDi-^YgD
z#D(V5q}{`W+`xk0$cWs(faK1gsoHLy(PX^vg!0`^^V>}K<977gNQ<j%pw(yY&=l^^
z5bw?o^V&-A%?rQBx1!f+zBxuV00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0_90WK~zY`?b6v(+E5e+U>k6&Tea1i1BihN$R<*($RcX1MKFRGK$JxVf+$KW
zhy}F%>%F-wiB95-Go4N!ybt#&-#z)Ab9#DS&%Zx!2<}MUVuX;%<?>#luYX`*kQjU`
zlMN9=eZ#{e#K`E_*gHa$$j8UW<wXC)#Ke2z!{p?ojOaytm>8X!l8q66sme$E&ysih
zt7&qYC!kO&l?v`wDPTseR?osLMNt~iXti1mw7()hkpiNeo10UDQm51DL2WP?=7BPr
zOlHtpEEel?M1_c&BSs1+lgXro1&hV@3{im|lX;^N5wpo;(ZZt5X0wYEg;J%O;ZuCZ
zDHa^yu-ffQqT~y3NAec0qSvcYm3fLnRm^78CC#GM>VRdZ)43x4V<?4zPhsh%aJt-X
zk2q25T8%Zf8@mWro6YM4r`x?MMbIodjcv`s?sd5kS@roO$Sh8w!6_Vq6e}Lj+B#1*
zzIONK8}5wc-65M>-;p8xoPq5?Ah6xNgTWoxp=o*tc0-}?Ubo6-WNU*YN&h}F5MZKA
z00PlyGyuU^EFJ{<;NXCUP$H2C^W-qHdF1!+CzB*1Q6`E>EC|7PJWeB$N`)YtNT)MA
zA&*Z`6(sm7u~>_s_vus$y&p~s$r0`tnG|LTK25Qg$z)ESkQa5#FPw|RYPB!JQhOF_
z*>zaY<#Kt*vn-njQ6gLqkFQY3Lhh_sJO}ooRAReiUr6j;FJ6yTv>w?);W7ugV)23n
zwp6+*OAwdK?L_bUd~1-sJUh$5dGP|1D*;IgiByVwxm@mMIr;J89XtMch<EH7&xM=C
zF6@TGtyxq~;j~(<-oQ<*R=ep`xsCif<aX?uGe9#bCe)sFre3dCpxS6Q@1S;ne_!j6
z-?x#6<TXBM$LJK(U5`q=-l)P|v)QaYCdl9&YgGv`2__7gbiIy9qk#$c@nh0%Tq{c_
zMWvPEu9M<XmiBo`$6n9>cm4nf0rwvzb1L2d001R)MObuXVRU6WV{&C-bY%cCFflMK
zF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGP
kFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f_kQ{fB*mh

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_setop.png b/web/pgadmin/misc/static/explain/img/ex_setop.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3a9b1983b5b47c96ae910e73ab4260ae9c28b73
GIT binary patch
literal 1143
zcmV--1c>{IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002E
zxzL`V(1?rCc6!ljZ_&-ct;w>B#-wM%nNGrzJd>5te}mbYo#C;w>b<`0$jI=)!tS)S
z>Yk$FfrZ(}s&>qySJ}LM<k6by-Kg#6v*XQ`qNLELspq@A?YOz?x47%Bu<78)j_l>D
z>*J&8;hW6M%aoVjwzuoBvgxw3>bkq^zQOKonbT;Cw%F6E=-!y;+?VRzrRLP5*SC1B
zujqQE*JQ2NWs|?=+Lq|snys$rs;uaQsoGkq)?dWwX2$7un78KHmFC!#<=2zt){@q>
zb)U22NtVh}z2-Hy=1RThb;#&boz14Or{vX=<kXSo)|jNH<fy9WTeRLOv*bFs<#o5*
zUyi-x(~#rRkKMwEAh6_t&FbUOj<dAt7_H;t&Wz#Ai>j^WVVTb!u;hfp;!=vh;meBO
z#*ekO>Wt0lK!(EL%8BLApgM`e53A#l)9m2Mh~LPG-^YjE#f9R_n%=~P-olRE!h_qr
zgxtV_;K!5Guy4}DpWelaCqTqQ00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0xn5JK~zY`?bK^i+CUTrVD7hwTp9u~3K&4efQ155Dpjm4f~c)k&`L{%-n1>)
z8Vm%m{&hD%k|mfKr!(y*&NpUw=DfQ(dlZUEP3k|EQl-{twHmcbIry#98;mBC(V*AK
z0c6TNZLwNy_D3iJkj{ZQUHHrlPB<~gy=WGlv$#E8ug7AuTIbPOJx2O`et#ek@cVEQ
z(~p4#WO2zm9bC3Ac_=qPq43IMANT)2Boc{6p2Xsg1qngSR4_5mlTc_inS9EFXf&02
zhC3aSREyMFdQBwH*EcfBO%4E&da?EL)fS!|$)-f@i8MsEbNQXZ?%w{O1t(s=IdEa{
z9UyO)4`C8M+9{SY$0rd1ygNNTC_~YdQ=T+TwsVE|#Zvym-aY_BQK?j7P#cx`;~Y#@
z*NbPxQepRaGbMm(wdRV8r%``OjF8VqUpSKa`fZqr1GRJF!XOaM_w)$K<<I)$$4{|E
z?Yv%V7zLtrAHsyXEd1ig#_vn9Mg5AVV<tJ`7D*pe`=HruLPQdb$`$xve5NY^u4%^j
zfaCL-0Jv_EZNmdDS!~f_Sppgv`A=CNwrBy-V&I993`~e0m^z<{1x0q?S(Y2i<{%R%
zy5-);Sjr4PEwt!%S>{IaFeOjS2A<dne{2A{WhN_mVq*{45?=X3(8Ek!_M&$)_K+p(
zBEhYTPJ<wC!A;%vn9e=xqQ8_-b8vJi#oJ7y!L+k7OG)t;t8Y@1`X_Y<dEniZp!V7a
z0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFU
zC3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ov
JPDHLkV1j^qN`L?W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_sort.png b/web/pgadmin/misc/static/explain/img/ex_sort.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d46fd34beee098f997529bf0b7714c52232a2df
GIT binary patch
literal 1157
zcmY+Ce>l?#9LKkGD0JGb!<|bOQ+Me`PIBs@X131ubm*x$mvXaab;*yACb5J^<yUCU
z=?G6fnfz!u<i}*hkYy7xW3|nXjiOR_@wspHSATpy@8|t_KCjR7c|Ol4eIL!oZ1p#*
zQ7Dudg-rBA)cHdg8z6b}L>w1|LZ92`8{m!DQ{|nmX`=An**#5DzO7UKZPUMtXP6Il
z5fWW|pN=clXUJv+$_1mFs&#^Aj(62w4Qg7mCQzhhh$nO8bCLPQz+!<`3u9yCLG%bb
z?15U4$&kq)mBQ#~6BITAuO9LqLIsSB)IkpP_6i{rRI10&2s?LDwdh7Z8fJ@NCJm;!
zFd4t3ITfD?Q+k-tfKm+#6{x2Ho*jVkUqDF&&9OlR$ex0982Sc5B7<QifWi&bjP3!r
zaEsx0$?8cB`nK#iITTstVily{c^X+e5er>i<q%g4k%ho6fRJ1W5&*YE99$-0RE<W}
zX*u<}b08K&QxnwJgHU)0{BOZo5Q%sYYAS}-7f?P0MUzmdg?qD*qJqS6;3y$x4DvM)
zseqCx$W=qd3>dXTM?bXpLR$}rBv8e3)XBr99y8x4qpF)h&;TP34lIsGOB-m{TFz%8
zmqJJT(fm=Knjt1)R-Gi%qYx#33{uyzT`1HF4+_yMfZVAcm^py)vTU8m)18%`sKnhj
z*<uksYUi1eVOfJKPs>_|AHuUzvW>}&>XtFnD#12=5jSD-l}O2Xy6AEAsv_w~<-a~=
zR<a9*e9KC+;FgGmLH*CMbBgT*1FGqRFG(blkIk<5_s=(bzrrV0ONLT{JvvNXj&wR)
z>7XZDI#BoKl9GL|_MWroq;CqTDQRU3U&c7L*{b^OT#I7a=4*Z|__`WB7ft(h<#lmL
zwVU<juuHW=)EJqm7n3feC-+#%HCxw=uD|m{yYOyl`E(|KL();vVdHgcQY}`jeA$Kz
zd^GU|aTDfZM~Hc9Sy{U?g_H0ClY?C)4vFx^B<8H|$3?8%P+fzuPdEAthoRteGj^qv
zp2y-%Cb5#6EEi(d{<;x1@Ul9Xj(^`H-CwfeY>d+eFuzNvy3((g8#?Rm4)+uXRZ{0S
zek&Lj2&|vqTX>h8z4p+Yu5=*Wb*%X-%fX;6ZCeB97F{VJdvHeYIL!geO(qzY#fd~4
zgP|L%u1gYzNTmVY-#^*ix*}V#)w7h0IfJh~v<cg-uAtgBZSQdJ-+WSx^D$TOOB4TV
z&ougHZ<*gtEH4SK60NlsIlMIuW@DxNaj!)u>tp7<>2HN#d3gq@q^0Jug@L!_)33u3
zSD40j=<&?;yenlDmRu9KMFjKSyr<uxY0vOCm{D>(4e9y6VRvwCmgKfwx9qwK@bjUR
zis9q0OCHVF-XIovFSc|R*2(*Lx0j!tMXN^#U$e&f2a$)ekM=$o%{USr>i$P$C?Y6l
zf)l~f$=Q+M6yQv7ciHCd;_?IGCwBtDjc339GvLo~Mi}$-CxEcyBk-Zyo#6Tj=q}h_
PfdD9qH;q`i`*8CAb@5|F

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_subplan.png b/web/pgadmin/misc/static/explain/img/ex_subplan.png
new file mode 100644
index 0000000000000000000000000000000000000000..e13d7794e91fc0842702b0b34c1a9b61607dcf92
GIT binary patch
literal 1283
zcmV+e1^oJnP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002O
z!_bzQ(SL){ba>NdYtf>m<hHo#y}#_l#qYwz?#9UP$jI=?%J9p~@y*Wi&(QMG(el&N
z^Viq)tgh&<vFNF*=D4}*zQ66j!R@lN>5`V;n496cyz8c@(AnDc$Hlj+rEHy)MVE~(
zje!}GmC?n;b=BCB+u@bv?y2JJpyBGA(b0>*y>Y_8Zq(O`&Cz|**oW5HjmgV)&CP_q
zyl}CsSnBe)wXlcC#=6+uj;W$#*yDlM;eY7uxXQ|X(bI*zwrqx}WKxY8b*9HQh{34O
zXl={vL%r!uzvx%P>V?$sl-Tfk&g_TN?ycYR5Ub*n)$gj(YI2sLtk-VAk2du2$?)v9
z@9DJj?Z)lovFzcl@ae+p;+*H(mgLov>)x!v$huvZB(B<V=--*+(T?ies-Crnc&$CS
z>3-?jshY`Moz7yq?t<^;!0Fhh?&7_NyG?JLL&vIhi>qwE$G7O#rsmV5>)g5C#f9C%
zgW=4J+`xh5&Y;`Af8@=c4PV1400001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0=h{=K~zY`&6Md=+CUh_IT8h`QXz`eqXw2kLy;n)g4XJmdbG8avTB3Kp(v<$
z)p}e1_3p-?5Zu7nnNFV%o0)f?-@M1}Mx*)X((3dKs}!TxX;p}pR)f)GG8$jBwRd!w
z%`ZDUEmpIMqcggeuI?_I0%_~9**P5z(YJQL>Qz?8^x17DJq=;{x!wU~4cdV|%WIZ~
zu$^Xx5QDtK35c8yeo&)jELJMwVp*3&xINyX;bB4W`rcrMwbBt;yniI{)+-1?FfbY#
z<5dwki^CHb36sj4;gCDzP(xg7TRUOE_;`3?5(g$H!Vxb}L0qhf<BXVz0ua3qO*$hH
zHH5VoIL=<X3)B(ZiOt5=WWh)fsJ71<dhlKp3=LHn5NrmCg5jxYL1vmmz_>6q?R^LC
z+aGBz3y6${r7U>JZlAM>UNjEhXh=TF8Nd2bRuFzH;GTIO2?j%Mzk8N%fEdW$AV22w
zL@?s<g=QOEOV-}=<mI>0-wW-D+06_Mp*`e&BlRIa<9G3lpVBjaeSfJrI7i?75F?V;
zhba6A>5ka^!s61W*yk_H%U@$pB6W_~LOdQ{ip?)B&3~&x5><%OLCKO($?{#QQC=UB
z<Ren&Kr5@OYb)r7D5loYlSrKdt)|oIH6(6iGFem^Z!W)?&s9dPbVz|M5s8^hVH*(_
zo>gwAm@Mw(G_XV%g#!oHwzf7zl+6?hyND182=m3g{rzGd!crMCwGM*d8pU;Vu)TYD
zgz$MJ63OJ|-to!teyLO{m&;J$!1{@Z<P~R0Bq4++r@u~5@V88|)H+C(^;9Z*fR4`2
z&M%~1s%8btQZKa*97stu9$sEvoj**h=5i_>8k8**uFuf<<<-sY<GEC;&VjbCuSwwQ
z=Jy>1Q997kA*u1=_V(_Af+!tz^Qco}$N!-}nbr1E9_U2o0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1oA>oooOA

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_tid_scan.png b/web/pgadmin/misc/static/explain/img/ex_tid_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a6063a1af031c626802911fdca598983aff7d52
GIT binary patch
literal 1074
zcmV-21kL-2P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002d
z;PI2T-@Mf8{{H{L*6o?Q;ibal>hbyT^!nN5^vBrl+3WSI#^#K%-OAhV<Lvi^t=n~>
z)~3be%jEIR<?+4P?Qx#e)#LN7$>)Ek+0f?l*6Q@d;qbxV@5$rvYnjrc!{ox+?t7-#
zwb1FX%;=%P<Hg|a#oq4t`u(HD<awmm>Ezqh$F9h;kiMjKx|(3PkxRstJB*5mfq{XC
zhlh@ij+T~|prD|vtgO7eyv@zc+}zyo@bKZ`;laPVtDlO=oI}p1PuR6w-oa?$%4zD;
zYVFo~;L^J5>E!0-=GocV($dn%$jHFJz_hfqs;a7)nVF-bqn?|W>)wU&>3;X;Y4PG!
z_2EYK-Z;soXy4!8*4Eb0(9p`t%EiUS?d|R1)xgxbiOHZ?)2mYZ=33Can9k15#>U3O
z!^66|x{;BQ>gwv<&a=<QxxT)>xVX5tx3}Kg*UYkn%e$bxy}jh*<eZ$G=;-L1o13t(
zu%DlwudlDArKN?1g_M+(;^{T(00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0qIFZK~zY`?UY$l+E5sVm1q!Z0s&d05EXH$OJzw&f>9JvV-~<xtzpr&mJ)3#
zt^4}dJ;CwH5a3Q{crP>OnfJ;0@};Mzn|G656V%rsV(N#@1DyaC>j%>yg4)_VZtpi^
z4$R~na=AT7>*IZ1AL@sFZhUwc9|;5piB`Y>L|~&3j^krablejP42`#Fv4k9O$mI$;
zF%N;*>=WT2Y&Irr9@rh6L@)yJTD0r+VpD$OYqp`G5qH=Twobo1f&7qh2|N*)`RMCt
z5>^5=+dBb3rrVuclg!S|FML{Dj6`FxSe&F1G{YvB49;`QE2-7B^m-<<5!u}0xots|
z<ZO=2OPoHy%R7<P^ye?Td;4GaGY3ktP$(AJk|>G{P073tp-|<+t)p+>BR|ra<Kx}6
zp9P^(`c;(}RaK?3e4@NYvh3lhmf7D8Y$NrD70Xgh{acr1xh^r;=Ey0}bN}Z4ADn5#
z`7_Z35rLQn@fV!3T@cu3sYXRZOw?(q(U+=l1rYG!LIqTS4wb1%)v`)c*EjlN#riFI
zORLpVMG%CdCdSn7?sHM{I%^=7WY6#Nxs&Jv80AAeN<QWi_cvX!_a&yZ5Yzei!HOA(
z>8=<EC*^g;nl5r9OG%cY6BHS5^LNqB+sG4UmhG=%BJ%?P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$g8msoqyPW_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unique.png b/web/pgadmin/misc/static/explain/img/ex_unique.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb70480be8a4f49369d9a974ce9461284d1a4654
GIT binary patch
literal 1238
zcmV;{1S$K8P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002Z
z#KgUKcgI3Ps~Q@~u&}o&Ddc5l(9X`ss&&DSHnvqK%tl7dN=mayB!;PEQjHmZrCYPX
zru6a2@a(tm>b0~uF5<tRp~YT>vPSFWp6cS9>EWB(wt%)SEA8a5a-%iq-I&LARP5lb
z-DPF(<-exPW%BF9vLz+pz?JIUs&=Y7>DZ_2+_~o1l-8qo)LdNR&yAk7hpyUjc&<L=
z)RExHh~LMC=Fy_RTtmV?KjF-Z=+~yT<a*t~g5%7duOT6OvqIz1kK4U}xG^!fML3PY
zQ|HvA<j$bIJ3G^6X1VKs(35S+RaLw1gT_uymdstaH8rt4A-G~H%wuG_M@zb8Ws;TA
zW^2)iL34~eXwA;?y}#|h!R^Ar?#0IN$jR`_%<--<Hq^DqyuItEspqP!=dQ2mv9s#7
zx9hx}zpN}W)z$R0W_!S=%C9#);NbVg#O%qp*3r@OwTrE~yX?Nd?W<8{vvZEQtk|zV
zN|H5LgGPX=ey69T;D1YtlFETR00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*y&TK~zY`&6N9F(oh)3hs+U!xs^aDm&vUZae{%Mz>t6d6NpNgVwgoYFIh(3
zy7;emwsT-WPoD8Z&*S?eXP>?A`{nFI5QC)~(-9-qn4TPw8K(^79c_;qX;>yRGhrkM
zmgDZ;!yxpe#VVK0K;5Sag0tJFa13pkb~v0)7iDnswA^I|Fc`!t6CSVG?Df&|5A2Mc
z!yy3hc-(%<1rabC4w>YwJny3XHeUcC4=@N!Y=Y67XxiA1bfc8ZIN0SO&+|UgKRXvh
zUD&C47Dj2YfB}5L&;mV(@PZ&Ly2G|eBm_^E<{w2_CCX`sM~Fq1<B1`}C&Xea%&Iq;
zOraAtn&uS5sSu)=8Af8U2<cyQC6^Jh6isJYB}$L*I2jkk+%OUd1{d?;LMoL)BePGk
zEL;+-QB0IFnW0^bN?v7?8Vr}lB^C%K$n~|kVljbU#y!RTm7(NDzETiDp<uWw4oSsV
zYGtpCl;9o9;t`QtCtYGLmn$hZIa_^(yU7b1^-3X8DwULAxGEqp-;0rcUaP<81*7Gw
zBy{A<kv%cHxiuOAn5}W8+b#20LxQz!W_B9d5|f&{uUdPr_um+(w~{uGN$q#<KYaXj
zKp75?ByAo;$g$acEF)SxhLA%)^%){~kHK9IDF4aP7lhzE1{2$1^xKlu=tsIy%GLP0
z%U9sB({2~!v2K*jZ{34|T{`aYNox=7f;@(n{C?Iu7(mXM)eH3mdJKaAk6o%=pvP`N
zn!Bw|K76H)j6C-5UF2YX)XG2AV35adO6;r+Ja($S4C^s4@~?d5s&8bh#{hC(D_^Jx
z-eXexH}KeH7t!J|X}^=NZ1#fD;;{zEtA5=J<`#PF|I{BM#JXLtE~^v(001R)MObuX
zVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=
zI&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f(A@n
A@Bjb+

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unknown.png b/web/pgadmin/misc/static/explain/img/ex_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5d54f5c7b253978b96993ea260828a144395095
GIT binary patch
literal 1088
zcmV-G1i$-<P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-s00025
z!qAqu(2ljybgR*YvC*c#(8$fuxW>>^ozY{g+H$$zaJ}Pq#prd!=Xk~Eea7c~!sL0n
z;BT|sS)tRx%FtS*)o#A!TgB^2zUV@{=s>&YKf2~ayXQu|=uW=pS;FaL#_4Uz>V?ql
zgTmx#uG*ix&}^sCZn)r0!09iw<{+@-AFtyivE(VU<S(@3G`8hBxaB~(<wd*ZOTFe$
zzUOPl=~<%FSE18r!{{2W;}xvqRlw(3!RL9*>ypmuTB6fvyyGgg<`JvnU&H8?(CcKb
z+6}7WW5noy&+LiA<Yc7LR>0^9sp2xV<VL*aXU6Do$?BHZ?`E*vFt_Gl#_Mpx=3d3>
zQNZYfzTthY(P6#i1*qX$pwV}|;(pHSdCBU($<S58=^e1-60GB4s@Sl@&}p>ZUZ&P@
z%j|o{>5S9ub++Dj%<5;W*ml6=ipuD1!|15M&|t;sdd}^2y5K>(=xE66YQW}z$LM~@
z=wip~XUObp%Itj4?KV3{{{R300d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U
z00H_*L_t(Y$L-VoSJF@z2k>#Zm$&&66HySyHefJJgw$z(fRdI186mP%OejOc)GU0-
z>Thp&cTUGnWA_LBjqi^;_jC5#=Xowg_45ER0W(yCfW=C5-iGWBdSGy9_=%GlaUqi-
z7)9=}agTSxH_7@rUI+w3XtcKw!x1Su^>jKm6Hh#wotx+6KyX3qS=8e5Xfl=jOVUex
zCY$SJ(DF(?f1kYIGpjlM+Q3>O|MFk*N?zYUx};D{l{E6&w>hhkH|4iEy=IG*tr8}&
ziR$)Hxu#$u2i^f4va_4qyCc)=fD6L<KCfW%UbnKkRKg@awqsLAR5&PNlF7pVi-x=T
zA@#A_G0)+r?gu!`8IsoWZc$>r-g5o!YDdRuOx8EdI)ya=f(84cqtfc00Its*zP`{t
zvGm*-tI5)xw)yaY^JDU5HB1@DiXyUq6VF9xpNhu9hUnU)HhsCxm`TK0GodYv9AJ>K
zpwO8V$2+zpw@(TFp)QUR#cP=s&KF1o75*B>n>;TB;RT6Mw;+beHz%@@2+C4n@q3xe
zxX1(rB0fx-PLf_>Qk9e@SL44kfRHrYjx{x*Q0S;ZNGK6#A=nd5v?4T3cRC&Bgw()6
z&n4?oZ*z4HNy+sL?wE4ZCuHQu?RfR}=da`6SyFNV?OMnlOFxg0KS=w20#>Z+^Z)<=
zC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!q
zSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMn
GLSTY1nJ@bQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_update.png b/web/pgadmin/misc/static/explain/img/ex_update.png
new file mode 100644
index 0000000000000000000000000000000000000000..a45c53f83a0593704855bb8488871e8e8c9be701
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003#P)t-s00016
zTU$1W!3nA2a+aZzl9IuXHZry4b*9Hbyy(lKSW3X@*Qid%s&!q&>iw+&W5()h$LMIt
z>~72Kb<OPByMEiff7`u@<Gpj-zJT1nfd9QPd(rRQ!GeU*?%l(Kgwyce#Dw0(h2F-7
zjMVPm$A{m@h>_Lq;K_;M%!-%U@#D*$;?9iY%%0@Up5oAr<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp{Fb>D#L7+PCQ5nd;rD?A*BN;F{~+tnA&o>));H-n#7It?J{P?cclX;jZoAyXxef
z?BcKO;k@kQu<hls@8rJi=CbbQv+w1=@aDkp=(F$XweaY|@ae+v?6&di#PaRM^X<m-
z@W|M;qjdlP00DGTPE!Ct=GbNc000SaNLh0L01FcU01FcV0GgZ_0007=Nkl<ZNXPAy
z-B#LA5QQ<eL#ah;5tS;UMbwA_5<v)|fIxsm0YgCuG)2mv@BaclIp+XDLzfq>)m}Ik
z`zl|u_nwKx@;3iqJ}~+$R5OHe*le~9W_M+Eb)VV);&7ZYr@MQ57tF=s@$q-Y6tOKY
zFWZ<Eq^rzltJUgYHW0qY9ImfBj+s~b$~)|Np_(D^I2a5bC)(@vMljMeZ3shERr^4m
z0k9j9!To)$N40l*d;0*Il+U+8O{EeF5Mun>Zl6PH7^x9N(m>1S^C~n`i3##!Jnm*;
zhV*LqVXP05gphXrI;BEA5sz0XlFizcG0d`H|I-vdaf)Ui`bxFjrCB!Z-`K&F`2_lG
zW8HeL^u3$4uQLXA^nuZXrj20OZmUD+*A=A;U0IeplNZl1u(P(dwqr=q)KgrQK@JCl
z;>^E+p@=3)q}Ws)l=#)94014dCK_tO3=TIzaIq>bwt5*3%TPQ!V{>4cXY2w@vpi4H
z5p%f~7?%#4`cmO#jZGLtp*Uy@&3d0|w^~NK=oCe<1FcLZ-GKRfgiQARl7;%8Pr;%T
znUf=*08efhU<o1hL;@WKQ8^qA7C~fLHUsG+pZ6(3LA}y4RZ?t@Jvm83((Co|<_PjH
z|Dvj=XrUD8WCI!k&sR*5kA}v!pSNfJ6<IEJ=yr%Ul7estl~^=#V{VN2CpSP8r8wXr
z>T$a{bA%j*5fQuxo)|>jZr1{YRBA-{1CJ*HoZICJnIoNCF4LOJQi(<*B`9&BQ0dhg
zYo(KD3q<dDK30Jnj^of*LV-ZR+!#3wqPz#EaOo)mPN(zS97+20!yp>N#m*4FIU=Af
z#HdEyIudfZoB=prjNI`t<e#W9MZxd)7yi=N)=U1%xA~vu4`}JQAOw$Lx&QzG07*qo
IM6N<$g4z^C1ONa4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_values_scan.png b/web/pgadmin/misc/static/explain/img/ex_values_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..15b5ab4079cb8e2c015b4f5f10a3dbf8aa6410d3
GIT binary patch
literal 913
zcmV;C18)3@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-siMiW0
zh{12o?m@ljMZW2E&hL59?@+?(R>SIp)9{Ja@MXyCYRT+x%IuWb@tNB3d(Q2G(e0$&
z@u=SNj@0gw*6*+3^985jNWbYDuH#$9>o~XNP`>9?z~@=P=Z4YkYscts$mx~W?|Po8
zrNQK8meIkFHu36*?c-qX<6!dYhx6)(@8e(Z<X`jahxF@*@Z?|d<X`pchw|iK_Unh?
z@Av20E9lxQ=-Mjk+bZnaD(>4V@7pT#<zLt8_37Fw>)R^r+bZweD)8JY^yOdH>GbpD
zU-ji*_v?rF>xbLDiPY)z_T^vG==1U1D)QVa^V}-;<zLa~^7Py)_~l>q+$#9yU-|2Y
z`s|0!=JL+v^7-ap`|O6y<?;31D)-$g_}wY`=3mR?@%G&+`{rND<M8?2DahmS`Rs@M
z?1snV@W$fr{N`W%?1shR@B7{<{N5@3=3n>SDf-<h{^no8;O_n2DgNw+z~An<+wA_{
zDgWkQ)amoS-tDc{=`>ap0ssI20d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_
z00FH@L_t(Y$L-b0a)Lk*24Ek?nFomRW_gGUK~X^z7hFIQqoPPyTuOQNU}g{;#Yj~u
zhm?Jq%hywM|1~5M&-$-bf~P9QA@C)YD!#&4B$dhJ^6-HRlK><UDZb@$)Hi|e6h+aI
z7lL#eAd=5jtC&#LT8)Xk5M;BM1g#-eV_6OzX@=ukmluLU0gw}e6wC6MaC~zhM3C?#
zXpU=qVA33nA0Xiih4FYO5~N%P_eS3qM6pMZN(IEs1gO_R%)p!pq$!4J!B~W0dA{*T
zU_hti6P|CPal)1$2<_;=bi0@Y8wgGI=I8hOm;|k%FdTl$=-8HJWkw8nG`bFqIFuv>
z5m{ALg&>p3bzpku)=*JRlO)sW-M}cOX=((S&+i6irfAxdAd5xpz^qoW1LG6e7Dc)D
zP+)8u6H)Rf`%_@fM3(#wc;Boj%jm#tw?0u-yn)kXbuBtDXA-pbh^`yxxHJr711}%G
z-3~o|;r(g)dX0&(b{s2Az~Sh+#{;pMQ)_F3i9?ViuwB>2PwdUWSk8WbK|JGSEO{p|
n8_Tjb@b)wQYk!?*{c(K(wSJimSdJ6(00000NkvXXu0mjfih%ky

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f858be1cee3f5d8f2b67f55d730e6d3f43e9b42
GIT binary patch
literal 2021
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png b/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..b51e0b3f4c61ba166b69bd7f32cb23dc8428429e
GIT binary patch
literal 1965
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exclude.png b/web/pgadmin/misc/static/explain/img/exclude.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd62eef6410d9315857d2e6d246770e8b65b8f47
GIT binary patch
literal 725
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47y|-)LR^8|wi}<ymTvVw_QrY1
zLC3?d^*UDhFWLA1|9`)wM`KREbvXQ5A-=(6%9fR9ZhU_6^4^pwS28lLWMw^=HS5>=
z_isLZP1<@%wQ`Qjk=K%*nNgG09JqAnY+~Zm($d$dsZT2^E~KQ~e)?Lsd6{J1L`ko#
z8QYH^XzhAZSorVPuP+-md|tBT-`~HFYif>6nkJPuNuqp?YIOD56Ib^8`rV6+d^K<0
zzkmPUtXQ!pGFqZ!hGg3swe*g4XK!wIaz5qe_HfFSrwbQu^AC`0S|K@Qk7Uk-*~hPM
zEhsq<82I=1?;BI6UhV1m_vg>S@bIm*jgk|0NG?2Of9R!T^@7Cpr%znEwc5~Vm%sly
zUEQ5NKC5l*c3rqFx8Q{2nrn7@9?A4<w%>5&&c`3`Z{FOOoV;9Hdxfs<!JM4Ox9?6m
zenWfBW$A4XY?hzYUUH^<`Bv54PXhWDq@H_cwegx-ao6F7A05CjWh@Eu3ubV5b|VeM
zN%D4g;b^-zwF=1LEbxdd2GSm2>~=ES4#-&V>Eak7aXC5R07H*Y2Gbdx45l?XZ+LiQ
zWMmY~)Ws(}c=qt=V{riyAu&Nw;pq&V9$ucOPn<e=Qd>hyv$&ZhB;@K9Q<JS*N=v?e
zImpD;=5|bN*|M}_&%~xBFluK@M_UI6S4XqEt8Zx7+`W4C?)K%=xA(7?_c&m$V4z{4
zVq&6Wqh!RTA|NX)Ek1w3j45*_&6>t1bmBw`i*--4qPDWM%7n?><t2V{=CjN{U1V)w
zV3-jtUHUai{xs0(swJ)wB`Jv|saDBFsX&Us$iUE0*8qr2Lkumf3@ojTjkFEStPBi}
hcbK)IXvob^$xN%ntzp~MJ}aOG22WQ%mvv4FO#q89Jv#sZ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension-sm.png b/web/pgadmin/misc/static/explain/img/extension-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..432d2f44231c1f88e787091060efc5125d80ef64
GIT binary patch
literal 423
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}QGic~E0BJDv+cp5$Qv6qzrVk7
zW3&9@vzBik`G5Ho|K-)uTicc2-1GkNGyU0x%oo?3fBedPak1gnHlr`^;(z|izO~Kb
z%lm|1zjI$+bN~H2|Mm{mcMpR9{3*J*S>f3^`;Si}e|^pQ`={jETDdE$#Qyy)dv><$
z|KAGswyocQwlbCk`2{mLJiCzw<Zu>vL>2>S4={E+nQaGT<aoL`hDcoQ?f2wsP!M2o
z7l{eDQ+~(c|9@BE84^1Kx361bY|Bu`qQb-J62DBxZ_<p5BE`pwna}6wxpa1%>e!Vz
zahdU=*X&ESFOpJE*&;e)`qd3*DpaT5u9JDLS%3P((k)@<ZbaLzV+h!Of4=?`KK92q
z=3fE@l4^--L`h0wNvc(HQ7VvPFfuSS&^0vDH82b@GO#i+wlXo*HZZj^FqrpFZxxD$
o-29Zxv`UBu152<5plTB<12c$*Q`1A&05vdpy85}Sb4q9e01}k2!2kdN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension.png b/web/pgadmin/misc/static/explain/img/extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c533347883fc1dec73bcb21b32fc54e3c31732
GIT binary patch
literal 996
zcmV<A0~`E_P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%`cO<%MQ2zLf_6I6&9|zdd99>-@a*2m!Junh7N)|bRf|-bx|zVboSKha_w(lG
z;mc-L4XVVcQjb)R!IoN>UTm6cdTlP+)48aeY_F(+!qvh)fIq$I$ak@QM1Demb2p-y
zZNIpg%*U?Z+|0eUmR?Q)@#@yLtA3u9XFhv8mdv0tb~C!Ok)4xckc3Wsa4?*aUD?pH
z@afX-<;s|gP-<HecxWLITMn+{ye)Dq{QLL&`11Mo?Dg>A^X%E{;lk+My5`xk<JGC)
z(V?J^Pa0qr9%URUZYXJ}aH`|EXsd7=V;MkvKHbcisFznYY9*e@qsi^kq0p+c;k~!#
z!?5MPqv5li->#6?p?1NAale3n)0NuDkgb|nMQ$vEwur;)&X(P%klLe(*qwsbn0(Ze
z&%lqBg+*3S1ZY?dnutTImr{GNfpyZ2etkM9XCNqTCtr9wihVOAV;iN_u6D(TK#xcf
zTn`v!7(bOtw4`b=XCQ>Lht=`it<|(#mtSF?W<QQcm$;T>m1BZ;G0*SUx8%WGt7&4B
zV3D+tb8RIwgF3Luu#bN^7G4v==*yVRp-_@kjmeoSdoaG!zSzWv!K`UHUKC!MVuZSj
zaIAJVdpAyF9vxyClh&g+jY8GGe$u;klYuouT@*iVD?w)>Y;7EsekVC?DRR1gZ_tQZ
zyKv99ZOyY~z@}84hBK{_JHn(!l6fLVbTnhFZfMSfV$6Lul0qhGC2*Z?NP<TvhBL~q
zUB|0c#ivccpg)Ro98G#WT*`P>$a6}(W;2>aI;l}Wz+t|fIeljlScFSfreaXWZ%f2#
zMZ#t~zg?k^OOAUphI1*4cr3V}Qns8&igY1@Y#D@Y7H?b$T8UCtsbn_2S%i2!rk!)h
z!>EaMCcvamyR?(W!KK8$qF<3+9BCaAUJ)8<9X6LmEss1DV->ZTL3e2=8EP9)hfaum
zLy>wXERsEdsDEo(3zmE$ex`jsyImG&7=LOOfol~TcO*%INEmJ%7i}B|YRn1%0004W
zQchC<K<3zH00001VoOIv0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW
z0~-xG4!&HBF8}}lR!KxbR2b7^U?3j2xS;VvLXbtwTz~>0W=QhVA)yH_FkL{}1;huk
zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<<OZkC{A-h<6}A<CjbDaP8qDe
SR_4e60000<MNUMnLSTaX-^L37

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extensions.png b/web/pgadmin/misc/static/explain/img/extensions.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed7ca97a33ef595f448b8621168d531f86d51d5
GIT binary patch
literal 1017
zcmV<V0|xwwP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%_fSk!MS^xZ(#^N3p?R&Od+_Yu$ibj%T^6RorB#bmn!1_5x}2JiUH9|m=i$p{
zRt>7esZx(rkHMB&m|kp}YkF-i+S9qHoNTYCfx^|oK7c>H>Bx7neMEjjfO9vZnQgzg
znaszo-`vc-ww7K_0rBeAwyS=gm1jPCJeJI$Gj=n&vyq*XV~~VSeQ+?GkzLu)v+(KC
z?&Zpui%@D?5_o7K5L*te<Gd|$E&TiU`}p$t_U!fW;PdR+>*2!a-MZ%4vg6gM;L)L=
zk53w479M3BDQ+lfsBo&|xoE3!8)F$jd_LXGn5dUmHEJcE$)m~b(xK3*v*EqB=fkk&
zzN6u@o!_pI*P(X7g>k=tf76xP$dIj?SVe9ugSLpn>&}+lsF2#DiP)Wj)|h<Mlh449
zm4!uCPy}dL4Vs8UtCv!Hv4M5cjedPPC}$ujZ6{xNJBocXBx4(;)vk8Mhd_@=5nK-#
zWf(t|O0=YEF=rrzvxn93+^yBLT$f*Ao@PIeN0+#kWR+urcQMcJ*SF-sT&rnflVFjw
zkaKM%G=n;@$*_-qITl_M!syGG&7n||RE^1*D|;}$)4tfmhrz6AIbIZAnqq{yi*T%V
zHhVWtV;&u18I#tdIE_NpzkbrYc9VfMLtPX<ZYx1&B5Z9Om3}8VZ7FiPes9o-TDx%1
zw{6X{Wx%FXorW{5k~_krMv{3VMsze|t!`+}gJR5mHj+XnY9(-;Zb*VhCx$c1uU*Hh
zRmG=Gz@R^havV*1JY33nR>*Toyk;|+MLMZbK)_+XojHAH5m<yvR;FT5#&1i+YDL0k
zJHK6_k4uhwGlp|1i+C)!pi;J+M~ZYIf@~RtZ5D4_30jF#R;gq*y;+2KJ*J&=$it|K
zbtb^1PrI~}#=)h;zM@}|T^wm05?&D+Y8^J0MJ<m!6k`>&nL&4HC>d%SP=`*4d_$3X
zCoGaZfT(|KS__tZB7UZQKD%8OXc&KL7J+LO8+Rm0f=C!{9T#mJ7UDFE00001bW%=J
z06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru+XEXB
z6Cp(Rbcp}}0B%V{K~xyiU68R2z#t3+nS(tt!3~hY^vGg42FL=`EENNm0{M!6-y1;)
zQxFazvL_cK*b<_okFg0d8Hc#V=Eh`)h+yZmplehnlNZEdtgQ@hX0CNeIWEpxw!oyN
ndc~tsp9TlidOzOC&-)*|KH)3pD2ngr00000NkvXXu0mjf<KNUz

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable-sm.png b/web/pgadmin/misc/static/explain/img/exttable-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..acd43cb8abab6ef38daf34ff6d7544153a11dacf
GIT binary patch
literal 612
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47`X#{LR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)^XY$8$A0G@_?f)(TiB-0o~u9DE`4h_
z@3s2Om(8dDdI7mh-fGNz8JxJ$(RuCl>$mUSyLbQo{YQ@;J%0Rn>9T9)mKA9e*Bv-;
z!q9k*y7s}c^63vAJOIkOd8|>@*q@f(clYkyWy_XDM0Trbo^<nAscUdhS#3sp`^5F@
z*S~u8DlvJNy7pxi^{qR09)J1r<?Gk4&!4|-WV%sNW#!y?Ya^o8sA?W7EL!yO<Hxse
z-}X<~s-}6=(71d4{Dn`SK5cATs;+%VU9<bdiL;%Z6I)tVJ$drv&6_t59=vw<+ErTC
zQw{Rh4~>~Gc%JJ_00swRNswPKgTu2MX+REVfk$L9koEv$x0Bg+K*j`57sn8Z%gG4}
zOa@Af9Sp+8+}hIC?Ck2|<}MBG)BEEc0z4vIL{>Gq^Qb83DJpS_PM9=p;?&9E0U<$c
zTq{<rTA7`ZmHE2Bfwko87hay;9$%lc3z#+)6+IK17IrPoEP&B6+Pa$ET|Heqd_}|T
z-R;Y#Z{6O%UOwc2y~2g=!)h89Dk9JL?rkX8V67z4`B96(W2)e}i60vRfNoPQag8WR
zNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTd
cHGouG8JIydoSGiG2B?9-)78&qol`;+0LVQH?EnA(

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable.png b/web/pgadmin/misc/static/explain/img/exttable.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d84035feea62d53d457481178b1bcbe9622f856
GIT binary patch
literal 793
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47;6K3LR^7d#i`GGFZ`c${`aEG
zUw7Sjd-49u`;VVLefIp>vuC#+K3#L}dGf{=db3}2pa0)_=3m{3Kc$C%<?Q>BxZ_*c
zrqAAMJ~%9YXTIQ#?wnU@GhViy`J27(hvTw$W((iw&U_Y~)1HyF{qEhn_wL=hfB*i2
z2M-=TeE8_mqsNaQ8yXiV>iW%}zd>1TV^GL$BjYL^{S(WU9oVt+prXnmSNFZkm#=*C
z<jL~o%k>TF^o@>PyZ-vst5*{zPLflcU|_QU>a|B#uU=JF?bJ2cziipY_3PKae*OB&
zm8%-sEqaEB)ikG?m>;=w=lPpAZ{EIr`|{<>zP<^HDjPJkuS}Y>_5J(zA3l8e^y$;P
zckfoNT)BGn>N#`fDXPqIbUAEeo3EjBs<&_JrcIlkKYzY=?-6CSwcft_U%!6i?2@ga
zbAH+K$Dcoc{`&RnzI`WD)Hj5LAKkcd)7Gt9lT#YiwN7;R-um|K+wv9LmDM(Tcy0Rl
z@#D5_+u{=nRW-JDc3)AL{8UY|HzVudq)BroPg$ri@$mwIO;dmoz*rLG7tG-B>_!@p
z!&%@FSq!8-z}W3%wjGdh+0(@_MB;LCLV^n;4-Zd|&l#OHId5d<%!#>Uvqyo^u8z@B
zF;Otku#nSp1DBm9qhscRhMqMtE)H>yfu51Bp}w)6&cZqimabjQ@@VyoS1+4cMR<97
zd#?KUoIRtfbCAhvS=cqRZDrs1`uxr%FfOPQ4>vF8_xIM%-_X$2-@kPI{CbCkfC7#P
z2U8Or8zmzxD>XAS4xIy%;`1lWcrs_sq*>GW#5zu&ICF}R^VG?+r}Y`QMZ`qYkF!mE
zGG*G-@CFvG)vNY8H+eN(zQmoCbx`Y7z#e{vFBY0Te&TE7f!<Rsag8WRNi0dVN-jzT
zQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTdHGouG8JIyd
UoSGiG2B?9-)78&qol`;+0Q{<O$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttables.png b/web/pgadmin/misc/static/explain/img/exttables.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5dfbd454ed33727bd1deeb87c62f8671f6a5a03
GIT binary patch
literal 662
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47^MPyLR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)(1uT&ufDx|_ipp)e^tkR=O6f)yz^Vw
zrq7<MKiDpPYdG(<`plR2?%iuX{nra9vgEDdjF%cSU*5le-z{*Hjoqv(SFS&L^yu;9
z$BCh54YV)SY`ps5!GohmFPT|vP}AIAUOws6@q1>vXAHE?^>wU&`SRt%hYy!6TNV*D
zT}|t>v;9UL?W?Amt7goYwSN8jSFc`8IQ7}h=Aef5HBHT(=Pz7){rdI(-PetE&Kqf+
zTC?J?x9>_dt@Dk|J3fB=`1bAF+~nQb+SeRSH>_B(;_1_;Rn;?8HFg`BEWUQ_zJ=bg
zri$H9o;-Q;=FOu=PwX9+YwPW<2KnxXa>IqjgMEvDp~F}b<QL4~@a#q!ki%Kv5m^kR
zJ;2!QWVRiUvDwqbF+}2W?0HwQLk<G27xhdW8XmB4zY~jTVks}b`QPk?8Z+O@ht}mE
zxDUR+@ZDiy$V$_wu1ha@gBJAuXUmw$IQdLNWLm+2wv7?FW$l-rva#qG$_eeUt7l;6
zYfsQPQ#3I{XWjwDPG7CLC)J97I!!#j(8XtG(AulgXTzi?C<q+hI5%(o`R%TodS@Ig
zFq!9*{+X5YhEDkFv(xUs<#&JSY%y0q+H23fcl??hCZB62%C0l7Vc_x-dugW}eiG<>
z)e_f;l9a@fRIB8oR3OD*WMF8ZYiOivU>IU#U}a)#Wn!XjU}|MxU@=ow4n;$5eoAIq
iB}9XPC0GMUwUvPxM8m1+p=*E|7(8A5T-G@yGywoZHZsuw

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/js/snap.svg-min.js b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
new file mode 100644
index 0000000..6567d19
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
@@ -0,0 +1,21 @@
+// Snap.svg 0.4.1
+//
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// build: 2015-04-13
+
+!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g=/\s*,\s*/,h="*",i=function(a,b){return a-b},j={n:{}},k=function(){for(var a=0,b=this.length;b>a;a++)if("undefined"!=typeof this[a])return this[a]},l=function(){for(var a=this.length;--a;)if("undefined"!=typeof this[a])return this[a]},m=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=m.listeners(a),j=0,n=[],o={},p=[],q=b;p.firstDefined=k,p.lastDefined=l,b=a,c=0;for(var r=0,s=h.length;s>r;r++)"zIndex"in h[r]&&(n.push(h[r].zIndex),h[r].zIndex<0&&(o[h[r].zIndex]=h[r]));for(n.sort(i);n[j]<0;)if(e=o[n[j++]],p.push(e.apply(d,g)),c)return c=f,p;for(r=0;s>r;r++)if(e=h[r],"zIndex"in e)if(e.zIndex==n[j]){if(p.push(e.apply(d,g)),c)break;do if(j++,e=o[n[j]],e&&p.push(e.apply(d,g)),c)break;while(e)}else o[e.zIndex]=e;else if(p.push(e.apply(d,g)),c)break;return c=f,b=q,p};m._events=j,m.listeners=function(a){var b,c,d,e,g,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,g=m.length;g>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[h]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},m.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(g),d=0,e=c.length;e>d;d++)!function(a){for(var c,d=a.split(f),e=j,g=0,h=d.length;h>g;g++)e=e.n,e=e.hasOwnProperty(d[g])&&e[d[g]]||(e[d[g]]={n:{}});for(e.f=e.f||[],g=0,h=e.f.length;h>g;g++)if(e.f[g]==b){c=!0;break}!c&&e.f.push(b)}(c[d]);return function(a){+a==+a&&(b.zIndex=+a)}},m.f=function(a){var b=[].slice.call(arguments,1);return function(){m.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},m.stop=function(){c=1},m.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},m.nts=function(){return b.split(f)},m.off=m.unbind=function(a,b){if(!a)return void(m._events=j={n:{}});var c=a.split(g);if(c.length>1)for(var d=0,i=c.length;i>d;d++)m.off(c[d],b);else{c=a.split(f);var k,l,n,d,i,o,p,q=[j];for(d=0,i=c.length;i>d;d++)for(o=0;o<q.length;o+=n.length-2){if(n=[o,1],k=q[o].n,c[d]!=h)k[c[d]]&&n.push(k[c[d]]);else for(l in k)k[e](l)&&n.push(k[l]);q.splice.apply(q,n)}for(d=0,i=q.length;i>d;d++)for(k=q[d];k.n;){if(b){if(k.f){for(o=0,p=k.f.length;p>o;o++)if(k.f[o]==b){k.f.splice(o,1);break}!k.f.length&&delete k.f}for(l in k.n)if(k.n[e](l)&&k.n[l].f){var r=k.n[l].f;for(o=0,p=r.length;p>o;o++)if(r[o]==b){r.splice(o,1);break}!r.length&&delete k.n[l].f}}else{delete k.f;for(l in k.n)k.n[e](l)&&k.n[l].f&&delete k.n[l].f}k=k.n}}},m.once=function(a,b){var c=function(){return m.unbind(a,c),b.apply(this,arguments)};return m.on(a,c)},m.version=d,m.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=m:"function"==typeof define&&define.amd?define("eve",[],function(){return m}):a.eve=m}(this),function(a,b){if("function"==typeof define&&define.amd)define(["eve"],function(c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("eve");module.exports=b(a,c)}else b(a,a.eve)}(window||this,function(a,b){var c=function(b){var c={},d=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},e=Array.isArray||function(a){return a instanceof Array||"[object Array]"==Object.prototype.toString.call(a)},f=0,g="M"+(+new Date).toString(36),h=function(){return g+(f++).toString(36)},i=Date.now||function(){return+new Date},j=function(a){var b=this;if(null==a)return b.s;var c=b.s-a;b.b+=b.dur*c,b.B+=b.dur*c,b.s=a},k=function(a){var b=this;return null==a?b.spd:void(b.spd=a)},l=function(a){var b=this;return null==a?b.dur:(b.s=b.s*a/b.dur,void(b.dur=a))},m=function(){var a=this;delete c[a.id],a.update(),b("mina.stop."+a.id,a)},n=function(){var a=this;a.pdif||(delete c[a.id],a.update(),a.pdif=a.get()-a.b)},o=function(){var a=this;a.pdif&&(a.b=a.get()-a.pdif,delete a.pdif,c[a.id]=a)},p=function(){var a,b=this;if(e(b.start)){a=[];for(var c=0,d=b.start.length;d>c;c++)a[c]=+b.start[c]+(b.end[c]-b.start[c])*b.easing(b.s)}else a=+b.start+(b.end-b.start)*b.easing(b.s);b.set(a)},q=function(){var a=0;for(var e in c)if(c.hasOwnProperty(e)){var f=c[e],g=f.get();a++,f.s=(g-f.b)/(f.dur/f.spd),f.s>=1&&(delete c[e],f.s=1,a--,function(a){setTimeout(function(){b("mina.finish."+a.id,a)})}(f)),f.update()}a&&d(q)},r=function(a,b,e,f,g,i,s){var t={id:h(),start:a,end:b,b:e,s:0,dur:f-e,spd:1,get:g,set:i,easing:s||r.linear,status:j,speed:k,duration:l,stop:m,pause:n,resume:o,update:p};c[t.id]=t;var u,v=0;for(u in c)if(c.hasOwnProperty(u)&&(v++,2==v))break;return 1==v&&d(q),t};return r.time=i,r.getById=function(a){return c[a]||null},r.linear=function(a){return a},r.easeout=function(a){return Math.pow(a,1.7)},r.easein=function(a){return Math.pow(a,.48)},r.easeinout=function(a){if(1==a)return 1;if(0==a)return 0;var b=.48-a/1.04,c=Math.sqrt(.1734+b*b),d=c-b,e=Math.pow(Math.abs(d),1/3)*(0>d?-1:1),f=-c-b,g=Math.pow(Math.abs(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},r.backin=function(a){if(1==a)return 1;var b=1.70158;return a*a*((b+1)*a-b)},r.backout=function(a){if(0==a)return 0;a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},r.elastic=function(a){return a==!!a?a:Math.pow(2,-10*a)*Math.sin(2*(a-.075)*Math.PI/.3)+1},r.bounce=function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b},a.mina=r,r}("undefined"==typeof b?function(){}:b),d=function(a){function c(a,b){if(a){if(a.nodeType)return w(a);if(e(a,"array")&&c.set)return c.set.apply(c,a);if(a instanceof s)return a;if(null==b)return a=y.doc.querySelector(String(a)),w(a)}return a=null==a?"100%":a,b=null==b?"100%":b,new v(a,b)}function d(a,b){if(b){if("#text"==a&&(a=y.doc.createTextNode(b.text||b["#text"]||"")),"#comment"==a&&(a=y.doc.createComment(b.text||b["#text"]||"")),"string"==typeof a&&(a=d(a)),"string"==typeof b)return 1==a.nodeType?"xlink:"==b.substring(0,6)?a.getAttributeNS(T,b.substring(6)):"xml:"==b.substring(0,4)?a.getAttributeNS(U,b.substring(4)):a.getAttribute(b):"text"==b?a.nodeValue:null;if(1==a.nodeType){for(var c in b)if(b[z](c)){var e=A(b[c]);e?"xlink:"==c.substring(0,6)?a.setAttributeNS(T,c.substring(6),e):"xml:"==c.substring(0,4)?a.setAttributeNS(U,c.substring(4),e):a.setAttribute(c,e):a.removeAttribute(c)}}else"text"in b&&(a.nodeValue=b.text)}else a=y.doc.createElementNS(U,a);return a}function e(a,b){return b=A.prototype.toLowerCase.call(b),"finite"==b?isFinite(a):"array"==b&&(a instanceof Array||Array.isArray&&Array.isArray(a))?!0:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||J.call(a).slice(8,-1).toLowerCase()==b}function f(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=f(a[c]));return b}function h(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function i(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),g=d.cache=d.cache||{},i=d.count=d.count||[];return g[z](f)?(h(i,f),c?c(g[f]):g[f]):(i.length>=1e3&&delete g[i.shift()],i.push(f),g[f]=a.apply(b,e),c?c(g[f]):g[f])}return d}function j(a,b,c,d,e,f){if(null==e){var g=a-c,h=b-d;return g||h?(180+180*D.atan2(-h,-g)/H+360)%360:0}return j(a,b,e,f)-j(c,d,e,f)}function k(a){return a%360*H/180}function l(a){return 180*a/H%360}function m(a){var b=[];return a=a.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g,function(a,c,d){return d=d.split(/\s*,\s*|\s+/),"rotate"==c&&1==d.length&&d.push(0,0),"scale"==c&&(d.length>2?d=d.slice(0,2):2==d.length&&d.push(0,0),1==d.length&&d.push(d[0],0,0)),b.push("skewX"==c?["m",1,0,D.tan(k(d[0])),1,0,0]:"skewY"==c?["m",1,D.tan(k(d[0])),0,1,0,0]:[c.charAt(0)].concat(d)),a}),b}function n(a,b){var d=ab(a),e=new c.Matrix;if(d)for(var f=0,g=d.length;g>f;f++){var h,i,j,k,l,m=d[f],n=m.length,o=A(m[0]).toLowerCase(),p=m[0]!=o,q=p?e.invert():0;"t"==o&&2==n?e.translate(m[1],0):"t"==o&&3==n?p?(h=q.x(0,0),i=q.y(0,0),j=q.x(m[1],m[2]),k=q.y(m[1],m[2]),e.translate(j-h,k-i)):e.translate(m[1],m[2]):"r"==o?2==n?(l=l||b,e.rotate(m[1],l.x+l.width/2,l.y+l.height/2)):4==n&&(p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.rotate(m[1],j,k)):e.rotate(m[1],m[2],m[3])):"s"==o?2==n||3==n?(l=l||b,e.scale(m[1],m[n-1],l.x+l.width/2,l.y+l.height/2)):4==n?p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.scale(m[1],m[1],j,k)):e.scale(m[1],m[1],m[2],m[3]):5==n&&(p?(j=q.x(m[3],m[4]),k=q.y(m[3],m[4]),e.scale(m[1],m[2],j,k)):e.scale(m[1],m[2],m[3],m[4])):"m"==o&&7==n&&e.add(m[1],m[2],m[3],m[4],m[5],m[6])}return e}function o(a){var b=a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||a.node.parentNode&&w(a.node.parentNode)||c.select("svg")||c(0,0),d=b.select("defs"),e=null==d?!1:d.node;return e||(e=u("defs",b.node).node),e}function p(a){return a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||c.select("svg")}function q(a,b,c){function e(a){if(null==a)return I;if(a==+a)return a;d(j,{width:a});try{return j.getBBox().width}catch(b){return 0}}function f(a){if(null==a)return I;if(a==+a)return a;d(j,{height:a});try{return j.getBBox().height}catch(b){return 0}}function g(d,e){null==b?i[d]=e(a.attr(d)||0):d==b&&(i=e(null==c?a.attr(d)||0:c))}var h=p(a).node,i={},j=h.querySelector(".svg---mgr");switch(j||(j=d("rect"),d(j,{x:-9e9,y:-9e9,width:10,height:10,"class":"svg---mgr",fill:"none"}),h.appendChild(j)),a.type){case"rect":g("rx",e),g("ry",f);case"image":g("width",e),g("height",f);case"text":g("x",e),g("y",f);break;case"circle":g("cx",e),g("cy",f),g("r",e);break;case"ellipse":g("cx",e),g("cy",f),g("rx",e),g("ry",f);break;case"line":g("x1",e),g("x2",e),g("y1",f),g("y2",f);break;case"marker":g("refX",e),g("markerWidth",e),g("refY",f),g("markerHeight",f);break;case"radialGradient":g("fx",e),g("fy",f);break;case"tspan":g("dx",e),g("dy",f);break;default:g(b,e)}return h.removeChild(j),i}function r(a){e(a,"array")||(a=Array.prototype.slice.call(arguments,0));for(var b=0,c=0,d=this.node;this[b];)delete this[b++];for(b=0;b<a.length;b++)"set"==a[b].type?a[b].forEach(function(a){d.appendChild(a.node)}):d.appendChild(a[b].node);var f=d.childNodes;for(b=0;b<f.length;b++)this[c++]=w(f[b]);return this}function s(a){if(a.snap in V)return V[a.snap];var b;try{b=a.ownerSVGElement}catch(c){}this.node=a,b&&(this.paper=new v(b)),this.type=a.tagName||a.nodeName;var d=this.id=S(this);if(this.anims={},this._={transform:[]},a.snap=d,V[d]=this,"g"==this.type&&(this.add=r),this.type in{g:1,mask:1,pattern:1,symbol:1})for(var e in v.prototype)v.prototype[z](e)&&(this[e]=v.prototype[e])}function t(a){this.node=a}function u(a,b){var c=d(a);b.appendChild(c);var e=w(c);return e}function v(a,b){var c,e,f,g=v.prototype;if(a&&"svg"==a.tagName){if(a.snap in V)return V[a.snap];var h=a.ownerDocument;c=new s(a),e=a.getElementsByTagName("desc")[0],f=a.getElementsByTagName("defs")[0],e||(e=d("desc"),e.appendChild(h.createTextNode("Created with Snap")),c.node.appendChild(e)),f||(f=d("defs"),c.node.appendChild(f)),c.defs=f;for(var i in g)g[z](i)&&(c[i]=g[i]);c.paper=c.root=c}else c=u("svg",y.doc.body),d(c.node,{height:b,version:1.1,width:a,xmlns:U});return c}function w(a){return a?a instanceof s||a instanceof t?a:a.tagName&&"svg"==a.tagName.toLowerCase()?new v(a):a.tagName&&"object"==a.tagName.toLowerCase()&&"image/svg+xml"==a.type?new v(a.contentDocument.getElementsByTagName("svg")[0]):new s(a):a}function x(a,b){for(var c=0,d=a.length;d>c;c++){var e={type:a[c].type,attr:a[c].attr()},f=a[c].children();b.push(e),f.length&&x(f,e.childNodes=[])}}c.version="0.4.0",c.toString=function(){return"Snap v"+this.version},c._={};var y={win:a.window,doc:a.window.document};c._.glob=y;{var z="hasOwnProperty",A=String,B=parseFloat,C=parseInt,D=Math,E=D.max,F=D.min,G=D.abs,H=(D.pow,D.PI),I=(D.round,""),J=Object.prototype.toString,K=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,L=(c._.separator=/[,\s]+/,/[\s]*,[\s]*/),M={hs:1,rg:1},N=/([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,O=/([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,P=/(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/gi,Q=0,R="S"+(+new Date).toString(36),S=function(a){return(a&&a.type?a.type:I)+R+(Q++).toString(36)},T="http://www.w3.org/1999/xlink",U="http://www.w3.org/2000/svg",V={};c.url=function(a){return"url('#"+a+"')"}}c._.$=d,c._.id=S,c.format=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return A(b).replace(a,function(a,b){return c(a,b,d)})}}(),c._.clone=f,c._.cacher=i,c.rad=k,c.deg=l,c.sin=function(a){return D.sin(c.rad(a))},c.tan=function(a){return D.tan(c.rad(a))},c.cos=function(a){return D.cos(c.rad(a))},c.asin=function(a){return c.deg(D.asin(a))},c.acos=function(a){return c.deg(D.acos(a))},c.atan=function(a){return c.deg(D.atan(a))},c.atan2=function(a){return c.deg(D.atan2(a))},c.angle=j,c.len=function(a,b,d,e){return Math.sqrt(c.len2(a,b,d,e))},c.len2=function(a,b,c,d){return(a-c)*(a-c)+(b-d)*(b-d)},c.closestPoint=function(a,b,c){function d(a){var d=a.x-b,e=a.y-c;return d*d+e*e}for(var e,f,g,h,i=a.node,j=i.getTotalLength(),k=j/i.pathSegList.numberOfItems*.125,l=1/0,m=0;j>=m;m+=k)(h=d(g=i.getPointAtLength(m)))<l&&(e=g,f=m,l=h);for(k*=.5;k>.5;){var n,o,p,q,r,s;(p=f-k)>=0&&(r=d(n=i.getPointAtLength(p)))<l?(e=n,f=p,l=r):(q=f+k)<=j&&(s=d(o=i.getPointAtLength(q)))<l?(e=o,f=q,l=s):k*=.5}return e={x:e.x,y:e.y,length:f,distance:Math.sqrt(l)}},c.is=e,c.snapTo=function(a,b,c){if(c=e(c,"finite")?c:10,e(a,"array")){for(var d=a.length;d--;)if(G(a[d]-b)<=c)return a[d]}else{a=+a;var f=b%a;if(c>f)return b-f;if(f>a-c)return b-f+a}return b},c.getRGB=i(function(a){if(!a||(a=A(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:Z};if(!(M[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=W(a)),!a)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};var b,d,f,g,h,i,j=a.match(K);return j?(j[2]&&(f=C(j[2].substring(5),16),d=C(j[2].substring(3,5),16),b=C(j[2].substring(1,3),16)),j[3]&&(f=C((h=j[3].charAt(3))+h,16),d=C((h=j[3].charAt(2))+h,16),b=C((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=B(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),f=B(i[2]),"%"==i[2].slice(-1)&&(f*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsb2rgb(b,d,f,g)):j[6]?(i=j[6].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsl2rgb(b,d,f,g)):(b=F(D.round(b),255),d=F(D.round(d),255),f=F(D.round(f),255),g=F(E(g,0),1),j={r:b,g:d,b:f,toString:Z},j.hex="#"+(16777216|f|d<<8|b<<16).toString(16).slice(1),j.opacity=e(g,"finite")?g:1,j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z}},c),c.hsb=i(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=i(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=i(function(a,b,c,d){if(e(d,"finite")){var f=D.round;return"rgba("+[f(a),f(b),f(c),+d.toFixed(2)]+")"}return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)});var W=function(a){var b=y.doc.getElementsByTagName("head")[0]||y.doc.getElementsByTagName("svg")[0],c="rgb(255, 0, 0)";return(W=i(function(a){if("red"==a.toLowerCase())return c;b.style.color=c,b.style.color=a;var d=y.doc.defaultView.getComputedStyle(b,I).getPropertyValue("color");return d==c?null:d}))(a)},X=function(){return"hsb("+[this.h,this.s,this.b]+")"},Y=function(){return"hsl("+[this.h,this.s,this.l]+")"},Z=function(){return 1==this.opacity||null==this.opacity?this.hex:"rgba("+[this.r,this.g,this.b,this.opacity]+")"},$=function(a,b,d){if(null==b&&e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&e(a,string)){var f=c.getRGB(a);a=f.r,b=f.g,d=f.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},_=function(a,b,d,f){a=D.round(255*a),b=D.round(255*b),d=D.round(255*d);var g={r:a,g:b,b:d,opacity:e(f,"finite")?f:1,hex:c.rgb(a,b,d),toString:Z};return e(f,"finite")&&(g.opacity=f),g};c.color=function(a){var b;return e(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):e(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):(e(a,"string")&&(a=c.getRGB(a)),e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&!("error"in a)?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1,a.error=1)),a.toString=Z,a},c.hsb2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var f,g,h,i,j;return a=a%360/60,j=c*b,i=j*(1-G(a%2-1)),f=g=h=c-j,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.hsl2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var f,g,h,i,j;return a=a%360/60,j=2*b*(.5>c?c:1-c),i=j*(1-G(a%2-1)),f=g=h=c-j/2,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.rgb2hsb=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=E(a,b,c),g=f-F(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:X}},c.rgb2hsl=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=E(a,b,c),h=F(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:Y}},c.parsePathString=function(a){if(!a)return null;var b=c.path(a);if(b.arr)return c.path.clone(b.arr);var d={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},f=[];return e(a,"array")&&e(a[0],"array")&&(f=c.path.clone(a)),f.length||A(a).replace(N,function(a,b,c){var e=[],g=b.toLowerCase();if(c.replace(P,function(a,b){b&&e.push(+b)}),"m"==g&&e.length>2&&(f.push([b].concat(e.splice(0,2))),g="l",b="m"==b?"l":"L"),"o"==g&&1==e.length&&f.push([b,e[0]]),"r"==g)f.push([b].concat(e));else for(;e.length>=d[g]&&(f.push([b].concat(e.splice(0,d[g]))),d[g]););}),f.toString=c.path.toString,b.arr=c.path.clone(f),f};var ab=c.parseTransformString=function(a){if(!a)return null;var b=[];return e(a,"array")&&e(a[0],"array")&&(b=c.path.clone(a)),b.length||A(a).replace(O,function(a,c,d){{var e=[];c.toLowerCase()}d.replace(P,function(a,b){b&&e.push(+b)}),b.push([c].concat(e))}),b.toString=c.path.toString,b};c._.svgTransform2string=m,c._.rgTransform=/^[a-z][\s]*-?\.?\d/i,c._.transform2matrix=n,c._unit2px=q;y.doc.contains||y.doc.compareDocumentPosition?function(a,b){var c=9==a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a==d||!(!d||1!=d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)for(;b;)if(b=b.parentNode,b==a)return!0;return!1};c._.getSomeDefs=o,c._.getSomeSVG=p,c.select=function(a){return a=A(a).replace(/([^\\]):/g,"$1\\:"),w(y.doc.querySelector(a))},c.selectAll=function(a){for(var b=y.doc.querySelectorAll(a),d=(c.set||Array)(),e=0;e<b.length;e++)d.push(w(b[e]));return d},setInterval(function(){for(var a in V)if(V[z](a)){var b=V[a],c=b.node;("svg"!=b.type&&!c.ownerSVGElement||"svg"==b.type&&(!c.parentNode||"ownerSVGElement"in c.parentNode&&!c.ownerSVGElement))&&delete V[a]}},1e4),s.prototype.attr=function(a,c){var d=this,f=d.node;if(!a){if(1!=f.nodeType)return{text:f.nodeValue};for(var g=f.attributes,h={},i=0,j=g.length;j>i;i++)h[g[i].nodeName]=g[i].nodeValue;return h}if(e(a,"string")){if(!(arguments.length>1))return b("snap.util.getattr."+a,d).firstDefined();var k={};k[a]=c,a=k}for(var l in a)a[z](l)&&b("snap.util.attr."+l,d,a[l]);return d},c.parse=function(a){var b=y.doc.createDocumentFragment(),c=!0,d=y.doc.createElement("div");if(a=A(a),a.match(/^\s*<\s*svg(?:\s|>)/)||(a="<svg>"+a+"</svg>",c=!1),d.innerHTML=a,a=d.getElementsByTagName("svg")[0])if(c)b=a;else for(;a.firstChild;)b.appendChild(a.firstChild);return new t(b)},c.fragment=function(){for(var a=Array.prototype.slice.call(arguments,0),b=y.doc.createDocumentFragment(),d=0,e=a.length;e>d;d++){var f=a[d];f.node&&f.node.nodeType&&b.appendChild(f.node),f.nodeType&&b.appendChild(f),"string"==typeof f&&b.appendChild(c.parse(f).node)}return new t(b)},c._.make=u,c._.wrap=w,v.prototype.el=function(a,b){var c=u(a,this.node);return b&&c.attr(b),c},s.prototype.children=function(){for(var a=[],b=this.node.childNodes,d=0,e=b.length;e>d;d++)a[d]=c(b[d]);return a},s.prototype.toJSON=function(){var a=[];return x([this],a),a[0]},b.on("snap.util.getattr",function(){var a=b.nt();a=a.substring(a.lastIndexOf(".")+1);var c=a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});return bb[z](c)?this.node.ownerDocument.defaultView.getComputedStyle(this.node,null).getPropertyValue(c):d(this.node,a)});var bb={"alignment-baseline":0,"baseline-shift":0,clip:0,"clip-path":0,"clip-rule":0,color:0,"color-interpolation":0,"color-interpolation-filters":0,"color-profile":0,"color-rendering":0,cursor:0,direction:0,display:0,"dominant-baseline":0,"enable-background":0,fill:0,"fill-opacity":0,"fill-rule":0,filter:0,"flood-color":0,"flood-opacity":0,font:0,"font-family":0,"font-size":0,"font-size-adjust":0,"font-stretch":0,"font-style":0,"font-variant":0,"font-weight":0,"glyph-orientation-horizontal":0,"glyph-orientation-vertical":0,"image-rendering":0,kerning:0,"letter-spacing":0,"lighting-color":0,marker:0,"marker-end":0,"marker-mid":0,"marker-start":0,mask:0,opacity:0,overflow:0,"pointer-events":0,"shape-rendering":0,"stop-color":0,"stop-opacity":0,stroke:0,"stroke-dasharray":0,"stroke-dashoffset":0,"stroke-linecap":0,"stroke-linejoin":0,"stroke-miterlimit":0,"stroke-opacity":0,"stroke-width":0,"text-anchor":0,"text-decoration":0,"text-rendering":0,"unicode-bidi":0,visibility:0,"word-spacing":0,"writing-mode":0};b.on("snap.util.attr",function(a){var c=b.nt(),e={};c=c.substring(c.lastIndexOf(".")+1),e[c]=a;var f=c.replace(/-(\w)/gi,function(a,b){return b.toUpperCase()}),g=c.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});bb[z](g)?this.node.style[f]=null==a?I:a:d(this.node,e)}),function(){}(v.prototype),c.ajax=function(a,c,d,f){var g=new XMLHttpRequest,h=S();if(g){if(e(c,"function"))f=d,d=c,c=null;else if(e(c,"object")){var i=[];for(var j in c)c.hasOwnProperty(j)&&i.push(encodeURIComponent(j)+"="+encodeURIComponent(c[j]));c=i.join("&")}return g.open(c?"POST":"GET",a,!0),c&&(g.setRequestHeader("X-Requested-With","XMLHttpRequest"),g.setRequestHeader("Content-type","application/x-www-form-urlencoded")),d&&(b.once("snap.ajax."+h+".0",d),b.once("snap.ajax."+h+".200",d),b.once("snap.ajax."+h+".304",d)),g.onreadystatechange=function(){4==g.readyState&&b("snap.ajax."+h+"."+g.status,f,g)},4==g.readyState?g:(g.send(c),g)}},c.load=function(a,b,d){c.ajax(a,function(a){var e=c.parse(a.responseText);d?b.call(d,e):b(e)})};var cb=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,h=e.clientLeft||d.clientLeft||0,i=b.top+(g.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(g.win.pageXOffset||e.scrollLeft||d.scrollLeft)-h;return{y:i,x:j}};return c.getElementByPoint=function(a,b){var c=this,d=(c.canvas,y.doc.elementFromPoint(a,b));if(y.win.opera&&"svg"==d.tagName){var e=cb(d),f=d.createSVGRect();f.x=a-e.x,f.y=b-e.y,f.width=f.height=1;var g=d.getIntersectionList(f,null);g.length&&(d=g[g.length-1])}return d?w(d):null},c.plugin=function(a){a(c,s,v,y,t)},y.win.Snap=c,c}(a||this);return d.plugin(function(d,e,f,g,h){function i(a,b){if(null==b){var c=!0;if(b=a.node.getAttribute("linearGradient"==a.type||"radialGradient"==a.type?"gradientTransform":"pattern"==a.type?"patternTransform":"transform"),!b)return new d.Matrix;b=d._.svgTransform2string(b)}else b=d._.rgTransform.test(b)?o(b).replace(/\.{3}|\u2026/g,a._.transform||""):d._.svgTransform2string(b),n(b,"array")&&(b=d.path?d.path.toString.call(b):o(b)),a._.transform=b;var e=d._.transform2matrix(b,a.getBBox(1));return c?e:void(a.matrix=e)}function j(a){function b(a,b){var c=q(a.node,b);c=c&&c.match(f),c=c&&c[2],c&&"#"==c.charAt()&&(c=c.substring(1),c&&(h[c]=(h[c]||[]).concat(function(c){var d={};d[b]=URL(c),q(a.node,d)})))}function c(a){var b=q(a.node,"xlink:href");b&&"#"==b.charAt()&&(b=b.substring(1),b&&(h[b]=(h[b]||[]).concat(function(b){a.attr("xlink:href","#"+b)})))}for(var d,e=a.selectAll("*"),f=/^\s*url\(("|'|)(.*)\1\)\s*$/,g=[],h={},i=0,j=e.length;j>i;i++){d=e[i],b(d,"fill"),b(d,"stroke"),b(d,"filter"),b(d,"mask"),b(d,"clip-path"),c(d);var k=q(d.node,"id");k&&(q(d.node,{id:d.id}),g.push({old:k,id:d.id}))}for(i=0,j=g.length;j>i;i++){var l=h[g[i].old];if(l)for(var m=0,n=l.length;n>m;m++)l[m](g[i].id)}}function k(a,b,c){return function(d){var e=d.slice(a,b);return 1==e.length&&(e=e[0]),c?c(e):e}}function l(a){return function(){var b=a?"<"+this.type:"",c=this.node.attributes,d=this.node.childNodes;if(a)for(var e=0,f=c.length;f>e;e++)b+=" "+c[e].name+'="'+c[e].value.replace(/"/g,'\\"')+'"';if(d.length){for(a&&(b+=">"),e=0,f=d.length;f>e;e++)3==d[e].nodeType?b+=d[e].nodeValue:1==d[e].nodeType&&(b+=u(d[e]).toString());a&&(b+="</"+this.type+">")}else a&&(b+="/>");return b}}var m=e.prototype,n=d.is,o=String,p=d._unit2px,q=d._.$,r=d._.make,s=d._.getSomeDefs,t="hasOwnProperty",u=d._.wrap;m.getBBox=function(a){if(!d.Matrix||!d.path)return this.node.getBBox();var b=this,c=new d.Matrix;if(b.removed)return d._.box();for(;"use"==b.type;)if(a||(c=c.add(b.transform().localMatrix.translate(b.attr("x")||0,b.attr("y")||0))),b.original)b=b.original;else{var e=b.attr("xlink:href");b=b.original=b.node.ownerDocument.getElementById(e.substring(e.indexOf("#")+1))}var f=b._,g=d.path.get[b.type]||d.path.get.deflt;try{return a?(f.bboxwt=g?d.path.getBBox(b.realPath=g(b)):d._.box(b.node.getBBox()),d._.box(f.bboxwt)):(b.realPath=g(b),b.matrix=b.transform().localMatrix,f.bbox=d.path.getBBox(d.path.map(b.realPath,c.add(b.matrix))),d._.box(f.bbox))}catch(h){return d._.box()}};var v=function(){return this.string};m.transform=function(a){var b=this._;if(null==a){for(var c,e=this,f=new d.Matrix(this.node.getCTM()),g=i(this),h=[g],j=new d.Matrix,k=g.toTransformString(),l=o(g)==o(this.matrix)?o(b.transform):k;"svg"!=e.type&&(e=e.parent());)h.push(i(e));for(c=h.length;c--;)j.add(h[c]);return{string:l,globalMatrix:f,totalMatrix:j,localMatrix:g,diffMatrix:f.clone().add(g.invert()),global:f.toTransformString(),total:j.toTransformString(),local:k,toString:v}}return a instanceof d.Matrix?(this.matrix=a,this._.transform=a.toTransformString()):i(this,a),this.node&&("linearGradient"==this.type||"radialGradient"==this.type?q(this.node,{gradientTransform:this.matrix}):"pattern"==this.type?q(this.node,{patternTransform:this.matrix}):q(this.node,{transform:this.matrix})),this},m.parent=function(){return u(this.node.parentNode)},m.append=m.add=function(a){if(a){if("set"==a.type){var b=this;return a.forEach(function(a){b.add(a)}),this}a=u(a),this.node.appendChild(a.node),a.paper=this.paper}return this},m.appendTo=function(a){return a&&(a=u(a),a.append(this)),this},m.prepend=function(a){if(a){if("set"==a.type){var b,c=this;return a.forEach(function(a){b?b.after(a):c.prepend(a),b=a}),this}a=u(a);var d=a.parent();this.node.insertBefore(a.node,this.node.firstChild),this.add&&this.add(),a.paper=this.paper,this.parent()&&this.parent().add(),d&&d.add()}return this},m.prependTo=function(a){return a=u(a),a.prepend(this),this},m.before=function(a){if("set"==a.type){var b=this;return a.forEach(function(a){var c=a.parent();b.node.parentNode.insertBefore(a.node,b.node),c&&c.add()}),this.parent().add(),this}a=u(a);var c=a.parent();return this.node.parentNode.insertBefore(a.node,this.node),this.parent()&&this.parent().add(),c&&c.add(),a.paper=this.paper,this},m.after=function(a){a=u(a);var b=a.parent();return this.node.nextSibling?this.node.parentNode.insertBefore(a.node,this.node.nextSibling):this.node.parentNode.appendChild(a.node),this.parent()&&this.parent().add(),b&&b.add(),a.paper=this.paper,this},m.insertBefore=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.insertAfter=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node.nextSibling),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.remove=function(){var a=this.parent();return this.node.parentNode&&this.node.parentNode.removeChild(this.node),delete this.paper,this.removed=!0,a&&a.add(),this},m.select=function(a){return u(this.node.querySelector(a))},m.selectAll=function(a){for(var b=this.node.querySelectorAll(a),c=(d.set||Array)(),e=0;e<b.length;e++)c.push(u(b[e]));return c},m.asPX=function(a,b){return null==b&&(b=this.attr(a)),+p(this,a,b)},m.use=function(){var a,b=this.node.id;return b||(b=this.id,q(this.node,{id:b})),a="linearGradient"==this.type||"radialGradient"==this.type||"pattern"==this.type?r(this.type,this.node.parentNode):r("use",this.node.parentNode),q(a.node,{"xlink:href":"#"+b}),a.original=this,a},m.clone=function(){var a=u(this.node.cloneNode(!0));return q(a.node,"id")&&q(a.node,{id:a.id}),j(a),a.insertAfter(this),a},m.toDefs=function(){var a=s(this);return a.appendChild(this.node),this},m.pattern=m.toPattern=function(a,b,c,d){var e=r("pattern",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,a=a.x),q(e.node,{x:a,y:b,width:c,height:d,patternUnits:"userSpaceOnUse",id:e.id,viewBox:[a,b,c,d].join(" ")}),e.node.appendChild(this.node),e},m.marker=function(a,b,c,d,e,f){var g=r("marker",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,e=a.refX||a.cx,f=a.refY||a.cy,a=a.x),q(g.node,{viewBox:[a,b,c,d].join(" "),markerWidth:c,markerHeight:d,orient:"auto",refX:e||0,refY:f||0,id:g.id}),g.node.appendChild(this.node),g};var w=function(a,b,d,e){"function"!=typeof d||d.length||(e=d,d=c.linear),this.attr=a,this.dur=b,d&&(this.easing=d),e&&(this.callback=e)};d._.Animation=w,d.animation=function(a,b,c,d){return new w(a,b,c,d)},m.inAnim=function(){var a=this,b=[];for(var c in a.anims)a.anims[t](c)&&!function(a){b.push({anim:new w(a._attrs,a.dur,a.easing,a._callback),mina:a,curStatus:a.status(),status:function(b){return a.status(b)},stop:function(){a.stop()}})}(a.anims[c]);return b},d.animate=function(a,d,e,f,g,h){"function"!=typeof g||g.length||(h=g,g=c.linear);var i=c.time(),j=c(a,d,i,i+f,c.time,e,g);return h&&b.once("mina.finish."+j.id,h),j},m.stop=function(){for(var a=this.inAnim(),b=0,c=a.length;c>b;b++)a[b].stop();return this},m.animate=function(a,d,e,f){"function"!=typeof e||e.length||(f=e,e=c.linear),a instanceof w&&(f=a.callback,e=a.easing,d=a.dur,a=a.attr);var g,h,i,j,l=[],m=[],p={},q=this;for(var r in a)if(a[t](r)){q.equal?(j=q.equal(r,o(a[r])),g=j.from,h=j.to,i=j.f):(g=+q.attr(r),h=+a[r]);var s=n(g,"array")?g.length:1;p[r]=k(l.length,l.length+s,i),l=l.concat(g),m=m.concat(h)}var u=c.time(),v=c(l,m,u,u+d,c.time,function(a){var b={};for(var c in p)p[t](c)&&(b[c]=p[c](a));q.attr(b)},e);return q.anims[v.id]=v,v._attrs=a,v._callback=f,b("snap.animcreated."+q.id,v),b.once("mina.finish."+v.id,function(){delete q.anims[v.id],f&&f.call(q)}),b.once("mina.stop."+v.id,function(){delete q.anims[v.id]}),q};var x={};m.data=function(a,c){var e=x[this.id]=x[this.id]||{};if(0==arguments.length)return b("snap.data.get."+this.id,this,e,null),e;
+if(1==arguments.length){if(d.is(a,"object")){for(var f in a)a[t](f)&&this.data(f,a[f]);return this}return b("snap.data.get."+this.id,this,e[a],a),e[a]}return e[a]=c,b("snap.data.set."+this.id,this,c,a),this},m.removeData=function(a){return null==a?x[this.id]={}:x[this.id]&&delete x[this.id][a],this},m.outerSVG=m.toString=l(1),m.innerSVG=l(),m.toDataURL=function(){if(a&&a.btoa){var b=this.getBBox(),c=d.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>',{x:+b.x.toFixed(3),y:+b.y.toFixed(3),width:+b.width.toFixed(3),height:+b.height.toFixed(3),contents:this.outerSVG()});return"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(c)))}},h.prototype.select=m.select,h.prototype.selectAll=m.selectAll}),d.plugin(function(a){function b(a,b,d,e,f,g){return null==b&&"[object SVGMatrix]"==c.call(a)?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,void(this.f=a.f)):void(null!=a?(this.a=+a,this.b=+b,this.c=+d,this.d=+e,this.e=+f,this.f=+g):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0))}var c=Object.prototype.toString,d=String,e=Math,f="";!function(c){function g(a){return a[0]*a[0]+a[1]*a[1]}function h(a){var b=e.sqrt(g(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}c.add=function(a,c,d,e,f,g){var h,i,j,k,l=[[],[],[]],m=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],n=[[a,d,f],[c,e,g],[0,0,1]];for(a&&a instanceof b&&(n=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),h=0;3>h;h++)for(i=0;3>i;i++){for(k=0,j=0;3>j;j++)k+=m[h][j]*n[j][i];l[h][i]=k}return this.a=l[0][0],this.b=l[1][0],this.c=l[0][1],this.d=l[1][1],this.e=l[0][2],this.f=l[1][2],this},c.invert=function(){var a=this,c=a.a*a.d-a.b*a.c;return new b(a.d/c,-a.b/c,-a.c/c,a.a/c,(a.c*a.f-a.d*a.e)/c,(a.b*a.e-a.a*a.f)/c)},c.clone=function(){return new b(this.a,this.b,this.c,this.d,this.e,this.f)},c.translate=function(a,b){return this.add(1,0,0,1,a,b)},c.scale=function(a,b,c,d){return null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d),this},c.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var f=+e.cos(b).toFixed(9),g=+e.sin(b).toFixed(9);return this.add(f,g,-g,f,c,d),this.add(1,0,0,1,-c,-d)},c.x=function(a,b){return a*this.a+b*this.c+this.e},c.y=function(a,b){return a*this.b+b*this.d+this.f},c.get=function(a){return+this[d.fromCharCode(97+a)].toFixed(4)},c.toString=function(){return"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")"},c.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},c.determinant=function(){return this.a*this.d-this.b*this.c},c.split=function(){var b={};b.dx=this.e,b.dy=this.f;var c=[[this.a,this.c],[this.b,this.d]];b.scalex=e.sqrt(g(c[0])),h(c[0]),b.shear=c[0][0]*c[1][0]+c[0][1]*c[1][1],c[1]=[c[1][0]-c[0][0]*b.shear,c[1][1]-c[0][1]*b.shear],b.scaley=e.sqrt(g(c[1])),h(c[1]),b.shear/=b.scaley,this.determinant()<0&&(b.scalex=-b.scalex);var d=-c[0][1],f=c[1][1];return 0>f?(b.rotate=a.deg(e.acos(f)),0>d&&(b.rotate=360-b.rotate)):b.rotate=a.deg(e.asin(d)),b.isSimple=!(+b.shear.toFixed(9)||b.scalex.toFixed(9)!=b.scaley.toFixed(9)&&b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate,b},c.toTransformString=function(a){var b=a||this.split();return+b.shear.toFixed(9)?"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]:(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[+b.dx.toFixed(4),+b.dy.toFixed(4)]:f)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:f)+(b.rotate?"r"+[+b.rotate.toFixed(4),0,0]:f))}}(b.prototype),a.Matrix=b,a.matrix=function(a,c,d,e,f,g){return new b(a,c,d,e,f,g)}}),d.plugin(function(a,c,d,e,f){function g(d){return function(e){if(b.stop(),e instanceof f&&1==e.node.childNodes.length&&("radialGradient"==e.node.firstChild.tagName||"linearGradient"==e.node.firstChild.tagName||"pattern"==e.node.firstChild.tagName)&&(e=e.node.firstChild,n(this).appendChild(e),e=l(e)),e instanceof c)if("radialGradient"==e.type||"linearGradient"==e.type||"pattern"==e.type){e.node.id||p(e.node,{id:e.id});var g=q(e.node.id)}else g=e.attr(d);else if(g=a.color(e),g.error){var h=a(n(this).ownerSVGElement).gradient(e);h?(h.node.id||p(h.node,{id:h.id}),g=q(h.node.id)):g=e}else g=r(g);var i={};i[d]=g,p(this.node,i),this.node.style[d]=t}}function h(a){b.stop(),a==+a&&(a+="px"),this.node.style.fontSize=a}function i(a){for(var b=[],c=a.childNodes,d=0,e=c.length;e>d;d++){var f=c[d];3==f.nodeType&&b.push(f.nodeValue),"tspan"==f.tagName&&b.push(1==f.childNodes.length&&3==f.firstChild.nodeType?f.firstChild.nodeValue:i(f))}return b}function j(){return b.stop(),this.node.style.fontSize}var k=a._.make,l=a._.wrap,m=a.is,n=a._.getSomeDefs,o=/^url\(#?([^)]+)\)$/,p=a._.$,q=a.url,r=String,s=a._.separator,t="";b.on("snap.util.attr.mask",function(a){if(a instanceof c||a instanceof f){if(b.stop(),a instanceof f&&1==a.node.childNodes.length&&(a=a.node.firstChild,n(this).appendChild(a),a=l(a)),"mask"==a.type)var d=a;else d=k("mask",n(this)),d.node.appendChild(a.node);!d.node.id&&p(d.node,{id:d.id}),p(this.node,{mask:q(d.id)})}}),function(a){b.on("snap.util.attr.clip",a),b.on("snap.util.attr.clip-path",a),b.on("snap.util.attr.clipPath",a)}(function(a){if(a instanceof c||a instanceof f){if(b.stop(),"clipPath"==a.type)var d=a;else d=k("clipPath",n(this)),d.node.appendChild(a.node),!d.node.id&&p(d.node,{id:d.id});p(this.node,{"clip-path":q(d.node.id||d.id)})}}),b.on("snap.util.attr.fill",g("fill")),b.on("snap.util.attr.stroke",g("stroke"));var u=/^([lr])(?:\(([^)]*)\))?(.*)$/i;b.on("snap.util.grad.parse",function(a){a=r(a);var b=a.match(u);if(!b)return null;var c=b[1],d=b[2],e=b[3];return d=d.split(/\s*,\s*/).map(function(a){return+a==a?+a:a}),1==d.length&&0==d[0]&&(d=[]),e=e.split("-"),e=e.map(function(a){a=a.split(":");var b={color:a[0]};return a[1]&&(b.offset=parseFloat(a[1])),b}),{type:c,params:d,stops:e}}),b.on("snap.util.attr.d",function(c){b.stop(),m(c,"array")&&m(c[0],"array")&&(c=a.path.toString.call(c)),c=r(c),c.match(/[ruo]/i)&&(c=a.path.toAbsolute(c)),p(this.node,{d:c})})(-1),b.on("snap.util.attr.#text",function(a){b.stop(),a=r(a);for(var c=e.doc.createTextNode(a);this.node.firstChild;)this.node.removeChild(this.node.firstChild);this.node.appendChild(c)})(-1),b.on("snap.util.attr.path",function(a){b.stop(),this.attr({d:a})})(-1),b.on("snap.util.attr.class",function(a){b.stop(),this.node.className.baseVal=a})(-1),b.on("snap.util.attr.viewBox",function(a){var c;c=m(a,"object")&&"x"in a?[a.x,a.y,a.width,a.height].join(" "):m(a,"array")?a.join(" "):a,p(this.node,{viewBox:c}),b.stop()})(-1),b.on("snap.util.attr.transform",function(a){this.transform(a),b.stop()})(-1),b.on("snap.util.attr.r",function(a){"rect"==this.type&&(b.stop(),p(this.node,{rx:a,ry:a}))})(-1),b.on("snap.util.attr.textpath",function(a){if(b.stop(),"text"==this.type){var d,e,f;if(!a&&this.textPath){for(e=this.textPath;e.node.firstChild;)this.node.appendChild(e.node.firstChild);return e.remove(),void delete this.textPath}if(m(a,"string")){var g=n(this),h=l(g.parentNode).path(a);g.appendChild(h.node),d=h.id,h.attr({id:d})}else a=l(a),a instanceof c&&(d=a.attr("id"),d||(d=a.id,a.attr({id:d})));if(d)if(e=this.textPath,f=this.node,e)e.attr({"xlink:href":"#"+d});else{for(e=p("textPath",{"xlink:href":"#"+d});f.firstChild;)e.appendChild(f.firstChild);f.appendChild(e),this.textPath=l(e)}}})(-1),b.on("snap.util.attr.text",function(a){if("text"==this.type){for(var c=this.node,d=function(a){var b=p("tspan");if(m(a,"array"))for(var c=0;c<a.length;c++)b.appendChild(d(a[c]));else b.appendChild(e.doc.createTextNode(a));return b.normalize&&b.normalize(),b};c.firstChild;)c.removeChild(c.firstChild);for(var f=d(a);f.firstChild;)c.appendChild(f.firstChild)}b.stop()})(-1),b.on("snap.util.attr.fontSize",h)(-1),b.on("snap.util.attr.font-size",h)(-1),b.on("snap.util.getattr.transform",function(){return b.stop(),this.transform()})(-1),b.on("snap.util.getattr.textpath",function(){return b.stop(),this.textPath})(-1),function(){function c(c){return function(){b.stop();var d=e.doc.defaultView.getComputedStyle(this.node,null).getPropertyValue("marker-"+c);return"none"==d?d:a(e.doc.getElementById(d.match(o)[1]))}}function d(a){return function(c){b.stop();var d="marker"+a.charAt(0).toUpperCase()+a.substring(1);if(""==c||!c)return void(this.node.style[d]="none");if("marker"==c.type){var e=c.node.id;return e||p(c.node,{id:c.id}),void(this.node.style[d]=q(e))}}}b.on("snap.util.getattr.marker-end",c("end"))(-1),b.on("snap.util.getattr.markerEnd",c("end"))(-1),b.on("snap.util.getattr.marker-start",c("start"))(-1),b.on("snap.util.getattr.markerStart",c("start"))(-1),b.on("snap.util.getattr.marker-mid",c("mid"))(-1),b.on("snap.util.getattr.markerMid",c("mid"))(-1),b.on("snap.util.attr.marker-end",d("end"))(-1),b.on("snap.util.attr.markerEnd",d("end"))(-1),b.on("snap.util.attr.marker-start",d("start"))(-1),b.on("snap.util.attr.markerStart",d("start"))(-1),b.on("snap.util.attr.marker-mid",d("mid"))(-1),b.on("snap.util.attr.markerMid",d("mid"))(-1)}(),b.on("snap.util.getattr.r",function(){return"rect"==this.type&&p(this.node,"rx")==p(this.node,"ry")?(b.stop(),p(this.node,"rx")):void 0})(-1),b.on("snap.util.getattr.text",function(){if("text"==this.type||"tspan"==this.type){b.stop();var a=i(this.node);return 1==a.length?a[0]:a}})(-1),b.on("snap.util.getattr.#text",function(){return this.node.textContent})(-1),b.on("snap.util.getattr.viewBox",function(){b.stop();var c=p(this.node,"viewBox");return c?(c=c.split(s),a._.box(+c[0],+c[1],+c[2],+c[3])):void 0})(-1),b.on("snap.util.getattr.points",function(){var a=p(this.node,"points");return b.stop(),a?a.split(s):void 0})(-1),b.on("snap.util.getattr.path",function(){var a=p(this.node,"d");return b.stop(),a})(-1),b.on("snap.util.getattr.class",function(){return this.node.className.baseVal})(-1),b.on("snap.util.getattr.fontSize",j)(-1),b.on("snap.util.getattr.font-size",j)(-1)}),d.plugin(function(a,b){var c=/\S+/g,d=String,e=b.prototype;e.addClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(h.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e||k.push(f);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.removeClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(k.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e&&k.splice(e,1);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.hasClass=function(a){var b=this.node,d=b.className.baseVal,e=d.match(c)||[];return!!~e.indexOf(a)},e.toggleClass=function(a,b){if(null!=b)return b?this.addClass(a):this.removeClass(a);var d,e,f,g,h=(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];for(d=0;f=h[d++];)e=k.indexOf(f),~e?k.splice(e,1):k.push(f);return g=k.join(" "),j!=g&&(i.className.baseVal=g),this}}),d.plugin(function(){function a(a){return a}function c(a){return function(b){return+b.toFixed(3)+a}}var d={"+":function(a,b){return a+b},"-":function(a,b){return a-b},"/":function(a,b){return a/b},"*":function(a,b){return a*b}},e=String,f=/[a-z]+$/i,g=/^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;b.on("snap.util.attr",function(a){var c=e(a).match(g);if(c){var h=b.nt(),i=h.substring(h.lastIndexOf(".")+1),j=this.attr(i),k={};b.stop();var l=c[3]||"",m=j.match(f),n=d[c[1]];if(m&&m==l?a=n(parseFloat(j),+c[2]):(j=this.asPX(i),a=n(this.asPX(i),this.asPX(i,c[2]+l))),isNaN(j)||isNaN(a))return;k[i]=a,this.attr(k)}})(-10),b.on("snap.util.equal",function(h,i){var j=e(this.attr(h)||""),k=e(i).match(g);if(k){b.stop();var l=k[3]||"",m=j.match(f),n=d[k[1]];return m&&m==l?{from:parseFloat(j),to:n(parseFloat(j),+k[2]),f:c(m)}:(j=this.asPX(h),{from:j,to:n(j,this.asPX(h,k[2]+l)),f:a})}})(-10)}),d.plugin(function(c,d,e,f){var g=e.prototype,h=c.is;g.rect=function(a,b,c,d,e,f){var g;return null==f&&(f=e),h(a,"object")&&"[object Object]"==a?g=a:null!=a&&(g={x:a,y:b,width:c,height:d},null!=e&&(g.rx=e,g.ry=f)),this.el("rect",g)},g.circle=function(a,b,c){var d;return h(a,"object")&&"[object Object]"==a?d=a:null!=a&&(d={cx:a,cy:b,r:c}),this.el("circle",d)};var i=function(){function a(){this.parentNode.removeChild(this)}return function(b,c){var d=f.doc.createElement("img"),e=f.doc.body;d.style.cssText="position:absolute;left:-9999em;top:-9999em",d.onload=function(){c.call(d),d.onload=d.onerror=null,e.removeChild(d)},d.onerror=a,e.appendChild(d),d.src=b}}();g.image=function(a,b,d,e,f){var g=this.el("image");if(h(a,"object")&&"src"in a)g.attr(a);else if(null!=a){var j={"xlink:href":a,preserveAspectRatio:"none"};null!=b&&null!=d&&(j.x=b,j.y=d),null!=e&&null!=f?(j.width=e,j.height=f):i(a,function(){c._.$(g.node,{width:this.offsetWidth,height:this.offsetHeight})}),c._.$(g.node,j)}return g},g.ellipse=function(a,b,c,d){var e;return h(a,"object")&&"[object Object]"==a?e=a:null!=a&&(e={cx:a,cy:b,rx:c,ry:d}),this.el("ellipse",e)},g.path=function(a){var b;return h(a,"object")&&!h(a,"array")?b=a:a&&(b={d:a}),this.el("path",b)},g.group=g.g=function(a){var b=this.el("g");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.svg=function(a,b,c,d,e,f,g,i){var j={};return h(a,"object")&&null==b?j=a:(null!=a&&(j.x=a),null!=b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),null!=e&&null!=f&&null!=g&&null!=i&&(j.viewBox=[e,f,g,i])),this.el("svg",j)},g.mask=function(a){var b=this.el("mask");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.ptrn=function(a,b,c,d,e,f,g,i){if(h(a,"object"))var j=a;else j={patternUnits:"userSpaceOnUse"},a&&(j.x=a),b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),j.viewBox=null!=e&&null!=f&&null!=g&&null!=i?[e,f,g,i]:[a||0,b||0,c||0,d||0];return this.el("pattern",j)},g.use=function(a){return null!=a?(a instanceof d&&(a.attr("id")||a.attr({id:c._.id(a)}),a=a.attr("id")),"#"==String(a).charAt()&&(a=a.substring(1)),this.el("use",{"xlink:href":"#"+a})):d.prototype.use.call(this)},g.symbol=function(a,b,c,d){var e={};return null!=a&&null!=b&&null!=c&&null!=d&&(e.viewBox=[a,b,c,d]),this.el("symbol",e)},g.text=function(a,b,c){var d={};return h(a,"object")?d=a:null!=a&&(d={x:a,y:b,text:c||""}),this.el("text",d)},g.line=function(a,b,c,d){var e={};return h(a,"object")?e=a:null!=a&&(e={x1:a,x2:c,y1:b,y2:d}),this.el("line",e)},g.polyline=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polyline",b)},g.polygon=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polygon",b)},function(){function d(){return this.selectAll("stop")}function e(a,b){var d=k("stop"),e={offset:+b+"%"};return a=c.color(a),e["stop-color"]=a.hex,a.opacity<1&&(e["stop-opacity"]=a.opacity),k(d,e),this.node.appendChild(d),this}function f(){if("linearGradient"==this.type){var a=k(this.node,"x1")||0,b=k(this.node,"x2")||1,d=k(this.node,"y1")||0,e=k(this.node,"y2")||0;return c._.box(a,d,math.abs(b-a),math.abs(e-d))}var f=this.node.cx||.5,g=this.node.cy||.5,h=this.node.r||0;return c._.box(f-h,g-h,2*h,2*h)}function h(a,c){function d(a,b){for(var c=(b-l)/(a-m),d=m;a>d;d++)g[d].offset=+(+l+c*(d-m)).toFixed(2);m=a,l=b}var e,f=b("snap.util.grad.parse",null,c).firstDefined();if(!f)return null;f.params.unshift(a),e="l"==f.type.toLowerCase()?i.apply(0,f.params):j.apply(0,f.params),f.type!=f.type.toLowerCase()&&k(e.node,{gradientUnits:"userSpaceOnUse"});var g=f.stops,h=g.length,l=0,m=0;h--;for(var n=0;h>n;n++)"offset"in g[n]&&d(n,g[n].offset);for(g[h].offset=g[h].offset||100,d(h,g[h].offset),n=0;h>=n;n++){var o=g[n];e.addStop(o.color,o.offset)}return e}function i(a,b,g,h,i){var j=c._.make("linearGradient",a);return j.stops=d,j.addStop=e,j.getBBox=f,null!=b&&k(j.node,{x1:b,y1:g,x2:h,y2:i}),j}function j(a,b,g,h,i,j){var l=c._.make("radialGradient",a);return l.stops=d,l.addStop=e,l.getBBox=f,null!=b&&k(l.node,{cx:b,cy:g,r:h}),null!=i&&null!=j&&k(l.node,{fx:i,fy:j}),l}var k=c._.$;g.gradient=function(a){return h(this.defs,a)},g.gradientLinear=function(a,b,c,d){return i(this.defs,a,b,c,d)},g.gradientRadial=function(a,b,c,d,e){return j(this.defs,a,b,c,d,e)},g.toString=function(){var a,b=this.node.ownerDocument,d=b.createDocumentFragment(),e=b.createElement("div"),f=this.node.cloneNode(!0);return d.appendChild(e),e.appendChild(f),c._.$(f,{xmlns:"http://www.w3.org/2000/svg"}),a=e.innerHTML,d.removeChild(d.firstChild),a},g.toDataURL=function(){return a&&a.btoa?"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(this))):void 0},g.clear=function(){for(var a,b=this.node.firstChild;b;)a=b.nextSibling,"defs"!=b.tagName?b.parentNode.removeChild(b):g.clear.call({node:b}),b=a}}()}),d.plugin(function(a,b){function c(a){var b=c.ps=c.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[K](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]}function d(a,b,c,d){return null==a&&(a=b=c=d=0),null==b&&(b=a.y,c=a.width,d=a.height,a=a.x),{x:a,y:b,width:c,w:c,height:d,h:d,x2:a+c,y2:b+d,cx:a+c/2,cy:b+d/2,r1:N.min(c,d)/2,r2:N.max(c,d)/2,r0:N.sqrt(c*c+d*d)/2,path:w(a,b,c,d),vb:[a,b,c,d].join(" ")}}function e(){return this.join(",").replace(L,"$1")}function f(a){var b=J(a);return b.toString=e,b}function g(a,b,c,d,e,f,g,h,j){return null==j?n(a,b,c,d,e,f,g,h):i(a,b,c,d,e,f,g,h,o(a,b,c,d,e,f,g,h,j))}function h(c,d){function e(a){return+(+a).toFixed(3)}return a._.cacher(function(a,f,h){a instanceof b&&(a=a.attr("d")),a=E(a);for(var j,k,l,m,n,o="",p={},q=0,r=0,s=a.length;s>r;r++){if(l=a[r],"M"==l[0])j=+l[1],k=+l[2];else{if(m=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6]),q+m>f){if(d&&!p.start){if(n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q),o+=["C"+e(n.start.x),e(n.start.y),e(n.m.x),e(n.m.y),e(n.x),e(n.y)],h)return o;p.start=o,o=["M"+e(n.x),e(n.y)+"C"+e(n.n.x),e(n.n.y),e(n.end.x),e(n.end.y),e(l[5]),e(l[6])].join(),q+=m,j=+l[5],k=+l[6];continue}if(!c&&!d)return n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q)}q+=m,j=+l[5],k=+l[6]}o+=l.shift()+l}return p.end=o,n=c?q:d?p:i(j,k,l[0],l[1],l[2],l[3],l[4],l[5],1)},null,a._.clone)}function i(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/O;return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}}function j(b,c,e,f,g,h,i,j){a.is(b,"array")||(b=[b,c,e,f,g,h,i,j]);var k=D.apply(null,b);return d(k.min.x,k.min.y,k.max.x-k.min.x,k.max.y-k.min.y)}function k(a,b,c){return b>=a.x&&b<=a.x+a.width&&c>=a.y&&c<=a.y+a.height}function l(a,b){return a=d(a),b=d(b),k(b,a.x,a.y)||k(b,a.x2,a.y)||k(b,a.x,a.y2)||k(b,a.x2,a.y2)||k(a,b.x,b.y)||k(a,b.x2,b.y)||k(a,b.x,b.y2)||k(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)}function m(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function n(a,b,c,d,e,f,g,h,i){null==i&&(i=1),i=i>1?1:0>i?0:i;for(var j=i/2,k=12,l=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;k>p;p++){var q=j*l[p]+j,r=m(q,a,c,e,g),s=m(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return j*o}function o(a,b,c,d,e,f,g,h,i){if(!(0>i||n(a,b,c,d,e,f,g,h)<i)){var j,k=1,l=k/2,m=k-l,o=.01;for(j=n(a,b,c,d,e,f,g,h,m);S(j-i)>o;)l/=2,m+=(i>j?1:-1)*l,j=n(a,b,c,d,e,f,g,h,m);return m}}function p(a,b,c,d,e,f,g,h){if(!(Q(a,c)<P(e,g)||P(a,c)>Q(e,g)||Q(b,d)<P(f,h)||P(b,d)>Q(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+Q(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+Q(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+Q(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+Q(f,h).toFixed(2)))return{x:l,y:m}}}}function q(a,b,c){var d=j(a),e=j(b);if(!l(d,e))return c?0:[];for(var f=n.apply(0,a),g=n.apply(0,b),h=~~(f/8),k=~~(g/8),m=[],o=[],q={},r=c?0:[],s=0;h+1>s;s++){var t=i.apply(0,a.concat(s/h));m.push({x:t.x,y:t.y,t:s/h})}for(s=0;k+1>s;s++)t=i.apply(0,b.concat(s/k)),o.push({x:t.x,y:t.y,t:s/k});for(s=0;h>s;s++)for(var u=0;k>u;u++){var v=m[s],w=m[s+1],x=o[u],y=o[u+1],z=S(w.x-v.x)<.001?"y":"x",A=S(y.x-x.x)<.001?"y":"x",B=p(v.x,v.y,w.x,w.y,x.x,x.y,y.x,y.y);if(B){if(q[B.x.toFixed(4)]==B.y.toFixed(4))continue;q[B.x.toFixed(4)]=B.y.toFixed(4);var C=v.t+S((B[z]-v[z])/(w[z]-v[z]))*(w.t-v.t),D=x.t+S((B[A]-x[A])/(y[A]-x[A]))*(y.t-x.t);C>=0&&1>=C&&D>=0&&1>=D&&(c?r++:r.push({x:B.x,y:B.y,t1:C,t2:D}))}}return r}function r(a,b){return t(a,b)}function s(a,b){return t(a,b,1)}function t(a,b,c){a=E(a),b=E(b);for(var d,e,f,g,h,i,j,k,l,m,n=c?0:[],o=0,p=a.length;p>o;o++){var r=a[o];if("M"==r[0])d=h=r[1],e=i=r[2];else{"C"==r[0]?(l=[d,e].concat(r.slice(1)),d=l[6],e=l[7]):(l=[d,e,d,e,h,i,h,i],d=h,e=i);for(var s=0,t=b.length;t>s;s++){var u=b[s];if("M"==u[0])f=j=u[1],g=k=u[2];else{"C"==u[0]?(m=[f,g].concat(u.slice(1)),f=m[6],g=m[7]):(m=[f,g,f,g,j,k,j,k],f=j,g=k);var v=q(l,m,c);if(c)n+=v;else{for(var w=0,x=v.length;x>w;w++)v[w].segment1=o,v[w].segment2=s,v[w].bez1=l,v[w].bez2=m;n=n.concat(v)}}}}}return n}function u(a,b,c){var d=v(a);return k(d,b,c)&&t(a,[["M",b,c],["H",d.x2+10]],1)%2==1}function v(a){var b=c(a);if(b.bbox)return J(b.bbox);if(!a)return d();a=E(a);for(var e,f=0,g=0,h=[],i=[],j=0,k=a.length;k>j;j++)if(e=a[j],"M"==e[0])f=e[1],g=e[2],h.push(f),i.push(g);else{var l=D(f,g,e[1],e[2],e[3],e[4],e[5],e[6]);h=h.concat(l.min.x,l.max.x),i=i.concat(l.min.y,l.max.y),f=e[5],g=e[6]}var m=P.apply(0,h),n=P.apply(0,i),o=Q.apply(0,h),p=Q.apply(0,i),q=d(m,n,o-m,p-n);return b.bbox=J(q),q}function w(a,b,c,d,f){if(f)return[["M",+a+ +f,b],["l",c-2*f,0],["a",f,f,0,0,1,f,f],["l",0,d-2*f],["a",f,f,0,0,1,-f,f],["l",2*f-c,0],["a",f,f,0,0,1,-f,-f],["l",0,2*f-d],["a",f,f,0,0,1,f,-f],["z"]];var g=[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]];return g.toString=e,g}function x(a,b,c,d,f){if(null==f&&null==d&&(d=c),a=+a,b=+b,c=+c,d=+d,null!=f)var g=Math.PI/180,h=a+c*Math.cos(-d*g),i=a+c*Math.cos(-f*g),j=b+c*Math.sin(-d*g),k=b+c*Math.sin(-f*g),l=[["M",h,j],["A",c,c,0,+(f-d>180),0,i,k]];else l=[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]];return l.toString=e,l}function y(b){var d=c(b),g=String.prototype.toLowerCase;if(d.rel)return f(d.rel);a.is(b,"array")&&a.is(b&&b[0],"array")||(b=a.parsePathString(b));var h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=b[0][1],j=b[0][2],k=i,l=j,m++,h.push(["M",i,j]));for(var n=m,o=b.length;o>n;n++){var p=h[n]=[],q=b[n];if(q[0]!=g.call(q[0]))switch(p[0]=g.call(q[0]),p[0]){case"a":p[1]=q[1],p[2]=q[2],p[3]=q[3],p[4]=q[4],p[5]=q[5],p[6]=+(q[6]-i).toFixed(3),p[7]=+(q[7]-j).toFixed(3);break;case"v":p[1]=+(q[1]-j).toFixed(3);break;case"m":k=q[1],l=q[2];default:for(var r=1,s=q.length;s>r;r++)p[r]=+(q[r]-(r%2?i:j)).toFixed(3)}else{p=h[n]=[],"m"==q[0]&&(k=q[1]+i,l=q[2]+j);for(var t=0,u=q.length;u>t;t++)h[n][t]=q[t]}var v=h[n].length;switch(h[n][0]){case"z":i=k,j=l;break;case"h":i+=+h[n][v-1];break;case"v":j+=+h[n][v-1];break;default:i+=+h[n][v-2],j+=+h[n][v-1]}}return h.toString=e,d.rel=f(h),h}function z(b){var d=c(b);if(d.abs)return f(d.abs);if(I(b,"array")&&I(b&&b[0],"array")||(b=a.parsePathString(b)),!b||!b.length)return[["M",0,0]];var g,h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=+b[0][1],j=+b[0][2],k=i,l=j,m++,h[0]=["M",i,j]);for(var n,o,p=3==b.length&&"M"==b[0][0]&&"R"==b[1][0].toUpperCase()&&"Z"==b[2][0].toUpperCase(),q=m,r=b.length;r>q;q++){if(h.push(n=[]),o=b[q],g=o[0],g!=g.toUpperCase())switch(n[0]=g.toUpperCase(),n[0]){case"A":n[1]=o[1],n[2]=o[2],n[3]=o[3],n[4]=o[4],n[5]=o[5],n[6]=+o[6]+i,n[7]=+o[7]+j;break;case"V":n[1]=+o[1]+j;break;case"H":n[1]=+o[1]+i;break;case"R":for(var s=[i,j].concat(o.slice(1)),t=2,u=s.length;u>t;t++)s[t]=+s[t]+i,s[++t]=+s[t]+j;h.pop(),h=h.concat(G(s,p));break;case"O":h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);break;case"U":h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));break;case"M":k=+o[1]+i,l=+o[2]+j;default:for(t=1,u=o.length;u>t;t++)n[t]=+o[t]+(t%2?i:j)}else if("R"==g)s=[i,j].concat(o.slice(1)),h.pop(),h=h.concat(G(s,p)),n=["R"].concat(o.slice(-2));else if("O"==g)h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);else if("U"==g)h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));else for(var v=0,w=o.length;w>v;v++)n[v]=o[v];if(g=g.toUpperCase(),"O"!=g)switch(n[0]){case"Z":i=+k,j=+l;break;case"H":i=n[1];break;case"V":j=n[1];break;case"M":k=n[n.length-2],l=n[n.length-1];default:i=n[n.length-2],j=n[n.length-1]}}return h.toString=e,d.abs=f(h),h}function A(a,b,c,d){return[a,b,c,d,c,d]}function B(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]}function C(b,c,d,e,f,g,h,i,j,k){var l,m=120*O/180,n=O/180*(+f||0),o=[],p=a._.cacher(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(b,c,-n),b=l.x,c=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(O/180*f),N.sin(O/180*f),(b-i)/2),r=(c-j)/2,s=q*q/(d*d)+r*r/(e*e);s>1&&(s=N.sqrt(s),d=s*d,e=s*e);var t=d*d,u=e*e,v=(g==h?-1:1)*N.sqrt(S((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*d*r/e+(b+i)/2,x=v*-e*q/d+(c+j)/2,y=N.asin(((c-x)/e).toFixed(9)),z=N.asin(((j-x)/e).toFixed(9));y=w>b?O-y:y,z=w>i?O-z:z,0>y&&(y=2*O+y),0>z&&(z=2*O+z),h&&y>z&&(y-=2*O),!h&&z>y&&(z-=2*O)}var A=z-y;if(S(A)>m){var B=z,D=i,E=j;z=y+m*(h&&z>y?1:-1),i=w+d*N.cos(z),j=x+e*N.sin(z),o=C(i,j,d,e,f,0,h,D,E,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),J=N.tan(A/4),K=4/3*d*J,L=4/3*e*J,M=[b,c],P=[b+K*G,c-L*F],Q=[i+K*I,j-L*H],R=[i,j];if(P[0]=2*M[0]-P[0],P[1]=2*M[1]-P[1],k)return[P,Q,R].concat(o);o=[P,Q,R].concat(o).join().split(",");for(var T=[],U=0,V=o.length;V>U;U++)T[U]=U%2?p(o[U-1],o[U],n).y:p(o[U],o[U+1],n).x;return T}function D(a,b,c,d,e,f,g,h){for(var i,j,k,l,m,n,o,p,q=[],r=[[],[]],s=0;2>s;++s)if(0==s?(j=6*a-12*c+6*e,i=-3*a+9*c-9*e+3*g,k=3*c-3*a):(j=6*b-12*d+6*f,i=-3*b+9*d-9*f+3*h,k=3*d-3*b),S(i)<1e-12){if(S(j)<1e-12)continue;l=-k/j,l>0&&1>l&&q.push(l)}else o=j*j-4*k*i,p=N.sqrt(o),0>o||(m=(-j+p)/(2*i),m>0&&1>m&&q.push(m),n=(-j-p)/(2*i),n>0&&1>n&&q.push(n));for(var t,u=q.length,v=u;u--;)l=q[u],t=1-l,r[0][u]=t*t*t*a+3*t*t*l*c+3*t*l*l*e+l*l*l*g,r[1][u]=t*t*t*b+3*t*t*l*d+3*t*l*l*f+l*l*l*h;return r[0][v]=a,r[1][v]=b,r[0][v+1]=g,r[1][v+1]=h,r[0].length=r[1].length=v+2,{min:{x:P.apply(0,r[0]),y:P.apply(0,r[1])},max:{x:Q.apply(0,r[0]),y:Q.apply(0,r[1])}}}function E(a,b){var d=!b&&c(a);if(!b&&d.curve)return f(d.curve);for(var e=z(a),g=b&&z(b),h={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},i={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},j=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"].concat(C.apply(0,[b.x,b.y].concat(a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e].concat(a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"].concat(B(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"].concat(B(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"].concat(A(b.x,b.y,a[1],a[2]));break;case"H":a=["C"].concat(A(b.x,b.y,a[1],b.y));break;case"V":a=["C"].concat(A(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"].concat(A(b.x,b.y,b.X,b.Y))}return a}),k=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)m[b]="A",g&&(n[b]="A"),a.splice(b++,0,["C"].concat(c.splice(0,6)));a.splice(b,1),r=Q(e.length,g&&g.length||0)}},l=function(a,b,c,d,f){a&&b&&"M"==a[f][0]&&"M"!=b[f][0]&&(b.splice(f,0,["M",d.x,d.y]),c.bx=0,c.by=0,c.x=a[f][1],c.y=a[f][2],r=Q(e.length,g&&g.length||0))},m=[],n=[],o="",p="",q=0,r=Q(e.length,g&&g.length||0);r>q;q++){e[q]&&(o=e[q][0]),"C"!=o&&(m[q]=o,q&&(p=m[q-1])),e[q]=j(e[q],h,p),"A"!=m[q]&&"C"==o&&(m[q]="C"),k(e,q),g&&(g[q]&&(o=g[q][0]),"C"!=o&&(n[q]=o,q&&(p=n[q-1])),g[q]=j(g[q],i,p),"A"!=n[q]&&"C"==o&&(n[q]="C"),k(g,q)),l(e,g,h,i,q),l(g,e,i,h,q);var s=e[q],t=g&&g[q],u=s.length,v=g&&t.length;h.x=s[u-2],h.y=s[u-1],h.bx=M(s[u-4])||h.x,h.by=M(s[u-3])||h.y,i.bx=g&&(M(t[v-4])||i.x),i.by=g&&(M(t[v-3])||i.y),i.x=g&&t[v-2],i.y=g&&t[v-1]}return g||(d.curve=f(e)),g?[e,g]:e}function F(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=E(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a}function G(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}var H=b.prototype,I=a.is,J=a._.clone,K="hasOwnProperty",L=/,?([a-z]),?/gi,M=parseFloat,N=Math,O=N.PI,P=N.min,Q=N.max,R=N.pow,S=N.abs,T=h(1),U=h(),V=h(0,1),W=a._unit2px,X={path:function(a){return a.attr("path")},circle:function(a){var b=W(a);return x(b.cx,b.cy,b.r)},ellipse:function(a){var b=W(a);return x(b.cx||0,b.cy||0,b.rx,b.ry)},rect:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height,b.rx,b.ry)},image:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height)},line:function(a){return"M"+[a.attr("x1")||0,a.attr("y1")||0,a.attr("x2"),a.attr("y2")]},polyline:function(a){return"M"+a.attr("points")},polygon:function(a){return"M"+a.attr("points")+"z"},deflt:function(a){var b=a.node.getBBox();return w(b.x,b.y,b.width,b.height)}};a.path=c,a.path.getTotalLength=T,a.path.getPointAtLength=U,a.path.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return V(a,b).end;var d=V(a,c,1);return b?V(d,b).end:d},H.getTotalLength=function(){return this.node.getTotalLength?this.node.getTotalLength():void 0},H.getPointAtLength=function(a){return U(this.attr("d"),a)},H.getSubpath=function(b,c){return a.path.getSubpath(this.attr("d"),b,c)},a._.box=d,a.path.findDotsAtSegment=i,a.path.bezierBBox=j,a.path.isPointInsideBBox=k,a.closest=function(b,c,e,f){for(var g=100,h=d(b-g/2,c-g/2,g,g),i=[],j=e[0].hasOwnProperty("x")?function(a){return{x:e[a].x,y:e[a].y}}:function(a){return{x:e[a],y:f[a]}},l=0;1e6>=g&&!l;){for(var m=0,n=e.length;n>m;m++){var o=j(m);if(k(h,o.x,o.y)){l++,i.push(o);break}}l||(g*=2,h=d(b-g/2,c-g/2,g,g))}if(1e6!=g){var p,q=1/0;for(m=0,n=i.length;n>m;m++){var r=a.len(b,c,i[m].x,i[m].y);q>r&&(q=r,i[m].len=r,p=i[m])}return p}},a.path.isBBoxIntersect=l,a.path.intersection=r,a.path.intersectionNumber=s,a.path.isPointInside=u,a.path.getBBox=v,a.path.get=X,a.path.toRelative=y,a.path.toAbsolute=z,a.path.toCubic=E,a.path.map=F,a.path.toString=e,a.path.clone=f}),d.plugin(function(a){var d=Math.max,e=Math.min,f=function(a){if(this.items=[],this.bindings={},this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)a[b]&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},g=f.prototype;g.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],a&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},g.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},g.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this},g.animate=function(d,e,f,g){"function"!=typeof f||f.length||(g=f,f=c.linear),d instanceof a._.Animation&&(g=d.callback,f=d.easing,e=f.dur,d=d.attr);var h=arguments;if(a.is(d,"array")&&a.is(h[h.length-1],"array"))var i=!0;var j,k=function(){j?this.b=j:j=this.b},l=0,m=this,n=g&&function(){++l==m.length&&g.call(this)
+};return this.forEach(function(a,c){b.once("snap.animcreated."+a.id,k),i?h[c]&&a.animate.apply(a,h[c]):a.animate(d,e,f,n)})},g.remove=function(){for(;this.length;)this.pop().remove();return this},g.bind=function(a,b,c){var d={};if("function"==typeof b)this.bindings[a]=b;else{var e=c||a;this.bindings[a]=function(a){d[e]=a,b.attr(d)}}return this},g.attr=function(a){var b={};for(var c in a)this.bindings[c]?this.bindings[c](a[c]):b[c]=a[c];for(var d=0,e=this.items.length;e>d;d++)this.items[d].attr(b);return this},g.clear=function(){for(;this.length;)this.pop()},g.splice=function(a,b){a=0>a?d(this.length+a,0):a,b=d(0,e(this.length-a,b));var c,g=[],h=[],i=[];for(c=2;c<arguments.length;c++)i.push(arguments[c]);for(c=0;b>c;c++)h.push(this[a+c]);for(;c<this.length-a;c++)g.push(this[a+c]);var j=i.length;for(c=0;c<j+g.length;c++)this.items[a+c]=this[a+c]=j>c?i[c]:g[c-j];for(c=this.items.length=this.length-=b-j;this[c];)delete this[c++];return new f(h)},g.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0;return!1},g.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},g.getBBox=function(){for(var a=[],b=[],c=[],f=[],g=this.items.length;g--;)if(!this.items[g].removed){var h=this.items[g].getBBox();a.push(h.x),b.push(h.y),c.push(h.x+h.width),f.push(h.y+h.height)}return a=e.apply(0,a),b=e.apply(0,b),c=d.apply(0,c),f=d.apply(0,f),{x:a,y:b,x2:c,y2:f,width:c-a,height:f-b,cx:a+(c-a)/2,cy:b+(f-b)/2}},g.clone=function(a){a=new f;for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},g.toString=function(){return"Snap‘s set"},g.type="set",a.Set=f,a.set=function(){var a=new f;return arguments.length&&a.push.apply(a,Array.prototype.slice.call(arguments,0)),a}}),d.plugin(function(a,c){function d(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}}function e(b,c,e){c=p(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];for(var f,g,h,i,l=Math.max(b.length,c.length),m=[],n=[],o=0;l>o;o++){if(h=b[o]||d(c[o]),i=c[o]||d(h),h[0]!=i[0]||"r"==h[0].toLowerCase()&&(h[2]!=i[2]||h[3]!=i[3])||"s"==h[0].toLowerCase()&&(h[3]!=i[3]||h[4]!=i[4])){b=a._.transform2matrix(b,e()),c=a._.transform2matrix(c,e()),m=[["m",b.a,b.b,b.c,b.d,b.e,b.f]],n=[["m",c.a,c.b,c.c,c.d,c.e,c.f]];break}for(m[o]=[],n[o]=[],f=0,g=Math.max(h.length,i.length);g>f;f++)f in h&&(m[o][f]=h[f]),f in i&&(n[o][f]=i[f])}return{from:k(m),to:k(n),f:j(m)}}function f(a){return a}function g(a){return function(b){return+b.toFixed(3)+a}}function h(a){return a.join(" ")}function i(b){return a.rgb(b[0],b[1],b[2])}function j(a){var b,c,d,e,f,g,h=0,i=[];for(b=0,c=a.length;c>b;b++){for(f="[",g=['"'+a[b][0]+'"'],d=1,e=a[b].length;e>d;d++)g[d]="val["+h++ +"]";f+=g+"]",i[b]=f}return Function("val","return Snap.path.toString.call(["+i+"])")}function k(a){for(var b=[],c=0,d=a.length;d>c;c++)for(var e=1,f=a[c].length;f>e;e++)b.push(a[c][e]);return b}function l(a){return isFinite(parseFloat(a))}function m(b,c){return a.is(b,"array")&&a.is(c,"array")?b.toString()==c.toString():!1}var n={},o=/[a-z]+$/i,p=String;n.stroke=n.fill="colour",c.prototype.equal=function(a,c){return b("snap.util.equal",this,a,c).firstDefined()},b.on("snap.util.equal",function(b,c){var d,q,r=p(this.attr(b)||""),s=this;if(l(r)&&l(c))return{from:parseFloat(r),to:parseFloat(c),f:f};if("colour"==n[b])return d=a.color(r),q=a.color(c),{from:[d.r,d.g,d.b,d.opacity],to:[q.r,q.g,q.b,q.opacity],f:i};if("viewBox"==b)return d=this.attr(b).vb.split(" ").map(Number),q=c.split(" ").map(Number),{from:d,to:q,f:h};if("transform"==b||"gradientTransform"==b||"patternTransform"==b)return c instanceof a.Matrix&&(c=c.toTransformString()),a._.rgTransform.test(c)||(c=a._.svgTransform2string(c)),e(r,c,function(){return s.getBBox(1)});if("d"==b||"path"==b)return d=a.path.toCubic(r,c),{from:k(d[0]),to:k(d[1]),f:j(d[0])};if("points"==b)return d=p(r).split(a._.separator),q=p(c).split(a._.separator),{from:d,to:q,f:function(a){return a}};var t=r.match(o),u=p(c).match(o);return t&&m(t,u)?{from:parseFloat(r),to:parseFloat(c),f:g(t)}:{from:this.asPX(b),to:this.asPX(b,c),f:f}})}),d.plugin(function(a,c,d,e){for(var f=c.prototype,g="hasOwnProperty",h=("createTouch"in e.doc),i=["click","dblclick","mousedown","mousemove","mouseout","mouseover","mouseup","touchstart","touchmove","touchend","touchcancel"],j={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},k=(function(a,b){var c="y"==a?"scrollTop":"scrollLeft",d=b&&b.node?b.node.ownerDocument:e.doc;return d[c in d.documentElement?"documentElement":"body"][c]}),l=function(){return this.originalEvent.preventDefault()},m=function(){return this.originalEvent.stopPropagation()},n=function(a,b,c,d){var e=h&&j[b]?j[b]:b,f=function(e){var f=k("y",d),i=k("x",d);if(h&&j[g](b))for(var n=0,o=e.targetTouches&&e.targetTouches.length;o>n;n++)if(e.targetTouches[n].target==a||a.contains(e.targetTouches[n].target)){var p=e;e=e.targetTouches[n],e.originalEvent=p,e.preventDefault=l,e.stopPropagation=m;break}var q=e.clientX+i,r=e.clientY+f;return c.call(d,e,q,r)};return b!==e&&a.addEventListener(b,f,!1),a.addEventListener(e,f,!1),function(){return b!==e&&a.removeEventListener(b,f,!1),a.removeEventListener(e,f,!1),!0}},o=[],p=function(a){for(var c,d=a.clientX,e=a.clientY,f=k("y"),g=k("x"),i=o.length;i--;){if(c=o[i],h){for(var j,l=a.touches&&a.touches.length;l--;)if(j=a.touches[l],j.identifier==c.el._drag.id||c.el.node.contains(j.target)){d=j.clientX,e=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();{var m=c.el.node;m.nextSibling,m.parentNode,m.style.display}d+=g,e+=f,b("snap.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},q=function(c){a.unmousemove(p).unmouseup(q);for(var d,e=o.length;e--;)d=o[e],d.el._drag={},b("snap.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c),b.off("snap.drag.*."+d.el.id);o=[]},r=i.length;r--;)!function(b){a[b]=f[b]=function(c,d){if(a.is(c,"function"))this.events=this.events||[],this.events.push({name:b,f:c,unbind:n(this.node||document,b,c,d||this)});else for(var e=0,f=this.events.length;f>e;e++)if(this.events[e].name==b)try{this.events[e].f.call(this)}catch(g){}return this},a["un"+b]=f["un"+b]=function(a){for(var c=this.events||[],d=c.length;d--;)if(c[d].name==b&&(c[d].f==a||!a))return c[d].unbind(),c.splice(d,1),!c.length&&delete this.events,this;return this}}(i[r]);f.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},f.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var s=[];f.drag=function(c,d,e,f,g,h){function i(i,j,l){(i.originalEvent||i).preventDefault(),k._drag.x=j,k._drag.y=l,k._drag.id=i.identifier,!o.length&&a.mousemove(p).mouseup(q),o.push({el:k,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("snap.drag.start."+k.id,d),c&&b.on("snap.drag.move."+k.id,c),e&&b.on("snap.drag.end."+k.id,e),b("snap.drag.start."+k.id,g||f||k,j,l,i)}function j(a,c,d){b("snap.draginit."+k.id,k,a,c,d)}var k=this;if(!arguments.length){var l;return k.drag(function(a,b){this.attr({transform:l+(l?"T":"t")+[a,b]})},function(){l=this.transform().local})}return b.on("snap.draginit."+k.id,i),k._drag={},s.push({el:k,start:i,init:j}),k.mousedown(j),k},f.undrag=function(){for(var c=s.length;c--;)s[c].el==this&&(this.unmousedown(s[c].init),s.splice(c,1),b.unbind("snap.drag.*."+this.id),b.unbind("snap.draginit."+this.id));return!s.length&&a.unmousemove(p).unmouseup(q),this}}),d.plugin(function(a,c,d){var e=(c.prototype,d.prototype),f=/^\s*url\((.+)\)/,g=String,h=a._.$;a.filter={},e.filter=function(b){var d=this;"svg"!=d.type&&(d=d.paper);var e=a.parse(g(b)),f=a._.id(),i=(d.node.offsetWidth,d.node.offsetHeight,h("filter"));return h(i,{id:f,filterUnits:"userSpaceOnUse"}),i.appendChild(e.node),d.defs.appendChild(i),new c(i)},b.on("snap.util.getattr.filter",function(){b.stop();var c=h(this.node,"filter");if(c){var d=g(c).match(f);return d&&a.select(d[1])}}),b.on("snap.util.attr.filter",function(d){if(d instanceof c&&"filter"==d.type){b.stop();var e=d.node.id;e||(h(d.node,{id:d.id}),e=d.id),h(this.node,{filter:a.url(e)})}d&&"none"!=d||(b.stop(),this.node.removeAttribute("filter"))}),a.filter.blur=function(b,c){null==b&&(b=2);var d=null==c?b:[b,c];return a.format('<feGaussianBlur stdDeviation="{def}"/>',{def:d})},a.filter.blur.toString=function(){return this()},a.filter.shadow=function(b,c,d,e,f){return"string"==typeof d&&(e=d,f=e,d=4),"string"!=typeof e&&(f=e,e="#000"),e=e||"#000",null==d&&(d=4),null==f&&(f=1),null==b&&(b=0,c=2),null==c&&(c=b),e=a.color(e),a.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>',{color:e,dx:b,dy:c,blur:d,opacity:f})},a.filter.shadow.toString=function(){return this()},a.filter.grayscale=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>',{a:.2126+.7874*(1-b),b:.7152-.7152*(1-b),c:.0722-.0722*(1-b),d:.2126-.2126*(1-b),e:.7152+.2848*(1-b),f:.0722-.0722*(1-b),g:.2126-.2126*(1-b),h:.0722+.9278*(1-b)})},a.filter.grayscale.toString=function(){return this()},a.filter.sepia=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>',{a:.393+.607*(1-b),b:.769-.769*(1-b),c:.189-.189*(1-b),d:.349-.349*(1-b),e:.686+.314*(1-b),f:.168-.168*(1-b),g:.272-.272*(1-b),h:.534-.534*(1-b),i:.131+.869*(1-b)})},a.filter.sepia.toString=function(){return this()},a.filter.saturate=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="saturate" values="{amount}"/>',{amount:1-b})},a.filter.saturate.toString=function(){return this()},a.filter.hueRotate=function(b){return b=b||0,a.format('<feColorMatrix type="hueRotate" values="{angle}"/>',{angle:b})},a.filter.hueRotate.toString=function(){return this()},a.filter.invert=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>',{amount:b,amount2:1-b})},a.filter.invert.toString=function(){return this()},a.filter.brightness=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>',{amount:b})},a.filter.brightness.toString=function(){return this()},a.filter.contrast=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>',{amount:b,amount2:.5-b/2})},a.filter.contrast.toString=function(){return this()}}),d.plugin(function(a,b){var c=a._.box,d=a.is,e=/^[^a-z]*([tbmlrc])/i,f=function(){return"T"+this.dx+","+this.dy};b.prototype.getAlign=function(a,b){null==b&&d(a,"string")&&(b=a,a=null),a=a||this.paper;var g=a.getBBox?a.getBBox():c(a),h=this.getBBox(),i={};switch(b=b&&b.match(e),b=b?b[1].toLowerCase():"c"){case"t":i.dx=0,i.dy=g.y-h.y;break;case"b":i.dx=0,i.dy=g.y2-h.y2;break;case"m":i.dx=0,i.dy=g.cy-h.cy;break;case"l":i.dx=g.x-h.x,i.dy=0;break;case"r":i.dx=g.x2-h.x2,i.dy=0;break;default:i.dx=g.cx-h.cx,i.dy=0}return i.toString=f,i},b.prototype.align=function(a,b){return this.transform("..."+this.getAlign(a,b))}}),d});
diff --git a/web/pgadmin/misc/static/explain/js/snap.svg.js b/web/pgadmin/misc/static/explain/js/snap.svg.js
new file mode 100644
index 0000000..ef0fb6d
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg.js
@@ -0,0 +1,8149 @@
+// Snap.svg 0.4.1
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// build: 2015-04-13
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ┌────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.4.2 - JavaScript Events Library                      │ \\
+// ├────────────────────────────────────────────────────────────┤ \\
+// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+// └────────────────────────────────────────────────────────────┘ \\
+(function (glob) {
+    var version = "0.4.2",
+        has = "hasOwnProperty",
+        separator = /[\.\/]/,
+        comaseparator = /\s*,\s*/,
+        wildcard = "*",
+        fun = function () {},
+        numsort = function (a, b) {
+            return a - b;
+        },
+        current_event,
+        stop,
+        events = {n: {}},
+        firstDefined = function () {
+            for (var i = 0, ii = this.length; i < ii; i++) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+        lastDefined = function () {
+            var i = this.length;
+            while (--i) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+    /*\
+     * eve
+     [ method ]
+     * Fires event with given `name`, given scope and other parameters.
+     > Arguments
+     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
+     - scope (object) context for the event handlers
+     - varargs (...) the rest of arguments will be sent to event handlers
+     = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value.
+    \*/
+        eve = function (name, scope) {
+            name = String(name);
+            var e = events,
+                oldstop = stop,
+                args = Array.prototype.slice.call(arguments, 2),
+                listeners = eve.listeners(name),
+                z = 0,
+                f = false,
+                l,
+                indexed = [],
+                queue = {},
+                out = [],
+                ce = current_event,
+                errors = [];
+            out.firstDefined = firstDefined;
+            out.lastDefined = lastDefined;
+            current_event = name;
+            stop = 0;
+            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                indexed.push(listeners[i].zIndex);
+                if (listeners[i].zIndex < 0) {
+                    queue[listeners[i].zIndex] = listeners[i];
+                }
+            }
+            indexed.sort(numsort);
+            while (indexed[z] < 0) {
+                l = queue[indexed[z++]];
+                out.push(l.apply(scope, args));
+                if (stop) {
+                    stop = oldstop;
+                    return out;
+                }
+            }
+            for (i = 0; i < ii; i++) {
+                l = listeners[i];
+                if ("zIndex" in l) {
+                    if (l.zIndex == indexed[z]) {
+                        out.push(l.apply(scope, args));
+                        if (stop) {
+                            break;
+                        }
+                        do {
+                            z++;
+                            l = queue[indexed[z]];
+                            l && out.push(l.apply(scope, args));
+                            if (stop) {
+                                break;
+                            }
+                        } while (l)
+                    } else {
+                        queue[l.zIndex] = l;
+                    }
+                } else {
+                    out.push(l.apply(scope, args));
+                    if (stop) {
+                        break;
+                    }
+                }
+            }
+            stop = oldstop;
+            current_event = ce;
+            return out;
+        };
+        // Undocumented. Debug only.
+        eve._events = events;
+    /*\
+     * eve.listeners
+     [ method ]
+     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
+     > Arguments
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated
+     = (array) array of event handlers
+    \*/
+    eve.listeners = function (name) {
+        var names = name.split(separator),
+            e = events,
+            item,
+            items,
+            k,
+            i,
+            ii,
+            j,
+            jj,
+            nes,
+            es = [e],
+            out = [];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            nes = [];
+            for (j = 0, jj = es.length; j < jj; j++) {
+                e = es[j].n;
+                items = [e[names[i]], e[wildcard]];
+                k = 2;
+                while (k--) {
+                    item = items[k];
+                    if (item) {
+                        nes.push(item);
+                        out = out.concat(item.f || []);
+                    }
+                }
+            }
+            es = nes;
+        }
+        return out;
+    };
+    /*\
+     * eve.on
+     [ method ]
+     **
+     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
+     | eve.on("*.under.*", f);
+     | eve("mouse.under.floor"); // triggers f
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
+     > Example:
+     | eve.on("mouse", eatIt)(2);
+     | eve.on("mouse", scream);
+     | eve.on("mouse", catchIt)(1);
+     * This will ensure that `catchIt` function will be called before `eatIt`.
+     *
+     * If you want to put your handler before non-indexed handlers, specify a negative value.
+     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
+    \*/
+    eve.on = function (name, f) {
+        name = String(name);
+        if (typeof f != "function") {
+            return function () {};
+        }
+        var names = name.split(comaseparator);
+        for (var i = 0, ii = names.length; i < ii; i++) {
+            (function (name) {
+                var names = name.split(separator),
+                    e = events,
+                    exist;
+                for (var i = 0, ii = names.length; i < ii; i++) {
+                    e = e.n;
+                    e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
+                }
+                e.f = e.f || [];
+                for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+                    exist = true;
+                    break;
+                }
+                !exist && e.f.push(f);
+            }(names[i]));
+        }
+        return function (zIndex) {
+            if (+zIndex == +zIndex) {
+                f.zIndex = +zIndex;
+            }
+        };
+    };
+    /*\
+     * eve.f
+     [ method ]
+     **
+     * Returns function that will fire given event with optional arguments.
+     * Arguments that will be passed to the result function will be also
+     * concated to the list of final arguments.
+     | el.onclick = eve.f("click", 1, 2);
+     | eve.on("click", function (a, b, c) {
+     |     console.log(a, b, c); // 1, 2, [event object]
+     | });
+     > Arguments
+     - event (string) event name
+     - varargs (…) and any other arguments
+     = (function) possible event handler function
+    \*/
+    eve.f = function (event) {
+        var attrs = [].slice.call(arguments, 1);
+        return function () {
+            eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
+        };
+    };
+    /*\
+     * eve.stop
+     [ method ]
+     **
+     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
+    \*/
+    eve.stop = function () {
+        stop = 1;
+    };
+    /*\
+     * eve.nt
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     > Arguments
+     **
+     - subname (string) #optional subname of the event
+     **
+     = (string) name of the event, if `subname` is not specified
+     * or
+     = (boolean) `true`, if current event’s name contains `subname`
+    \*/
+    eve.nt = function (subname) {
+        if (subname) {
+            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+        }
+        return current_event;
+    };
+    /*\
+     * eve.nts
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     **
+     = (array) names of the event
+    \*/
+    eve.nts = function () {
+        return current_event.split(separator);
+    };
+    /*\
+     * eve.off
+     [ method ]
+     **
+     * Removes given function from the list of event listeners assigned to given name.
+     * If no arguments specified all the events will be cleared.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+    \*/
+    /*\
+     * eve.unbind
+     [ method ]
+     **
+     * See @eve.off
+    \*/
+    eve.off = eve.unbind = function (name, f) {
+        if (!name) {
+            eve._events = events = {n: {}};
+            return;
+        }
+        var names = name.split(comaseparator);
+        if (names.length > 1) {
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                eve.off(names[i], f);
+            }
+            return;
+        }
+        names = name.split(separator);
+        var e,
+            key,
+            splice,
+            i, ii, j, jj,
+            cur = [events];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
+                splice = [j, 1];
+                e = cur[j].n;
+                if (names[i] != wildcard) {
+                    if (e[names[i]]) {
+                        splice.push(e[names[i]]);
+                    }
+                } else {
+                    for (key in e) if (e[has](key)) {
+                        splice.push(e[key]);
+                    }
+                }
+                cur.splice.apply(cur, splice);
+            }
+        }
+        for (i = 0, ii = cur.length; i < ii; i++) {
+            e = cur[i];
+            while (e.n) {
+                if (f) {
+                    if (e.f) {
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
+                            break;
+                        }
+                        !e.f.length && delete e.f;
+                    }
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        var funcs = e.n[key].f;
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
+                            break;
+                        }
+                        !funcs.length && delete e.n[key].f;
+                    }
+                } else {
+                    delete e.f;
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        delete e.n[key].f;
+                    }
+                }
+                e = e.n;
+            }
+        }
+    };
+    /*\
+     * eve.once
+     [ method ]
+     **
+     * Binds given event handler with a given name to only run once then unbind itself.
+     | eve.once("login", f);
+     | eve("login"); // triggers f
+     | eve("login"); // no listeners
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) same return function as @eve.on
+    \*/
+    eve.once = function (name, f) {
+        var f2 = function () {
+            eve.unbind(name, f2);
+            return f.apply(this, arguments);
+        };
+        return eve.on(name, f2);
+    };
+    /*\
+     * eve.version
+     [ property (string) ]
+     **
+     * Current version of the library.
+    \*/
+    eve.version = version;
+    eve.toString = function () {
+        return "You are running Eve " + version;
+    };
+    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
+})(this);
+
+(function (glob, factory) {
+    // AMD support
+    if (typeof define == "function" && define.amd) {
+        // Define as an anonymous module
+        define(["eve"], function (eve) {
+            return factory(glob, eve);
+        });
+    } else if (typeof exports != 'undefined') {
+        // Next for Node.js or CommonJS
+        var eve = require('eve');
+        module.exports = factory(glob, eve);
+    } else {
+        // Browser globals (glob is window)
+        // Snap adds itself to window
+        factory(glob, glob.eve);
+    }
+}(window || this, function (window, eve) {
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+var mina = (function (eve) {
+    var animations = {},
+    requestAnimFrame = window.requestAnimationFrame       ||
+                       window.webkitRequestAnimationFrame ||
+                       window.mozRequestAnimationFrame    ||
+                       window.oRequestAnimationFrame      ||
+                       window.msRequestAnimationFrame     ||
+                       function (callback) {
+                           setTimeout(callback, 16);
+                       },
+    isArray = Array.isArray || function (a) {
+        return a instanceof Array ||
+            Object.prototype.toString.call(a) == "[object Array]";
+    },
+    idgen = 0,
+    idprefix = "M" + (+new Date).toString(36),
+    ID = function () {
+        return idprefix + (idgen++).toString(36);
+    },
+    diff = function (a, b, A, B) {
+        if (isArray(a)) {
+            res = [];
+            for (var i = 0, ii = a.length; i < ii; i++) {
+                res[i] = diff(a[i], b, A[i], B);
+            }
+            return res;
+        }
+        var dif = (A - a) / (B - b);
+        return function (bb) {
+            return a + dif * (bb - b);
+        };
+    },
+    timer = Date.now || function () {
+        return +new Date;
+    },
+    sta = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.s;
+        }
+        var ds = a.s - val;
+        a.b += a.dur * ds;
+        a.B += a.dur * ds;
+        a.s = val;
+    },
+    speed = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.spd;
+        }
+        a.spd = val;
+    },
+    duration = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.dur;
+        }
+        a.s = a.s * val / a.dur;
+        a.dur = val;
+    },
+    stopit = function () {
+        var a = this;
+        delete animations[a.id];
+        a.update();
+        eve("mina.stop." + a.id, a);
+    },
+    pause = function () {
+        var a = this;
+        if (a.pdif) {
+            return;
+        }
+        delete animations[a.id];
+        a.update();
+        a.pdif = a.get() - a.b;
+    },
+    resume = function () {
+        var a = this;
+        if (!a.pdif) {
+            return;
+        }
+        a.b = a.get() - a.pdif;
+        delete a.pdif;
+        animations[a.id] = a;
+    },
+    update = function () {
+        var a = this,
+            res;
+        if (isArray(a.start)) {
+            res = [];
+            for (var j = 0, jj = a.start.length; j < jj; j++) {
+                res[j] = +a.start[j] +
+                    (a.end[j] - a.start[j]) * a.easing(a.s);
+            }
+        } else {
+            res = +a.start + (a.end - a.start) * a.easing(a.s);
+        }
+        a.set(res);
+    },
+    frame = function () {
+        var len = 0;
+        for (var i in animations) if (animations.hasOwnProperty(i)) {
+            var a = animations[i],
+                b = a.get(),
+                res;
+            len++;
+            a.s = (b - a.b) / (a.dur / a.spd);
+            if (a.s >= 1) {
+                delete animations[i];
+                a.s = 1;
+                len--;
+                (function (a) {
+                    setTimeout(function () {
+                        eve("mina.finish." + a.id, a);
+                    });
+                }(a));
+            }
+            a.update();
+        }
+        len && requestAnimFrame(frame);
+    },
+    /*\
+     * mina
+     [ method ]
+     **
+     * Generic animation of numbers
+     **
+     - a (number) start _slave_ number
+     - A (number) end _slave_ number
+     - b (number) start _master_ number (start time in general case)
+     - B (number) end _master_ number (end time in gereal case)
+     - get (function) getter of _master_ number (see @mina.time)
+     - set (function) setter of _slave_ number
+     - easing (function) #optional easing function, default is @mina.linear
+     = (object) animation descriptor
+     o {
+     o         id (string) animation id,
+     o         start (number) start _slave_ number,
+     o         end (number) end _slave_ number,
+     o         b (number) start _master_ number,
+     o         s (number) animation status (0..1),
+     o         dur (number) animation duration,
+     o         spd (number) animation speed,
+     o         get (function) getter of _master_ number (see @mina.time),
+     o         set (function) setter of _slave_ number,
+     o         easing (function) easing function, default is @mina.linear,
+     o         status (function) status getter/setter,
+     o         speed (function) speed getter/setter,
+     o         duration (function) duration getter/setter,
+     o         stop (function) animation stopper
+     o         pause (function) pauses the animation
+     o         resume (function) resumes the animation
+     o         update (function) calles setter with the right value of the animation
+     o }
+    \*/
+    mina = function (a, A, b, B, get, set, easing) {
+        var anim = {
+            id: ID(),
+            start: a,
+            end: A,
+            b: b,
+            s: 0,
+            dur: B - b,
+            spd: 1,
+            get: get,
+            set: set,
+            easing: easing || mina.linear,
+            status: sta,
+            speed: speed,
+            duration: duration,
+            stop: stopit,
+            pause: pause,
+            resume: resume,
+            update: update
+        };
+        animations[anim.id] = anim;
+        var len = 0, i;
+        for (i in animations) if (animations.hasOwnProperty(i)) {
+            len++;
+            if (len == 2) {
+                break;
+            }
+        }
+        len == 1 && requestAnimFrame(frame);
+        return anim;
+    };
+    /*\
+     * mina.time
+     [ method ]
+     **
+     * Returns the current time. Equivalent to:
+     | function () {
+     |     return (new Date).getTime();
+     | }
+    \*/
+    mina.time = timer;
+    /*\
+     * mina.getById
+     [ method ]
+     **
+     * Returns an animation by its id
+     - id (string) animation's id
+     = (object) See @mina
+    \*/
+    mina.getById = function (id) {
+        return animations[id] || null;
+    };
+
+    /*\
+     * mina.linear
+     [ method ]
+     **
+     * Default linear easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.linear = function (n) {
+        return n;
+    };
+    /*\
+     * mina.easeout
+     [ method ]
+     **
+     * Easeout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeout = function (n) {
+        return Math.pow(n, 1.7);
+    };
+    /*\
+     * mina.easein
+     [ method ]
+     **
+     * Easein easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easein = function (n) {
+        return Math.pow(n, .48);
+    };
+    /*\
+     * mina.easeinout
+     [ method ]
+     **
+     * Easeinout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeinout = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        if (n == 0) {
+            return 0;
+        }
+        var q = .48 - n / 1.04,
+            Q = Math.sqrt(.1734 + q * q),
+            x = Q - q,
+            X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+            y = -Q - q,
+            Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+            t = X + Y + .5;
+        return (1 - t) * 3 * t * t + t * t * t;
+    };
+    /*\
+     * mina.backin
+     [ method ]
+     **
+     * Backin easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backin = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        var s = 1.70158;
+        return n * n * ((s + 1) * n - s);
+    };
+    /*\
+     * mina.backout
+     [ method ]
+     **
+     * Backout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backout = function (n) {
+        if (n == 0) {
+            return 0;
+        }
+        n = n - 1;
+        var s = 1.70158;
+        return n * n * ((s + 1) * n + s) + 1;
+    };
+    /*\
+     * mina.elastic
+     [ method ]
+     **
+     * Elastic easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.elastic = function (n) {
+        if (n == !!n) {
+            return n;
+        }
+        return Math.pow(2, -10 * n) * Math.sin((n - .075) *
+            (2 * Math.PI) / .3) + 1;
+    };
+    /*\
+     * mina.bounce
+     [ method ]
+     **
+     * Bounce easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.bounce = function (n) {
+        var s = 7.5625,
+            p = 2.75,
+            l;
+        if (n < (1 / p)) {
+            l = s * n * n;
+        } else {
+            if (n < (2 / p)) {
+                n -= (1.5 / p);
+                l = s * n * n + .75;
+            } else {
+                if (n < (2.5 / p)) {
+                    n -= (2.25 / p);
+                    l = s * n * n + .9375;
+                } else {
+                    n -= (2.625 / p);
+                    l = s * n * n + .984375;
+                }
+            }
+        }
+        return l;
+    };
+    window.mina = mina;
+    return mina;
+})(typeof eve == "undefined" ? function () {} : eve);
+// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var Snap = (function(root) {
+Snap.version = "0.4.0";
+/*\
+ * Snap
+ [ method ]
+ **
+ * Creates a drawing surface or wraps existing SVG element.
+ **
+ - width (number|string) width of surface
+ - height (number|string) height of surface
+ * or
+ - DOM (SVGElement) element to be wrapped into Snap structure
+ * or
+ - array (array) array of elements (will return set of elements)
+ * or
+ - query (string) CSS query selector
+ = (object) @Element
+\*/
+function Snap(w, h) {
+    if (w) {
+        if (w.nodeType) {
+            return wrap(w);
+        }
+        if (is(w, "array") && Snap.set) {
+            return Snap.set.apply(Snap, w);
+        }
+        if (w instanceof Element) {
+            return w;
+        }
+        if (h == null) {
+            w = glob.doc.querySelector(String(w));
+            return wrap(w);
+        }
+    }
+    w = w == null ? "100%" : w;
+    h = h == null ? "100%" : h;
+    return new Paper(w, h);
+}
+Snap.toString = function () {
+    return "Snap v" + this.version;
+};
+Snap._ = {};
+var glob = {
+    win: root.window,
+    doc: root.window.document
+};
+Snap._.glob = glob;
+var has = "hasOwnProperty",
+    Str = String,
+    toFloat = parseFloat,
+    toInt = parseInt,
+    math = Math,
+    mmax = math.max,
+    mmin = math.min,
+    abs = math.abs,
+    pow = math.pow,
+    PI = math.PI,
+    round = math.round,
+    E = "",
+    S = " ",
+    objectToString = Object.prototype.toString,
+    ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+    colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,
+    bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+    reURLValue = /^url\(#?([^)]+)\)$/,
+    separator = Snap._.separator = /[,\s]+/,
+    whitespace = /[\s]/g,
+    commaSpaces = /[\s]*,[\s]*/,
+    hsrg = {hs: 1, rg: 1},
+    pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig,
+    idgen = 0,
+    idprefix = "S" + (+new Date).toString(36),
+    ID = function (el) {
+        return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36);
+    },
+    xlink = "http://www.w3.org/1999/xlink",
+    xmlns = "http://www.w3.org/2000/svg",
+    hub = {},
+    URL = Snap.url = function (url) {
+        return "url('#" + url + "')";
+    };
+
+function $(el, attr) {
+    if (attr) {
+        if (el == "#text") {
+            el = glob.doc.createTextNode(attr.text || attr["#text"] || "");
+        }
+        if (el == "#comment") {
+            el = glob.doc.createComment(attr.text || attr["#text"] || "");
+        }
+        if (typeof el == "string") {
+            el = $(el);
+        }
+        if (typeof attr == "string") {
+            if (el.nodeType == 1) {
+                if (attr.substring(0, 6) == "xlink:") {
+                    return el.getAttributeNS(xlink, attr.substring(6));
+                }
+                if (attr.substring(0, 4) == "xml:") {
+                    return el.getAttributeNS(xmlns, attr.substring(4));
+                }
+                return el.getAttribute(attr);
+            } else if (attr == "text") {
+                return el.nodeValue;
+            } else {
+                return null;
+            }
+        }
+        if (el.nodeType == 1) {
+            for (var key in attr) if (attr[has](key)) {
+                var val = Str(attr[key]);
+                if (val) {
+                    if (key.substring(0, 6) == "xlink:") {
+                        el.setAttributeNS(xlink, key.substring(6), val);
+                    } else if (key.substring(0, 4) == "xml:") {
+                        el.setAttributeNS(xmlns, key.substring(4), val);
+                    } else {
+                        el.setAttribute(key, val);
+                    }
+                } else {
+                    el.removeAttribute(key);
+                }
+            }
+        } else if ("text" in attr) {
+            el.nodeValue = attr.text;
+        }
+    } else {
+        el = glob.doc.createElementNS(xmlns, el);
+    }
+    return el;
+}
+Snap._.$ = $;
+Snap._.id = ID;
+function getAttrs(el) {
+    var attrs = el.attributes,
+        name,
+        out = {};
+    for (var i = 0; i < attrs.length; i++) {
+        if (attrs[i].namespaceURI == xlink) {
+            name = "xlink:";
+        } else {
+            name = "";
+        }
+        name += attrs[i].name;
+        out[name] = attrs[i].textContent;
+    }
+    return out;
+}
+function is(o, type) {
+    type = Str.prototype.toLowerCase.call(type);
+    if (type == "finite") {
+        return isFinite(o);
+    }
+    if (type == "array" &&
+        (o instanceof Array || Array.isArray && Array.isArray(o))) {
+        return true;
+    }
+    return  (type == "null" && o === null) ||
+            (type == typeof o && o !== null) ||
+            (type == "object" && o === Object(o)) ||
+            objectToString.call(o).slice(8, -1).toLowerCase() == type;
+}
+/*\
+ * Snap.format
+ [ method ]
+ **
+ * Replaces construction of type `{<name>}` to the corresponding argument
+ **
+ - token (string) string to format
+ - json (object) object which properties are used as a replacement
+ = (string) formatted string
+ > Usage
+ | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z"
+ | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
+ |     x: 10,
+ |     y: 20,
+ |     dim: {
+ |         width: 40,
+ |         height: 50,
+ |         "negative width": -40
+ |     }
+ | }));
+\*/
+Snap.format = (function () {
+    var tokenRegex = /\{([^\}]+)\}/g,
+        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+        replacer = function (all, key, obj) {
+            var res = obj;
+            key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+                name = name || quotedName;
+                if (res) {
+                    if (name in res) {
+                        res = res[name];
+                    }
+                    typeof res == "function" && isFunc && (res = res());
+                }
+            });
+            res = (res == null || res == obj ? all : res) + "";
+            return res;
+        };
+    return function (str, obj) {
+        return Str(str).replace(tokenRegex, function (all, key) {
+            return replacer(all, key, obj);
+        });
+    };
+})();
+function clone(obj) {
+    if (typeof obj == "function" || Object(obj) !== obj) {
+        return obj;
+    }
+    var res = new obj.constructor;
+    for (var key in obj) if (obj[has](key)) {
+        res[key] = clone(obj[key]);
+    }
+    return res;
+}
+Snap._.clone = clone;
+function repush(array, item) {
+    for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+        return array.push(array.splice(i, 1)[0]);
+    }
+}
+function cacher(f, scope, postprocessor) {
+    function newf() {
+        var arg = Array.prototype.slice.call(arguments, 0),
+            args = arg.join("\u2400"),
+            cache = newf.cache = newf.cache || {},
+            count = newf.count = newf.count || [];
+        if (cache[has](args)) {
+            repush(count, args);
+            return postprocessor ? postprocessor(cache[args]) : cache[args];
+        }
+        count.length >= 1e3 && delete cache[count.shift()];
+        count.push(args);
+        cache[args] = f.apply(scope, arg);
+        return postprocessor ? postprocessor(cache[args]) : cache[args];
+    }
+    return newf;
+}
+Snap._.cacher = cacher;
+function angle(x1, y1, x2, y2, x3, y3) {
+    if (x3 == null) {
+        var x = x1 - x2,
+            y = y1 - y2;
+        if (!x && !y) {
+            return 0;
+        }
+        return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+    } else {
+        return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3);
+    }
+}
+function rad(deg) {
+    return deg % 360 * PI / 180;
+}
+function deg(rad) {
+    return rad * 180 / PI % 360;
+}
+function x_y() {
+    return this.x + S + this.y;
+}
+function x_y_w_h() {
+    return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+}
+
+/*\
+ * Snap.rad
+ [ method ]
+ **
+ * Transform angle to radians
+ - deg (number) angle in degrees
+ = (number) angle in radians
+\*/
+Snap.rad = rad;
+/*\
+ * Snap.deg
+ [ method ]
+ **
+ * Transform angle to degrees
+ - rad (number) angle in radians
+ = (number) angle in degrees
+\*/
+Snap.deg = deg;
+/*\
+ * Snap.sin
+ [ method ]
+ **
+ * Equivalent to `Math.sin()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) sin
+\*/
+Snap.sin = function (angle) {
+    return math.sin(Snap.rad(angle));
+};
+/*\
+ * Snap.tan
+ [ method ]
+ **
+ * Equivalent to `Math.tan()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) tan
+\*/
+Snap.tan = function (angle) {
+    return math.tan(Snap.rad(angle));
+};
+/*\
+ * Snap.cos
+ [ method ]
+ **
+ * Equivalent to `Math.cos()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) cos
+\*/
+Snap.cos = function (angle) {
+    return math.cos(Snap.rad(angle));
+};
+/*\
+ * Snap.asin
+ [ method ]
+ **
+ * Equivalent to `Math.asin()` only works with degrees, not radians.
+ - num (number) value
+ = (number) asin in degrees
+\*/
+Snap.asin = function (num) {
+    return Snap.deg(math.asin(num));
+};
+/*\
+ * Snap.acos
+ [ method ]
+ **
+ * Equivalent to `Math.acos()` only works with degrees, not radians.
+ - num (number) value
+ = (number) acos in degrees
+\*/
+Snap.acos = function (num) {
+    return Snap.deg(math.acos(num));
+};
+/*\
+ * Snap.atan
+ [ method ]
+ **
+ * Equivalent to `Math.atan()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan in degrees
+\*/
+Snap.atan = function (num) {
+    return Snap.deg(math.atan(num));
+};
+/*\
+ * Snap.atan2
+ [ method ]
+ **
+ * Equivalent to `Math.atan2()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan2 in degrees
+\*/
+Snap.atan2 = function (num) {
+    return Snap.deg(math.atan2(num));
+};
+/*\
+ * Snap.angle
+ [ method ]
+ **
+ * Returns an angle between two or three points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ - x3 (number) #optional x coord of third point
+ - y3 (number) #optional y coord of third point
+ = (number) angle in degrees
+\*/
+Snap.angle = angle;
+/*\
+ * Snap.len
+ [ method ]
+ **
+ * Returns distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len = function (x1, y1, x2, y2) {
+    return Math.sqrt(Snap.len2(x1, y1, x2, y2));
+};
+/*\
+ * Snap.len2
+ [ method ]
+ **
+ * Returns squared distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len2 = function (x1, y1, x2, y2) {
+    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
+};
+/*\
+ * Snap.closestPoint
+ [ method ]
+ **
+ * Returns closest point to a given one on a given path.
+ > Parameters
+ - path (Element) path element
+ - x (number) x coord of a point
+ - y (number) y coord of a point
+ = (object) in format
+ {
+    x (number) x coord of the point on the path
+    y (number) y coord of the point on the path
+    length (number) length of the path to the point
+    distance (number) distance from the given point to the path
+ }
+\*/
+// Copied from http://bl.ocks.org/mbostock/8027637
+Snap.closestPoint = function (path, x, y) {
+    function distance2(p) {
+        var dx = p.x - x,
+            dy = p.y - y;
+        return dx * dx + dy * dy;
+    }
+    var pathNode = path.node,
+        pathLength = pathNode.getTotalLength(),
+        precision = pathLength / pathNode.pathSegList.numberOfItems * .125,
+        best,
+        bestLength,
+        bestDistance = Infinity;
+
+    // linear scan for coarse approximation
+    for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
+        if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
+            best = scan, bestLength = scanLength, bestDistance = scanDistance;
+        }
+    }
+
+    // binary search for precise estimate
+    precision *= .5;
+    while (precision > .5) {
+        var before,
+            after,
+            beforeLength,
+            afterLength,
+            beforeDistance,
+            afterDistance;
+        if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
+            best = before, bestLength = beforeLength, bestDistance = beforeDistance;
+        } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
+            best = after, bestLength = afterLength, bestDistance = afterDistance;
+        } else {
+            precision *= .5;
+        }
+    }
+
+    best = {
+        x: best.x,
+        y: best.y,
+        length: bestLength,
+        distance: Math.sqrt(bestDistance)
+    };
+    return best;
+}
+/*\
+ * Snap.is
+ [ method ]
+ **
+ * Handy replacement for the `typeof` operator
+ - o (…) any object or primitive
+ - type (string) name of the type, e.g., `string`, `function`, `number`, etc.
+ = (boolean) `true` if given value is of given type
+\*/
+Snap.is = is;
+/*\
+ * Snap.snapTo
+ [ method ]
+ **
+ * Snaps given value to given grid
+ - values (array|number) given array of values or step of the grid
+ - value (number) value to adjust
+ - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`.
+ = (number) adjusted value
+\*/
+Snap.snapTo = function (values, value, tolerance) {
+    tolerance = is(tolerance, "finite") ? tolerance : 10;
+    if (is(values, "array")) {
+        var i = values.length;
+        while (i--) if (abs(values[i] - value) <= tolerance) {
+            return values[i];
+        }
+    } else {
+        values = +values;
+        var rem = value % values;
+        if (rem < tolerance) {
+            return value - rem;
+        }
+        if (rem > values - tolerance) {
+            return value - rem + values;
+        }
+    }
+    return value;
+};
+// Colour
+/*\
+ * Snap.getRGB
+ [ method ]
+ **
+ * Parses color string as RGB object
+ - color (string) color string in one of the following formats:
+ # <ul>
+ #     <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li>
+ #     <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li>
+ #     <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li>
+ #     <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200,&nbsp;100,&nbsp;0)</code>)</li>
+ #     <li>rgba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li>
+ #     <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>)</li>
+ #     <li>hsba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;0.5)</code>)</li>
+ #     <li>hsla(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li>
+ # </ul>
+ * Note that `%` can be used any time: `rgb(20%, 255, 50%)`.
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) true if string can't be parsed
+ o }
+\*/
+Snap.getRGB = cacher(function (colour) {
+    if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    if (colour == "none") {
+        return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString};
+    }
+    !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+    if (!colour) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    var res,
+        red,
+        green,
+        blue,
+        opacity,
+        t,
+        values,
+        rgb = colour.match(colourRegExp);
+    if (rgb) {
+        if (rgb[2]) {
+            blue = toInt(rgb[2].substring(5), 16);
+            green = toInt(rgb[2].substring(3, 5), 16);
+            red = toInt(rgb[2].substring(1, 3), 16);
+        }
+        if (rgb[3]) {
+            blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+            green = toInt((t = rgb[3].charAt(2)) + t, 16);
+            red = toInt((t = rgb[3].charAt(1)) + t, 16);
+        }
+        if (rgb[4]) {
+            values = rgb[4].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red *= 2.55);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green *= 2.55);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue *= 2.55);
+            rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+        }
+        if (rgb[5]) {
+            values = rgb[5].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsb2rgb(red, green, blue, opacity);
+        }
+        if (rgb[6]) {
+            values = rgb[6].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsl2rgb(red, green, blue, opacity);
+        }
+        red = mmin(math.round(red), 255);
+        green = mmin(math.round(green), 255);
+        blue = mmin(math.round(blue), 255);
+        opacity = mmin(mmax(opacity, 0), 1);
+        rgb = {r: red, g: green, b: blue, toString: rgbtoString};
+        rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+        rgb.opacity = is(opacity, "finite") ? opacity : 1;
+        return rgb;
+    }
+    return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+}, Snap);
+/*\
+ * Snap.hsb
+ [ method ]
+ **
+ * Converts HSB values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - b (number) value or brightness
+ = (string) hex representation of the color
+\*/
+Snap.hsb = cacher(function (h, s, b) {
+    return Snap.hsb2rgb(h, s, b).hex;
+});
+/*\
+ * Snap.hsl
+ [ method ]
+ **
+ * Converts HSL values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (string) hex representation of the color
+\*/
+Snap.hsl = cacher(function (h, s, l) {
+    return Snap.hsl2rgb(h, s, l).hex;
+});
+/*\
+ * Snap.rgb
+ [ method ]
+ **
+ * Converts RGB values to a hex representation of the color
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (string) hex representation of the color
+\*/
+Snap.rgb = cacher(function (r, g, b, o) {
+    if (is(o, "finite")) {
+        var round = math.round;
+        return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")";
+    }
+    return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+});
+var toHex = function (color) {
+    var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0],
+        red = "rgb(255, 0, 0)";
+    toHex = cacher(function (color) {
+        if (color.toLowerCase() == "red") {
+            return red;
+        }
+        i.style.color = red;
+        i.style.color = color;
+        var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+        return out == red ? null : out;
+    });
+    return toHex(color);
+},
+hsbtoString = function () {
+    return "hsb(" + [this.h, this.s, this.b] + ")";
+},
+hsltoString = function () {
+    return "hsl(" + [this.h, this.s, this.l] + ")";
+},
+rgbtoString = function () {
+    return this.opacity == 1 || this.opacity == null ?
+            this.hex :
+            "rgba(" + [this.r, this.g, this.b, this.opacity] + ")";
+},
+prepareRGB = function (r, g, b) {
+    if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) {
+        b = r.b;
+        g = r.g;
+        r = r.r;
+    }
+    if (g == null && is(r, string)) {
+        var clr = Snap.getRGB(r);
+        r = clr.r;
+        g = clr.g;
+        b = clr.b;
+    }
+    if (r > 1 || g > 1 || b > 1) {
+        r /= 255;
+        g /= 255;
+        b /= 255;
+    }
+
+    return [r, g, b];
+},
+packageRGB = function (r, g, b, o) {
+    r = math.round(r * 255);
+    g = math.round(g * 255);
+    b = math.round(b * 255);
+    var rgb = {
+        r: r,
+        g: g,
+        b: b,
+        opacity: is(o, "finite") ? o : 1,
+        hex: Snap.rgb(r, g, b),
+        toString: rgbtoString
+    };
+    is(o, "finite") && (rgb.opacity = o);
+    return rgb;
+};
+/*\
+ * Snap.color
+ [ method ]
+ **
+ * Parses the color string and returns an object featuring the color's component values
+ - clr (string) color string in one of the supported formats (see @Snap.getRGB)
+ = (object) Combined RGB/HSB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) `true` if string can't be parsed,
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     v (number) value (brightness),
+ o     l (number) lightness
+ o }
+\*/
+Snap.color = function (clr) {
+    var rgb;
+    if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+        rgb = Snap.hsb2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+        rgb = Snap.hsl2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else {
+        if (is(clr, "string")) {
+            clr = Snap.getRGB(clr);
+        }
+        if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) {
+            rgb = Snap.rgb2hsl(clr);
+            clr.h = rgb.h;
+            clr.s = rgb.s;
+            clr.l = rgb.l;
+            rgb = Snap.rgb2hsb(clr);
+            clr.v = rgb.b;
+        } else {
+            clr = {hex: "none"};
+            clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+            clr.error = 1;
+        }
+    }
+    clr.toString = rgbtoString;
+    return clr;
+};
+/*\
+ * Snap.hsb2rgb
+ [ method ]
+ **
+ * Converts HSB values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - v (number) value or brightness
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsb2rgb = function (h, s, v, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "b" in h) {
+        v = h.b;
+        s = h.s;
+        o = h.o;
+        h = h.h;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = v * s;
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = v - C;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.hsl2rgb
+ [ method ]
+ **
+ * Converts HSL values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsl2rgb = function (h, s, l, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "l" in h) {
+        l = h.l;
+        s = h.s;
+        h = h.h;
+    }
+    if (h > 1 || s > 1 || l > 1) {
+        h /= 360;
+        s /= 100;
+        l /= 100;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = 2 * s * (l < .5 ? l : 1 - l);
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = l - C / 2;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.rgb2hsb
+ [ method ]
+ **
+ * Converts RGB values to an HSB object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSB object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     b (number) brightness
+ o }
+\*/
+Snap.rgb2hsb = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, V, C;
+    V = mmax(r, g, b);
+    C = V - mmin(r, g, b);
+    H = (C == 0 ? null :
+         V == r ? (g - b) / C :
+         V == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4
+        );
+    H = ((H + 360) % 6) * 60 / 360;
+    S = C == 0 ? 0 : C / V;
+    return {h: H, s: S, b: V, toString: hsbtoString};
+};
+/*\
+ * Snap.rgb2hsl
+ [ method ]
+ **
+ * Converts RGB values to an HSL object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSL object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     l (number) luminosity
+ o }
+\*/
+Snap.rgb2hsl = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, L, M, m, C;
+    M = mmax(r, g, b);
+    m = mmin(r, g, b);
+    C = M - m;
+    H = (C == 0 ? null :
+         M == r ? (g - b) / C :
+         M == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4);
+    H = ((H + 360) % 6) * 60 / 360;
+    L = (M + m) / 2;
+    S = (C == 0 ? 0 :
+         L < .5 ? C / (2 * L) :
+                  C / (2 - 2 * L));
+    return {h: H, s: S, l: L, toString: hsltoString};
+};
+
+// Transformations
+/*\
+ * Snap.parsePathString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given path string into an array of arrays of path segments
+ - pathString (string|array) path string or array of segments (in the last case it is returned straight away)
+ = (array) array of segments
+\*/
+Snap.parsePathString = function (pathString) {
+    if (!pathString) {
+        return null;
+    }
+    var pth = Snap.path(pathString);
+    if (pth.arr) {
+        return Snap.path.clone(pth.arr);
+    }
+
+    var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
+        data = [];
+    if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
+        data = Snap.path.clone(pathString);
+    }
+    if (!data.length) {
+        Str(pathString).replace(pathCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            if (name == "m" && params.length > 2) {
+                data.push([b].concat(params.splice(0, 2)));
+                name = "l";
+                b = b == "m" ? "l" : "L";
+            }
+            if (name == "o" && params.length == 1) {
+                data.push([b, params[0]]);
+            }
+            if (name == "r") {
+                data.push([b].concat(params));
+            } else while (params.length >= paramCounts[name]) {
+                data.push([b].concat(params.splice(0, paramCounts[name])));
+                if (!paramCounts[name]) {
+                    break;
+                }
+            }
+        });
+    }
+    data.toString = Snap.path.toString;
+    pth.arr = Snap.path.clone(data);
+    return data;
+};
+/*\
+ * Snap.parseTransformString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given transform string into an array of transformations
+ - TString (string|array) transform string or array of transformations (in the last case it is returned straight away)
+ = (array) array of transformations
+\*/
+var parseTransformString = Snap.parseTransformString = function (TString) {
+    if (!TString) {
+        return null;
+    }
+    var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+        data = [];
+    if (is(TString, "array") && is(TString[0], "array")) { // rough assumption
+        data = Snap.path.clone(TString);
+    }
+    if (!data.length) {
+        Str(TString).replace(tCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            data.push([b].concat(params));
+        });
+    }
+    data.toString = Snap.path.toString;
+    return data;
+};
+function svgTransform2string(tstr) {
+    var res = [];
+    tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
+        params = params.split(/\s*,\s*|\s+/);
+        if (name == "rotate" && params.length == 1) {
+            params.push(0, 0);
+        }
+        if (name == "scale") {
+            if (params.length > 2) {
+                params = params.slice(0, 2);
+            } else if (params.length == 2) {
+                params.push(0, 0);
+            }
+            if (params.length == 1) {
+                params.push(params[0], 0, 0);
+            }
+        }
+        if (name == "skewX") {
+            res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]);
+        } else if (name == "skewY") {
+            res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]);
+        } else {
+            res.push([name.charAt(0)].concat(params));
+        }
+        return all;
+    });
+    return res;
+}
+Snap._.svgTransform2string = svgTransform2string;
+Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i;
+function transform2matrix(tstr, bbox) {
+    var tdata = parseTransformString(tstr),
+        m = new Snap.Matrix;
+    if (tdata) {
+        for (var i = 0, ii = tdata.length; i < ii; i++) {
+            var t = tdata[i],
+                tlen = t.length,
+                command = Str(t[0]).toLowerCase(),
+                absolute = t[0] != command,
+                inver = absolute ? m.invert() : 0,
+                x1,
+                y1,
+                x2,
+                y2,
+                bb;
+            if (command == "t" && tlen == 2){
+                m.translate(t[1], 0);
+            } else if (command == "t" && tlen == 3) {
+                if (absolute) {
+                    x1 = inver.x(0, 0);
+                    y1 = inver.y(0, 0);
+                    x2 = inver.x(t[1], t[2]);
+                    y2 = inver.y(t[1], t[2]);
+                    m.translate(x2 - x1, y2 - y1);
+                } else {
+                    m.translate(t[1], t[2]);
+                }
+            } else if (command == "r") {
+                if (tlen == 2) {
+                    bb = bb || bbox;
+                    m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.rotate(t[1], x2, y2);
+                    } else {
+                        m.rotate(t[1], t[2], t[3]);
+                    }
+                }
+            } else if (command == "s") {
+                if (tlen == 2 || tlen == 3) {
+                    bb = bb || bbox;
+                    m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.scale(t[1], t[1], x2, y2);
+                    } else {
+                        m.scale(t[1], t[1], t[2], t[3]);
+                    }
+                } else if (tlen == 5) {
+                    if (absolute) {
+                        x2 = inver.x(t[3], t[4]);
+                        y2 = inver.y(t[3], t[4]);
+                        m.scale(t[1], t[2], x2, y2);
+                    } else {
+                        m.scale(t[1], t[2], t[3], t[4]);
+                    }
+                }
+            } else if (command == "m" && tlen == 7) {
+                m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+            }
+        }
+    }
+    return m;
+}
+Snap._.transform2matrix = transform2matrix;
+Snap._unit2px = unit2px;
+var contains = glob.doc.contains || glob.doc.compareDocumentPosition ?
+    function (a, b) {
+        var adown = a.nodeType == 9 ? a.documentElement : a,
+            bup = b && b.parentNode;
+            return a == bup || !!(bup && bup.nodeType == 1 && (
+                adown.contains ?
+                    adown.contains(bup) :
+                    a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
+            ));
+    } :
+    function (a, b) {
+        if (b) {
+            while (b) {
+                b = b.parentNode;
+                if (b == a) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
+function getSomeDefs(el) {
+    var p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) ||
+            (el.node.parentNode && wrap(el.node.parentNode)) ||
+            Snap.select("svg") ||
+            Snap(0, 0),
+        pdefs = p.select("defs"),
+        defs  = pdefs == null ? false : pdefs.node;
+    if (!defs) {
+        defs = make("defs", p.node).node;
+    }
+    return defs;
+}
+function getSomeSVG(el) {
+    return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg");
+}
+Snap._.getSomeDefs = getSomeDefs;
+Snap._.getSomeSVG = getSomeSVG;
+function unit2px(el, name, value) {
+    var svg = getSomeSVG(el).node,
+        out = {},
+        mgr = svg.querySelector(".svg---mgr");
+    if (!mgr) {
+        mgr = $("rect");
+        $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"});
+        svg.appendChild(mgr);
+    }
+    function getW(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {width: val});
+        try {
+            return mgr.getBBox().width;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function getH(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {height: val});
+        try {
+            return mgr.getBBox().height;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function set(nam, f) {
+        if (name == null) {
+            out[nam] = f(el.attr(nam) || 0);
+        } else if (nam == name) {
+            out = f(value == null ? el.attr(nam) || 0 : value);
+        }
+    }
+    switch (el.type) {
+        case "rect":
+            set("rx", getW);
+            set("ry", getH);
+        case "image":
+            set("width", getW);
+            set("height", getH);
+        case "text":
+            set("x", getW);
+            set("y", getH);
+        break;
+        case "circle":
+            set("cx", getW);
+            set("cy", getH);
+            set("r", getW);
+        break;
+        case "ellipse":
+            set("cx", getW);
+            set("cy", getH);
+            set("rx", getW);
+            set("ry", getH);
+        break;
+        case "line":
+            set("x1", getW);
+            set("x2", getW);
+            set("y1", getH);
+            set("y2", getH);
+        break;
+        case "marker":
+            set("refX", getW);
+            set("markerWidth", getW);
+            set("refY", getH);
+            set("markerHeight", getH);
+        break;
+        case "radialGradient":
+            set("fx", getW);
+            set("fy", getH);
+        break;
+        case "tspan":
+            set("dx", getW);
+            set("dy", getH);
+        break;
+        default:
+            set(name, getW);
+    }
+    svg.removeChild(mgr);
+    return out;
+}
+/*\
+ * Snap.select
+ [ method ]
+ **
+ * Wraps a DOM element specified by CSS selector as @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.select = function (query) {
+    query = Str(query).replace(/([^\\]):/g, "$1\\:");
+    return wrap(glob.doc.querySelector(query));
+};
+/*\
+ * Snap.selectAll
+ [ method ]
+ **
+ * Wraps DOM elements specified by CSS selector as set or array of @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.selectAll = function (query) {
+    var nodelist = glob.doc.querySelectorAll(query),
+        set = (Snap.set || Array)();
+    for (var i = 0; i < nodelist.length; i++) {
+        set.push(wrap(nodelist[i]));
+    }
+    return set;
+};
+
+function add2group(list) {
+    if (!is(list, "array")) {
+        list = Array.prototype.slice.call(arguments, 0);
+    }
+    var i = 0,
+        j = 0,
+        node = this.node;
+    while (this[i]) delete this[i++];
+    for (i = 0; i < list.length; i++) {
+        if (list[i].type == "set") {
+            list[i].forEach(function (el) {
+                node.appendChild(el.node);
+            });
+        } else {
+            node.appendChild(list[i].node);
+        }
+    }
+    var children = node.childNodes;
+    for (i = 0; i < children.length; i++) {
+        this[j++] = wrap(children[i]);
+    }
+    return this;
+}
+// Hub garbage collector every 10s
+setInterval(function () {
+    for (var key in hub) if (hub[has](key)) {
+        var el = hub[key],
+            node = el.node;
+        if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
+            delete hub[key];
+        }
+    }
+}, 1e4);
+function Element(el) {
+    if (el.snap in hub) {
+        return hub[el.snap];
+    }
+    var svg;
+    try {
+        svg = el.ownerSVGElement;
+    } catch(e) {}
+    /*\
+     * Element.node
+     [ property (object) ]
+     **
+     * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
+     > Usage
+     | // draw a circle at coordinate 10,10 with radius of 10
+     | var c = paper.circle(10, 10, 10);
+     | c.node.onclick = function () {
+     |     c.attr("fill", "red");
+     | };
+    \*/
+    this.node = el;
+    if (svg) {
+        this.paper = new Paper(svg);
+    }
+    /*\
+     * Element.type
+     [ property (string) ]
+     **
+     * SVG tag name of the given element.
+    \*/
+    this.type = el.tagName || el.nodeName;
+    var id = this.id = ID(this);
+    this.anims = {};
+    this._ = {
+        transform: []
+    };
+    el.snap = id;
+    hub[id] = this;
+    if (this.type == "g") {
+        this.add = add2group;
+    }
+    if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) {
+        for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
+            this[method] = Paper.prototype[method];
+        }
+    }
+}
+   /*\
+     * Element.attr
+     [ method ]
+     **
+     * Gets or sets given attributes of the element.
+     **
+     - params (object) contains key-value pairs of attributes you want to set
+     * or
+     - param (string) name of the attribute
+     = (Element) the current element
+     * or
+     = (string) value of attribute
+     > Usage
+     | el.attr({
+     |     fill: "#fc0",
+     |     stroke: "#000",
+     |     strokeWidth: 2, // CamelCase...
+     |     "fill-opacity": 0.5, // or dash-separated names
+     |     width: "*=2" // prefixed values
+     | });
+     | console.log(el.attr("fill")); // #fc0
+     * Prefixed values in format `"+=10"` supported. All four operations
+     * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+`
+     * and `-`: `"+=2em"`.
+    \*/
+    Element.prototype.attr = function (params, value) {
+        var el = this,
+            node = el.node;
+        if (!params) {
+            if (node.nodeType != 1) {
+                return {
+                    text: node.nodeValue
+                };
+            }
+            var attr = node.attributes,
+                out = {};
+            for (var i = 0, ii = attr.length; i < ii; i++) {
+                out[attr[i].nodeName] = attr[i].nodeValue;
+            }
+            return out;
+        }
+        if (is(params, "string")) {
+            if (arguments.length > 1) {
+                var json = {};
+                json[params] = value;
+                params = json;
+            } else {
+                return eve("snap.util.getattr." + params, el).firstDefined();
+            }
+        }
+        for (var att in params) {
+            if (params[has](att)) {
+                eve("snap.util.attr." + att, el, params[att]);
+            }
+        }
+        return el;
+    };
+/*\
+ * Snap.parse
+ [ method ]
+ **
+ * Parses SVG fragment and converts it into a @Fragment
+ **
+ - svg (string) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.parse = function (svg) {
+    var f = glob.doc.createDocumentFragment(),
+        full = true,
+        div = glob.doc.createElement("div");
+    svg = Str(svg);
+    if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) {
+        svg = "<svg>" + svg + "</svg>";
+        full = false;
+    }
+    div.innerHTML = svg;
+    svg = div.getElementsByTagName("svg")[0];
+    if (svg) {
+        if (full) {
+            f = svg;
+        } else {
+            while (svg.firstChild) {
+                f.appendChild(svg.firstChild);
+            }
+        }
+    }
+    return new Fragment(f);
+};
+function Fragment(frag) {
+    this.node = frag;
+}
+/*\
+ * Snap.fragment
+ [ method ]
+ **
+ * Creates a DOM fragment from a given list of elements or strings
+ **
+ - varargs (…) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.fragment = function () {
+    var args = Array.prototype.slice.call(arguments, 0),
+        f = glob.doc.createDocumentFragment();
+    for (var i = 0, ii = args.length; i < ii; i++) {
+        var item = args[i];
+        if (item.node && item.node.nodeType) {
+            f.appendChild(item.node);
+        }
+        if (item.nodeType) {
+            f.appendChild(item);
+        }
+        if (typeof item == "string") {
+            f.appendChild(Snap.parse(item).node);
+        }
+    }
+    return new Fragment(f);
+};
+
+function make(name, parent) {
+    var res = $(name);
+    parent.appendChild(res);
+    var el = wrap(res);
+    return el;
+}
+function Paper(w, h) {
+    var res,
+        desc,
+        defs,
+        proto = Paper.prototype;
+    if (w && w.tagName == "svg") {
+        if (w.snap in hub) {
+            return hub[w.snap];
+        }
+        var doc = w.ownerDocument;
+        res = new Element(w);
+        desc = w.getElementsByTagName("desc")[0];
+        defs = w.getElementsByTagName("defs")[0];
+        if (!desc) {
+            desc = $("desc");
+            desc.appendChild(doc.createTextNode("Created with Snap"));
+            res.node.appendChild(desc);
+        }
+        if (!defs) {
+            defs = $("defs");
+            res.node.appendChild(defs);
+        }
+        res.defs = defs;
+        for (var key in proto) if (proto[has](key)) {
+            res[key] = proto[key];
+        }
+        res.paper = res.root = res;
+    } else {
+        res = make("svg", glob.doc.body);
+        $(res.node, {
+            height: h,
+            version: 1.1,
+            width: w,
+            xmlns: xmlns
+        });
+    }
+    return res;
+}
+function wrap(dom) {
+    if (!dom) {
+        return dom;
+    }
+    if (dom instanceof Element || dom instanceof Fragment) {
+        return dom;
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "svg") {
+        return new Paper(dom);
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") {
+        return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]);
+    }
+    return new Element(dom);
+}
+
+Snap._.make = make;
+Snap._.wrap = wrap;
+/*\
+ * Paper.el
+ [ method ]
+ **
+ * Creates an element on paper with a given name and no attributes
+ **
+ - name (string) tag name
+ - attr (object) attributes
+ = (Element) the current element
+ > Usage
+ | var c = paper.circle(10, 10, 10); // is the same as...
+ | var c = paper.el("circle").attr({
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+ | // and the same as
+ | var c = paper.el("circle", {
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+\*/
+Paper.prototype.el = function (name, attr) {
+    var el = make(name, this.node);
+    attr && el.attr(attr);
+    return el;
+};
+/*\
+ * Element.children
+ [ method ]
+ **
+ * Returns array of all the children of the element.
+ = (array) array of Elements
+\*/
+Element.prototype.children = function () {
+    var out = [],
+        ch = this.node.childNodes;
+    for (var i = 0, ii = ch.length; i < ii; i++) {
+        out[i] = Snap(ch[i]);
+    }
+    return out;
+};
+function jsonFiller(root, o) {
+    for (var i = 0, ii = root.length; i < ii; i++) {
+        var item = {
+                type: root[i].type,
+                attr: root[i].attr()
+            },
+            children = root[i].children();
+        o.push(item);
+        if (children.length) {
+            jsonFiller(children, item.childNodes = []);
+        }
+    }
+}
+/*\
+ * Element.toJSON
+ [ method ]
+ **
+ * Returns object representation of the given element and all its children.
+ = (object) in format
+ o {
+ o     type (string) this.type,
+ o     attr (object) attributes map,
+ o     childNodes (array) optional array of children in the same format
+ o }
+\*/
+Element.prototype.toJSON = function () {
+    var out = [];
+    jsonFiller([this], out);
+    return out[0];
+};
+// default
+eve.on("snap.util.getattr", function () {
+    var att = eve.nt();
+    att = att.substring(att.lastIndexOf(".") + 1);
+    var css = att.replace(/[A-Z]/g, function (letter) {
+        return "-" + letter.toLowerCase();
+    });
+    if (cssAttr[has](css)) {
+        return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css);
+    } else {
+        return $(this.node, att);
+    }
+});
+var cssAttr = {
+    "alignment-baseline": 0,
+    "baseline-shift": 0,
+    "clip": 0,
+    "clip-path": 0,
+    "clip-rule": 0,
+    "color": 0,
+    "color-interpolation": 0,
+    "color-interpolation-filters": 0,
+    "color-profile": 0,
+    "color-rendering": 0,
+    "cursor": 0,
+    "direction": 0,
+    "display": 0,
+    "dominant-baseline": 0,
+    "enable-background": 0,
+    "fill": 0,
+    "fill-opacity": 0,
+    "fill-rule": 0,
+    "filter": 0,
+    "flood-color": 0,
+    "flood-opacity": 0,
+    "font": 0,
+    "font-family": 0,
+    "font-size": 0,
+    "font-size-adjust": 0,
+    "font-stretch": 0,
+    "font-style": 0,
+    "font-variant": 0,
+    "font-weight": 0,
+    "glyph-orientation-horizontal": 0,
+    "glyph-orientation-vertical": 0,
+    "image-rendering": 0,
+    "kerning": 0,
+    "letter-spacing": 0,
+    "lighting-color": 0,
+    "marker": 0,
+    "marker-end": 0,
+    "marker-mid": 0,
+    "marker-start": 0,
+    "mask": 0,
+    "opacity": 0,
+    "overflow": 0,
+    "pointer-events": 0,
+    "shape-rendering": 0,
+    "stop-color": 0,
+    "stop-opacity": 0,
+    "stroke": 0,
+    "stroke-dasharray": 0,
+    "stroke-dashoffset": 0,
+    "stroke-linecap": 0,
+    "stroke-linejoin": 0,
+    "stroke-miterlimit": 0,
+    "stroke-opacity": 0,
+    "stroke-width": 0,
+    "text-anchor": 0,
+    "text-decoration": 0,
+    "text-rendering": 0,
+    "unicode-bidi": 0,
+    "visibility": 0,
+    "word-spacing": 0,
+    "writing-mode": 0
+};
+
+eve.on("snap.util.attr", function (value) {
+    var att = eve.nt(),
+        attr = {};
+    att = att.substring(att.lastIndexOf(".") + 1);
+    attr[att] = value;
+    var style = att.replace(/-(\w)/gi, function (all, letter) {
+            return letter.toUpperCase();
+        }),
+        css = att.replace(/[A-Z]/g, function (letter) {
+            return "-" + letter.toLowerCase();
+        });
+    if (cssAttr[has](css)) {
+        this.node.style[style] = value == null ? E : value;
+    } else {
+        $(this.node, attr);
+    }
+});
+(function (proto) {}(Paper.prototype));
+
+// simple ajax
+/*\
+ * Snap.ajax
+ [ method ]
+ **
+ * Simple implementation of Ajax
+ **
+ - url (string) URL
+ - postData (object|string) data for post request
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ * or
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ = (XMLHttpRequest) the XMLHttpRequest object, just in case
+\*/
+Snap.ajax = function (url, postData, callback, scope){
+    var req = new XMLHttpRequest,
+        id = ID();
+    if (req) {
+        if (is(postData, "function")) {
+            scope = callback;
+            callback = postData;
+            postData = null;
+        } else if (is(postData, "object")) {
+            var pd = [];
+            for (var key in postData) if (postData.hasOwnProperty(key)) {
+                pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
+            }
+            postData = pd.join("&");
+        }
+        req.open((postData ? "POST" : "GET"), url, true);
+        if (postData) {
+            req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+            req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+        }
+        if (callback) {
+            eve.once("snap.ajax." + id + ".0", callback);
+            eve.once("snap.ajax." + id + ".200", callback);
+            eve.once("snap.ajax." + id + ".304", callback);
+        }
+        req.onreadystatechange = function() {
+            if (req.readyState != 4) return;
+            eve("snap.ajax." + id + "." + req.status, scope, req);
+        };
+        if (req.readyState == 4) {
+            return req;
+        }
+        req.send(postData);
+        return req;
+    }
+};
+/*\
+ * Snap.load
+ [ method ]
+ **
+ * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX)
+ **
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+\*/
+Snap.load = function (url, callback, scope) {
+    Snap.ajax(url, function (req) {
+        var f = Snap.parse(req.responseText);
+        scope ? callback.call(scope, f) : callback(f);
+    });
+};
+var getOffset = function (elem) {
+    var box = elem.getBoundingClientRect(),
+        doc = elem.ownerDocument,
+        body = doc.body,
+        docElem = doc.documentElement,
+        clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+        top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
+        left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+    return {
+        y: top,
+        x: left
+    };
+};
+/*\
+ * Snap.getElementByPoint
+ [ method ]
+ **
+ * Returns you topmost element under given point.
+ **
+ = (object) Snap element object
+ - x (number) x coordinate from the top left corner of the window
+ - y (number) y coordinate from the top left corner of the window
+ > Usage
+ | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
+\*/
+Snap.getElementByPoint = function (x, y) {
+    var paper = this,
+        svg = paper.canvas,
+        target = glob.doc.elementFromPoint(x, y);
+    if (glob.win.opera && target.tagName == "svg") {
+        var so = getOffset(target),
+            sr = target.createSVGRect();
+        sr.x = x - so.x;
+        sr.y = y - so.y;
+        sr.width = sr.height = 1;
+        var hits = target.getIntersectionList(sr, null);
+        if (hits.length) {
+            target = hits[hits.length - 1];
+        }
+    }
+    if (!target) {
+        return null;
+    }
+    return wrap(target);
+};
+/*\
+ * Snap.plugin
+ [ method ]
+ **
+ * Let you write plugins. You pass in a function with five arguments, like this:
+ | Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
+ |     Snap.newmethod = function () {};
+ |     Element.prototype.newmethod = function () {};
+ |     Paper.prototype.newmethod = function () {};
+ | });
+ * Inside the function you have access to all main objects (and their
+ * prototypes). This allow you to extend anything you want.
+ **
+ - f (function) your plugin body
+\*/
+Snap.plugin = function (f) {
+    f(Snap, Element, Paper, glob, Fragment);
+};
+glob.win.Snap = Snap;
+return Snap;
+}(window || this));
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        Str = String,
+        unit2px = Snap._unit2px,
+        $ = Snap._.$,
+        make = Snap._.make,
+        getSomeDefs = Snap._.getSomeDefs,
+        has = "hasOwnProperty",
+        wrap = Snap._.wrap;
+    /*\
+     * Element.getBBox
+     [ method ]
+     **
+     * Returns the bounding box descriptor for the given element
+     **
+     = (object) bounding box descriptor:
+     o {
+     o     cx: (number) x of the center,
+     o     cy: (number) x of the center,
+     o     h: (number) height,
+     o     height: (number) height,
+     o     path: (string) path command for the box,
+     o     r0: (number) radius of a circle that fully encloses the box,
+     o     r1: (number) radius of the smallest circle that can be enclosed,
+     o     r2: (number) radius of the largest circle that can be enclosed,
+     o     vb: (string) box as a viewbox command,
+     o     w: (number) width,
+     o     width: (number) width,
+     o     x2: (number) x of the right side,
+     o     x: (number) x of the left side,
+     o     y2: (number) y of the bottom edge,
+     o     y: (number) y of the top edge
+     o }
+    \*/
+    elproto.getBBox = function (isWithoutTransform) {
+        if (!Snap.Matrix || !Snap.path) {
+            return this.node.getBBox();
+        }
+        var el = this,
+            m = new Snap.Matrix;
+        if (el.removed) {
+            return Snap._.box();
+        }
+        while (el.type == "use") {
+            if (!isWithoutTransform) {
+                m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0));
+            }
+            if (el.original) {
+                el = el.original;
+            } else {
+                var href = el.attr("xlink:href");
+                el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1));
+            }
+        }
+        var _ = el._,
+            pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt;
+        try {
+            if (isWithoutTransform) {
+                _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox());
+                return Snap._.box(_.bboxwt);
+            } else {
+                el.realPath = pathfinder(el);
+                el.matrix = el.transform().localMatrix;
+                _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix)));
+                return Snap._.box(_.bbox);
+            }
+        } catch (e) {
+            // Firefox doesn’t give you bbox of hidden element
+            return Snap._.box();
+        }
+    };
+    var propString = function () {
+        return this.string;
+    };
+    function extractTransform(el, tstr) {
+        if (tstr == null) {
+            var doReturn = true;
+            if (el.type == "linearGradient" || el.type == "radialGradient") {
+                tstr = el.node.getAttribute("gradientTransform");
+            } else if (el.type == "pattern") {
+                tstr = el.node.getAttribute("patternTransform");
+            } else {
+                tstr = el.node.getAttribute("transform");
+            }
+            if (!tstr) {
+                return new Snap.Matrix;
+            }
+            tstr = Snap._.svgTransform2string(tstr);
+        } else {
+            if (!Snap._.rgTransform.test(tstr)) {
+                tstr = Snap._.svgTransform2string(tstr);
+            } else {
+                tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || "");
+            }
+            if (is(tstr, "array")) {
+                tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr);
+            }
+            el._.transform = tstr;
+        }
+        var m = Snap._.transform2matrix(tstr, el.getBBox(1));
+        if (doReturn) {
+            return m;
+        } else {
+            el.matrix = m;
+        }
+    }
+    /*\
+     * Element.transform
+     [ method ]
+     **
+     * Gets or sets transformation of the element
+     **
+     - tstr (string) transform string in Snap or SVG format
+     = (Element) the current element
+     * or
+     = (object) transformation descriptor:
+     o {
+     o     string (string) transform string,
+     o     globalMatrix (Matrix) matrix of all transformations applied to element or its parents,
+     o     localMatrix (Matrix) matrix of transformations applied only to the element,
+     o     diffMatrix (Matrix) matrix of difference between global and local transformations,
+     o     global (string) global transformation as string,
+     o     local (string) local transformation as string,
+     o     toString (function) returns `string` property
+     o }
+    \*/
+    elproto.transform = function (tstr) {
+        var _ = this._;
+        if (tstr == null) {
+            var papa = this,
+                global = new Snap.Matrix(this.node.getCTM()),
+                local = extractTransform(this),
+                ms = [local],
+                m = new Snap.Matrix,
+                i,
+                localString = local.toTransformString(),
+                string = Str(local) == Str(this.matrix) ?
+                            Str(_.transform) : localString;
+            while (papa.type != "svg" && (papa = papa.parent())) {
+                ms.push(extractTransform(papa));
+            }
+            i = ms.length;
+            while (i--) {
+                m.add(ms[i]);
+            }
+            return {
+                string: string,
+                globalMatrix: global,
+                totalMatrix: m,
+                localMatrix: local,
+                diffMatrix: global.clone().add(local.invert()),
+                global: global.toTransformString(),
+                total: m.toTransformString(),
+                local: localString,
+                toString: propString
+            };
+        }
+        if (tstr instanceof Snap.Matrix) {
+            this.matrix = tstr;
+            this._.transform = tstr.toTransformString();
+        } else {
+            extractTransform(this, tstr);
+        }
+
+        if (this.node) {
+            if (this.type == "linearGradient" || this.type == "radialGradient") {
+                $(this.node, {gradientTransform: this.matrix});
+            } else if (this.type == "pattern") {
+                $(this.node, {patternTransform: this.matrix});
+            } else {
+                $(this.node, {transform: this.matrix});
+            }
+        }
+
+        return this;
+    };
+    /*\
+     * Element.parent
+     [ method ]
+     **
+     * Returns the element's parent
+     **
+     = (Element) the parent element
+    \*/
+    elproto.parent = function () {
+        return wrap(this.node.parentNode);
+    };
+    /*\
+     * Element.append
+     [ method ]
+     **
+     * Appends the given element to current one
+     **
+     - el (Element|Set) element to append
+     = (Element) the parent element
+    \*/
+    /*\
+     * Element.add
+     [ method ]
+     **
+     * See @Element.append
+    \*/
+    elproto.append = elproto.add = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this;
+                el.forEach(function (el) {
+                    it.add(el);
+                });
+                return this;
+            }
+            el = wrap(el);
+            this.node.appendChild(el.node);
+            el.paper = this.paper;
+        }
+        return this;
+    };
+    /*\
+     * Element.appendTo
+     [ method ]
+     **
+     * Appends the current element to the given one
+     **
+     - el (Element) parent element to append to
+     = (Element) the child element
+    \*/
+    elproto.appendTo = function (el) {
+        if (el) {
+            el = wrap(el);
+            el.append(this);
+        }
+        return this;
+    };
+    /*\
+     * Element.prepend
+     [ method ]
+     **
+     * Prepends the given element to the current one
+     **
+     - el (Element) element to prepend
+     = (Element) the parent element
+    \*/
+    elproto.prepend = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this,
+                    first;
+                el.forEach(function (el) {
+                    if (first) {
+                        first.after(el);
+                    } else {
+                        it.prepend(el);
+                    }
+                    first = el;
+                });
+                return this;
+            }
+            el = wrap(el);
+            var parent = el.parent();
+            this.node.insertBefore(el.node, this.node.firstChild);
+            this.add && this.add();
+            el.paper = this.paper;
+            this.parent() && this.parent().add();
+            parent && parent.add();
+        }
+        return this;
+    };
+    /*\
+     * Element.prependTo
+     [ method ]
+     **
+     * Prepends the current element to the given one
+     **
+     - el (Element) parent element to prepend to
+     = (Element) the child element
+    \*/
+    elproto.prependTo = function (el) {
+        el = wrap(el);
+        el.prepend(this);
+        return this;
+    };
+    /*\
+     * Element.before
+     [ method ]
+     **
+     * Inserts given element before the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.before = function (el) {
+        if (el.type == "set") {
+            var it = this;
+            el.forEach(function (el) {
+                var parent = el.parent();
+                it.node.parentNode.insertBefore(el.node, it.node);
+                parent && parent.add();
+            });
+            this.parent().add();
+            return this;
+        }
+        el = wrap(el);
+        var parent = el.parent();
+        this.node.parentNode.insertBefore(el.node, this.node);
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.after
+     [ method ]
+     **
+     * Inserts given element after the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.after = function (el) {
+        el = wrap(el);
+        var parent = el.parent();
+        if (this.node.nextSibling) {
+            this.node.parentNode.insertBefore(el.node, this.node.nextSibling);
+        } else {
+            this.node.parentNode.appendChild(el.node);
+        }
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.insertBefore
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertBefore = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.insertAfter
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertAfter = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node.nextSibling);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.remove
+     [ method ]
+     **
+     * Removes element from the DOM
+     = (Element) the detached element
+    \*/
+    elproto.remove = function () {
+        var parent = this.parent();
+        this.node.parentNode && this.node.parentNode.removeChild(this.node);
+        delete this.paper;
+        this.removed = true;
+        parent && parent.add();
+        return this;
+    };
+    /*\
+     * Element.select
+     [ method ]
+     **
+     * Gathers the nested @Element matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Element) result of query selection
+    \*/
+    elproto.select = function (query) {
+        return wrap(this.node.querySelector(query));
+    };
+    /*\
+     * Element.selectAll
+     [ method ]
+     **
+     * Gathers nested @Element objects matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Set|array) result of query selection
+    \*/
+    elproto.selectAll = function (query) {
+        var nodelist = this.node.querySelectorAll(query),
+            set = (Snap.set || Array)();
+        for (var i = 0; i < nodelist.length; i++) {
+            set.push(wrap(nodelist[i]));
+        }
+        return set;
+    };
+    /*\
+     * Element.asPX
+     [ method ]
+     **
+     * Returns given attribute of the element as a `px` value (not %, em, etc.)
+     **
+     - attr (string) attribute name
+     - value (string) #optional attribute value
+     = (Element) result of query selection
+    \*/
+    elproto.asPX = function (attr, value) {
+        if (value == null) {
+            value = this.attr(attr);
+        }
+        return +unit2px(this, attr, value);
+    };
+    // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned <use> instantiates. It's a part of SVG with which ordinary web developers may be least familiar.
+    /*\
+     * Element.use
+     [ method ]
+     **
+     * Creates a `<use>` element linked to the current element
+     **
+     = (Element) the `<use>` element
+    \*/
+    elproto.use = function () {
+        var use,
+            id = this.node.id;
+        if (!id) {
+            id = this.id;
+            $(this.node, {
+                id: id
+            });
+        }
+        if (this.type == "linearGradient" || this.type == "radialGradient" ||
+            this.type == "pattern") {
+            use = make(this.type, this.node.parentNode);
+        } else {
+            use = make("use", this.node.parentNode);
+        }
+        $(use.node, {
+            "xlink:href": "#" + id
+        });
+        use.original = this;
+        return use;
+    };
+    function fixids(el) {
+        var els = el.selectAll("*"),
+            it,
+            url = /^\s*url\(("|'|)(.*)\1\)\s*$/,
+            ids = [],
+            uses = {};
+        function urltest(it, name) {
+            var val = $(it.node, name);
+            val = val && val.match(url);
+            val = val && val[2];
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    var attr = {};
+                    attr[name] = URL(id);
+                    $(it.node, attr);
+                });
+            }
+        }
+        function linktest(it) {
+            var val = $(it.node, "xlink:href");
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    it.attr("xlink:href", "#" + id);
+                });
+            }
+        }
+        for (var i = 0, ii = els.length; i < ii; i++) {
+            it = els[i];
+            urltest(it, "fill");
+            urltest(it, "stroke");
+            urltest(it, "filter");
+            urltest(it, "mask");
+            urltest(it, "clip-path");
+            linktest(it);
+            var oldid = $(it.node, "id");
+            if (oldid) {
+                $(it.node, {id: it.id});
+                ids.push({
+                    old: oldid,
+                    id: it.id
+                });
+            }
+        }
+        for (i = 0, ii = ids.length; i < ii; i++) {
+            var fs = uses[ids[i].old];
+            if (fs) {
+                for (var j = 0, jj = fs.length; j < jj; j++) {
+                    fs[j](ids[i].id);
+                }
+            }
+        }
+    }
+    /*\
+     * Element.clone
+     [ method ]
+     **
+     * Creates a clone of the element and inserts it after the element
+     **
+     = (Element) the clone
+    \*/
+    elproto.clone = function () {
+        var clone = wrap(this.node.cloneNode(true));
+        if ($(clone.node, "id")) {
+            $(clone.node, {id: clone.id});
+        }
+        fixids(clone);
+        clone.insertAfter(this);
+        return clone;
+    };
+    /*\
+     * Element.toDefs
+     [ method ]
+     **
+     * Moves element to the shared `<defs>` area
+     **
+     = (Element) the element
+    \*/
+    elproto.toDefs = function () {
+        var defs = getSomeDefs(this);
+        defs.appendChild(this.node);
+        return this;
+    };
+    /*\
+     * Element.toPattern
+     [ method ]
+     **
+     * Creates a `<pattern>` element from the current element
+     **
+     * To create a pattern you have to specify the pattern rect:
+     - x (string|number)
+     - y (string|number)
+     - width (string|number)
+     - height (string|number)
+     = (Element) the `<pattern>` element
+     * You can use pattern later on as an argument for `fill` attribute:
+     | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
+     |         fill: "none",
+     |         stroke: "#bada55",
+     |         strokeWidth: 5
+     |     }).pattern(0, 0, 10, 10),
+     |     c = paper.circle(200, 200, 100);
+     | c.attr({
+     |     fill: p
+     | });
+    \*/
+    elproto.pattern = elproto.toPattern = function (x, y, width, height) {
+        var p = make("pattern", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        $(p.node, {
+            x: x,
+            y: y,
+            width: width,
+            height: height,
+            patternUnits: "userSpaceOnUse",
+            id: p.id,
+            viewBox: [x, y, width, height].join(" ")
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+// SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path.
+// SIERRA Element.marker(): I suggest the method should accept default reference point values.  Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where?  Couldn't they also be assigned default values?
+    /*\
+     * Element.marker
+     [ method ]
+     **
+     * Creates a `<marker>` element from the current element
+     **
+     * To create a marker you have to specify the bounding rect and reference point:
+     - x (number)
+     - y (number)
+     - width (number)
+     - height (number)
+     - refX (number)
+     - refY (number)
+     = (Element) the `<marker>` element
+     * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end.
+    \*/
+    // TODO add usage for markers
+    elproto.marker = function (x, y, width, height, refX, refY) {
+        var p = make("marker", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            refX = x.refX || x.cx;
+            refY = x.refY || x.cy;
+            x = x.x;
+        }
+        $(p.node, {
+            viewBox: [x, y, width, height].join(" "),
+            markerWidth: width,
+            markerHeight: height,
+            orient: "auto",
+            refX: refX || 0,
+            refY: refY || 0,
+            id: p.id
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+    // animation
+    function slice(from, to, f) {
+        return function (arr) {
+            var res = arr.slice(from, to);
+            if (res.length == 1) {
+                res = res[0];
+            }
+            return f ? f(res) : res;
+        };
+    }
+    var Animation = function (attr, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        this.attr = attr;
+        this.dur = ms;
+        easing && (this.easing = easing);
+        callback && (this.callback = callback);
+    };
+    Snap._.Animation = Animation;
+    /*\
+     * Snap.animation
+     [ method ]
+     **
+     * Creates an animation object
+     **
+     - attr (object) attributes of final destination
+     - duration (number) duration of the animation, in milliseconds
+     - easing (function) #optional one of easing functions of @mina or custom one
+     - callback (function) #optional callback function that fires when animation ends
+     = (object) animation object
+    \*/
+    Snap.animation = function (attr, ms, easing, callback) {
+        return new Animation(attr, ms, easing, callback);
+    };
+    /*\
+     * Element.inAnim
+     [ method ]
+     **
+     * Returns a set of animations that may be able to manipulate the current element
+     **
+     = (object) in format:
+     o {
+     o     anim (object) animation object,
+     o     mina (object) @mina object,
+     o     curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+    \*/
+    elproto.inAnim = function () {
+        var el = this,
+            res = [];
+        for (var id in el.anims) if (el.anims[has](id)) {
+            (function (a) {
+                res.push({
+                    anim: new Animation(a._attrs, a.dur, a.easing, a._callback),
+                    mina: a,
+                    curStatus: a.status(),
+                    status: function (val) {
+                        return a.status(val);
+                    },
+                    stop: function () {
+                        a.stop();
+                    }
+                });
+            }(el.anims[id]));
+        }
+        return res;
+    };
+    /*\
+     * Snap.animate
+     [ method ]
+     **
+     * Runs generic animation of one number into another with a caring function
+     **
+     - from (number|array) number or array of numbers
+     - to (number|array) number or array of numbers
+     - setter (function) caring function that accepts one number argument
+     - duration (number) duration, in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function to execute when animation ends
+     = (object) animation object in @mina format
+     o {
+     o     id (string) animation id, consider it read-only,
+     o     duration (function) gets or sets the duration of the animation,
+     o     easing (function) easing,
+     o     speed (function) gets or sets the speed of the animation,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+     | var rect = Snap().rect(0, 0, 10, 10);
+     | Snap.animate(0, 10, function (val) {
+     |     rect.attr({
+     |         x: val
+     |     });
+     | }, 1000);
+     | // in given context is equivalent to
+     | rect.animate({x: 10}, 1000);
+    \*/
+    Snap.animate = function (from, to, setter, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        var now = mina.time(),
+            anim = mina(from, to, now, now + ms, mina.time, setter, easing);
+        callback && eve.once("mina.finish." + anim.id, callback);
+        return anim;
+    };
+    /*\
+     * Element.stop
+     [ method ]
+     **
+     * Stops all the animations for the current element
+     **
+     = (Element) the current element
+    \*/
+    elproto.stop = function () {
+        var anims = this.inAnim();
+        for (var i = 0, ii = anims.length; i < ii; i++) {
+            anims[i].stop();
+        }
+        return this;
+    };
+    /*\
+     * Element.animate
+     [ method ]
+     **
+     * Animates the given attributes of the element
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     = (Element) the current element
+    \*/
+    elproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = attrs.dur;
+            attrs = attrs.attr;
+        }
+        var fkeys = [], tkeys = [], keys = {}, from, to, f, eq,
+            el = this;
+        for (var key in attrs) if (attrs[has](key)) {
+            if (el.equal) {
+                eq = el.equal(key, Str(attrs[key]));
+                from = eq.from;
+                to = eq.to;
+                f = eq.f;
+            } else {
+                from = +el.attr(key);
+                to = +attrs[key];
+            }
+            var len = is(from, "array") ? from.length : 1;
+            keys[key] = slice(fkeys.length, fkeys.length + len, f);
+            fkeys = fkeys.concat(from);
+            tkeys = tkeys.concat(to);
+        }
+        var now = mina.time(),
+            anim = mina(fkeys, tkeys, now, now + ms, mina.time, function (val) {
+                var attr = {};
+                for (var key in keys) if (keys[has](key)) {
+                    attr[key] = keys[key](val);
+                }
+                el.attr(attr);
+            }, easing);
+        el.anims[anim.id] = anim;
+        anim._attrs = attrs;
+        anim._callback = callback;
+        eve("snap.animcreated." + el.id, anim);
+        eve.once("mina.finish." + anim.id, function () {
+            delete el.anims[anim.id];
+            callback && callback.call(el);
+        });
+        eve.once("mina.stop." + anim.id, function () {
+            delete el.anims[anim.id];
+        });
+        return el;
+    };
+    var eldata = {};
+    /*\
+     * Element.data
+     [ method ]
+     **
+     * Adds or retrieves given value associated with given key. (Don’t confuse
+     * with `data-` attributes)
+     *
+     * See also @Element.removeData
+     - key (string) key to store data
+     - value (any) #optional value to store
+     = (object) @Element
+     * or, if value is not specified:
+     = (any) value
+     > Usage
+     | for (var i = 0, i < 5, i++) {
+     |     paper.circle(10 + 15 * i, 10, 10)
+     |          .attr({fill: "#000"})
+     |          .data("i", i)
+     |          .click(function () {
+     |             alert(this.data("i"));
+     |          });
+     | }
+    \*/
+    elproto.data = function (key, value) {
+        var data = eldata[this.id] = eldata[this.id] || {};
+        if (arguments.length == 0){
+            eve("snap.data.get." + this.id, this, data, null);
+            return data;
+        }
+        if (arguments.length == 1) {
+            if (Snap.is(key, "object")) {
+                for (var i in key) if (key[has](i)) {
+                    this.data(i, key[i]);
+                }
+                return this;
+            }
+            eve("snap.data.get." + this.id, this, data[key], key);
+            return data[key];
+        }
+        data[key] = value;
+        eve("snap.data.set." + this.id, this, value, key);
+        return this;
+    };
+    /*\
+     * Element.removeData
+     [ method ]
+     **
+     * Removes value associated with an element by given key.
+     * If key is not provided, removes all the data of the element.
+     - key (string) #optional key
+     = (object) @Element
+    \*/
+    elproto.removeData = function (key) {
+        if (key == null) {
+            eldata[this.id] = {};
+        } else {
+            eldata[this.id] && delete eldata[this.id][key];
+        }
+        return this;
+    };
+    /*\
+     * Element.outerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element, equivalent to HTML's `outerHTML`.
+     *
+     * See also @Element.innerSVG
+     = (string) SVG code for the element
+    \*/
+    /*\
+     * Element.toString
+     [ method ]
+     **
+     * See @Element.outerSVG
+    \*/
+    elproto.outerSVG = elproto.toString = toString(1);
+    /*\
+     * Element.innerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML`
+     = (string) SVG code for the element
+    \*/
+    elproto.innerSVG = toString();
+    function toString(type) {
+        return function () {
+            var res = type ? "<" + this.type : "",
+                attr = this.node.attributes,
+                chld = this.node.childNodes;
+            if (type) {
+                for (var i = 0, ii = attr.length; i < ii; i++) {
+                    res += " " + attr[i].name + '="' +
+                            attr[i].value.replace(/"/g, '\\"') + '"';
+                }
+            }
+            if (chld.length) {
+                type && (res += ">");
+                for (i = 0, ii = chld.length; i < ii; i++) {
+                    if (chld[i].nodeType == 3) {
+                        res += chld[i].nodeValue;
+                    } else if (chld[i].nodeType == 1) {
+                        res += wrap(chld[i]).toString();
+                    }
+                }
+                type && (res += "</" + this.type + ">");
+            } else {
+                type && (res += "/>");
+            }
+            return res;
+        };
+    }
+    elproto.toDataURL = function () {
+        if (window && window.btoa) {
+            var bb = this.getBBox(),
+                svg = Snap.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>', {
+                x: +bb.x.toFixed(3),
+                y: +bb.y.toFixed(3),
+                width: +bb.width.toFixed(3),
+                height: +bb.height.toFixed(3),
+                contents: this.outerSVG()
+            });
+            return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
+        }
+    };
+    /*\
+     * Fragment.select
+     [ method ]
+     **
+     * See @Element.select
+    \*/
+    Fragment.prototype.select = elproto.select;
+    /*\
+     * Fragment.selectAll
+     [ method ]
+     **
+     * See @Element.selectAll
+    \*/
+    Fragment.prototype.selectAll = elproto.selectAll;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var objectToString = Object.prototype.toString,
+        Str = String,
+        math = Math,
+        E = "";
+    function Matrix(a, b, c, d, e, f) {
+        if (b == null && objectToString.call(a) == "[object SVGMatrix]") {
+            this.a = a.a;
+            this.b = a.b;
+            this.c = a.c;
+            this.d = a.d;
+            this.e = a.e;
+            this.f = a.f;
+            return;
+        }
+        if (a != null) {
+            this.a = +a;
+            this.b = +b;
+            this.c = +c;
+            this.d = +d;
+            this.e = +e;
+            this.f = +f;
+        } else {
+            this.a = 1;
+            this.b = 0;
+            this.c = 0;
+            this.d = 1;
+            this.e = 0;
+            this.f = 0;
+        }
+    }
+    (function (matrixproto) {
+        /*\
+         * Matrix.add
+         [ method ]
+         **
+         * Adds the given matrix to existing one
+         - a (number)
+         - b (number)
+         - c (number)
+         - d (number)
+         - e (number)
+         - f (number)
+         * or
+         - matrix (object) @Matrix
+        \*/
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
+
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
+
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
+                }
+            }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+            return this;
+        };
+        /*\
+         * Matrix.invert
+         [ method ]
+         **
+         * Returns an inverted version of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        /*\
+         * Matrix.clone
+         [ method ]
+         **
+         * Returns a copy of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        /*\
+         * Matrix.translate
+         [ method ]
+         **
+         * Translate the matrix
+         - x (number) horizontal offset distance
+         - y (number) vertical offset distance
+        \*/
+        matrixproto.translate = function (x, y) {
+            return this.add(1, 0, 0, 1, x, y);
+        };
+        /*\
+         * Matrix.scale
+         [ method ]
+         **
+         * Scales the matrix
+         - x (number) amount to be scaled, with `1` resulting in no change
+         - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.)
+         - cx (number) #optional horizontal origin point from which to scale
+         - cy (number) #optional vertical origin point from which to scale
+         * Default cx, cy is the middle point of the element.
+        \*/
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+            return this;
+        };
+        /*\
+         * Matrix.rotate
+         [ method ]
+         **
+         * Rotates the matrix
+         - a (number) angle of rotation, in degrees
+         - x (number) horizontal origin point from which to rotate
+         - y (number) vertical origin point from which to rotate
+        \*/
+        matrixproto.rotate = function (a, x, y) {
+            a = Snap.rad(a);
+            x = x || 0;
+            y = y || 0;
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            return this.add(1, 0, 0, 1, -x, -y);
+        };
+        /*\
+         * Matrix.x
+         [ method ]
+         **
+         * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y
+         - x (number)
+         - y (number)
+         = (number) x
+        \*/
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        /*\
+         * Matrix.y
+         [ method ]
+         **
+         * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x
+         - x (number)
+         - y (number)
+         = (number) y
+        \*/
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
+        }
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] && (a[0] /= mag);
+            a[1] && (a[1] /= mag);
+        }
+        /*\
+         * Matrix.determinant
+         [ method ]
+         **
+         * Finds determinant of the given matrix.
+         = (number) determinant
+        \*/
+        matrixproto.determinant = function () {
+            return this.a * this.d - this.b * this.c;
+        };
+        /*\
+         * Matrix.split
+         [ method ]
+         **
+         * Splits matrix into primitive transformations
+         = (object) in format:
+         o dx (number) translation by x
+         o dy (number) translation by y
+         o scalex (number) scale by x
+         o scaley (number) scale by y
+         o shear (number) shear
+         o rotate (number) rotation in deg
+         o isSimple (boolean) could it be represented via simple transformations
+        \*/
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
+
+            if (this.determinant() < 0) {
+                out.scalex = -out.scalex;
+            }
+
+            // rotation
+            var sin = -row[0][1],
+                cos = row[1][1];
+            if (cos < 0) {
+                out.rotate = Snap.deg(math.acos(cos));
+                if (sin < 0) {
+                    out.rotate = 360 - out.rotate;
+                }
+            } else {
+                out.rotate = Snap.deg(math.asin(sin));
+            }
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+            return out;
+        };
+        /*\
+         * Matrix.toTransformString
+         [ method ]
+         **
+         * Returns transform string that represents given matrix
+         = (string) transform string
+        \*/
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this.split();
+            if (!+s.shear.toFixed(9)) {
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) +
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E);
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+    /*\
+     * Snap.Matrix
+     [ method ]
+     **
+     * Matrix constructor, extend on your own risk.
+     * To create matrices use @Snap.matrix.
+    \*/
+    Snap.Matrix = Matrix;
+    /*\
+     * Snap.matrix
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns a matrix based on the given parameters
+     - a (number)
+     - b (number)
+     - c (number)
+     - d (number)
+     - e (number)
+     - f (number)
+     * or
+     - svgMatrix (SVGMatrix)
+     = (object) @Matrix
+    \*/
+    Snap.matrix = function (a, b, c, d, e, f) {
+        return new Matrix(a, b, c, d, e, f);
+    };
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var has = "hasOwnProperty",
+        make = Snap._.make,
+        wrap = Snap._.wrap,
+        is = Snap.is,
+        getSomeDefs = Snap._.getSomeDefs,
+        reURLValue = /^url\(#?([^)]+)\)$/,
+        $ = Snap._.$,
+        URL = Snap.url,
+        Str = String,
+        separator = Snap._.separator,
+        E = "";
+    // Attributes event handlers
+    eve.on("snap.util.attr.mask", function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value.type == "mask") {
+                var mask = value;
+            } else {
+                mask = make("mask", getSomeDefs(this));
+                mask.node.appendChild(value.node);
+            }
+            !mask.node.id && $(mask.node, {
+                id: mask.id
+            });
+            $(this.node, {
+                mask: URL(mask.id)
+            });
+        }
+    });
+    (function (clipIt) {
+        eve.on("snap.util.attr.clip", clipIt);
+        eve.on("snap.util.attr.clip-path", clipIt);
+        eve.on("snap.util.attr.clipPath", clipIt);
+    }(function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value.type == "clipPath") {
+                var clip = value;
+            } else {
+                clip = make("clipPath", getSomeDefs(this));
+                clip.node.appendChild(value.node);
+                !clip.node.id && $(clip.node, {
+                    id: clip.id
+                });
+            }
+            $(this.node, {
+                "clip-path": URL(clip.node.id || clip.id)
+            });
+        }
+    }));
+    function fillStroke(name) {
+        return function (value) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1 &&
+                (value.node.firstChild.tagName == "radialGradient" ||
+                value.node.firstChild.tagName == "linearGradient" ||
+                value.node.firstChild.tagName == "pattern")) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value instanceof Element) {
+                if (value.type == "radialGradient" || value.type == "linearGradient"
+                   || value.type == "pattern") {
+                    if (!value.node.id) {
+                        $(value.node, {
+                            id: value.id
+                        });
+                    }
+                    var fill = URL(value.node.id);
+                } else {
+                    fill = value.attr(name);
+                }
+            } else {
+                fill = Snap.color(value);
+                if (fill.error) {
+                    var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value);
+                    if (grad) {
+                        if (!grad.node.id) {
+                            $(grad.node, {
+                                id: grad.id
+                            });
+                        }
+                        fill = URL(grad.node.id);
+                    } else {
+                        fill = value;
+                    }
+                } else {
+                    fill = Str(fill);
+                }
+            }
+            var attrs = {};
+            attrs[name] = fill;
+            $(this.node, attrs);
+            this.node.style[name] = E;
+        };
+    }
+    eve.on("snap.util.attr.fill", fillStroke("fill"));
+    eve.on("snap.util.attr.stroke", fillStroke("stroke"));
+    var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i;
+    eve.on("snap.util.grad.parse", function parseGrad(string) {
+        string = Str(string);
+        var tokens = string.match(gradrg);
+        if (!tokens) {
+            return null;
+        }
+        var type = tokens[1],
+            params = tokens[2],
+            stops = tokens[3];
+        params = params.split(/\s*,\s*/).map(function (el) {
+            return +el == el ? +el : el;
+        });
+        if (params.length == 1 && params[0] == 0) {
+            params = [];
+        }
+        stops = stops.split("-");
+        stops = stops.map(function (el) {
+            el = el.split(":");
+            var out = {
+                color: el[0]
+            };
+            if (el[1]) {
+                out.offset = parseFloat(el[1]);
+            }
+            return out;
+        });
+        return {
+            type: type,
+            params: params,
+            stops: stops
+        };
+    });
+
+    eve.on("snap.util.attr.d", function (value) {
+        eve.stop();
+        if (is(value, "array") && is(value[0], "array")) {
+            value = Snap.path.toString.call(value);
+        }
+        value = Str(value);
+        if (value.match(/[ruo]/i)) {
+            value = Snap.path.toAbsolute(value);
+        }
+        $(this.node, {d: value});
+    })(-1);
+    eve.on("snap.util.attr.#text", function (value) {
+        eve.stop();
+        value = Str(value);
+        var txt = glob.doc.createTextNode(value);
+        while (this.node.firstChild) {
+            this.node.removeChild(this.node.firstChild);
+        }
+        this.node.appendChild(txt);
+    })(-1);
+    eve.on("snap.util.attr.path", function (value) {
+        eve.stop();
+        this.attr({d: value});
+    })(-1);
+    eve.on("snap.util.attr.class", function (value) {
+        eve.stop();
+        this.node.className.baseVal = value;
+    })(-1);
+    eve.on("snap.util.attr.viewBox", function (value) {
+        var vb;
+        if (is(value, "object") && "x" in value) {
+            vb = [value.x, value.y, value.width, value.height].join(" ");
+        } else if (is(value, "array")) {
+            vb = value.join(" ");
+        } else {
+            vb = value;
+        }
+        $(this.node, {
+            viewBox: vb
+        });
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.transform", function (value) {
+        this.transform(value);
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.r", function (value) {
+        if (this.type == "rect") {
+            eve.stop();
+            $(this.node, {
+                rx: value,
+                ry: value
+            });
+        }
+    })(-1);
+    eve.on("snap.util.attr.textpath", function (value) {
+        eve.stop();
+        if (this.type == "text") {
+            var id, tp, node;
+            if (!value && this.textPath) {
+                tp = this.textPath;
+                while (tp.node.firstChild) {
+                    this.node.appendChild(tp.node.firstChild);
+                }
+                tp.remove();
+                delete this.textPath;
+                return;
+            }
+            if (is(value, "string")) {
+                var defs = getSomeDefs(this),
+                    path = wrap(defs.parentNode).path(value);
+                defs.appendChild(path.node);
+                id = path.id;
+                path.attr({id: id});
+            } else {
+                value = wrap(value);
+                if (value instanceof Element) {
+                    id = value.attr("id");
+                    if (!id) {
+                        id = value.id;
+                        value.attr({id: id});
+                    }
+                }
+            }
+            if (id) {
+                tp = this.textPath;
+                node = this.node;
+                if (tp) {
+                    tp.attr({"xlink:href": "#" + id});
+                } else {
+                    tp = $("textPath", {
+                        "xlink:href": "#" + id
+                    });
+                    while (node.firstChild) {
+                        tp.appendChild(node.firstChild);
+                    }
+                    node.appendChild(tp);
+                    this.textPath = wrap(tp);
+                }
+            }
+        }
+    })(-1);
+    eve.on("snap.util.attr.text", function (value) {
+        if (this.type == "text") {
+            var i = 0,
+                node = this.node,
+                tuner = function (chunk) {
+                    var out = $("tspan");
+                    if (is(chunk, "array")) {
+                        for (var i = 0; i < chunk.length; i++) {
+                            out.appendChild(tuner(chunk[i]));
+                        }
+                    } else {
+                        out.appendChild(glob.doc.createTextNode(chunk));
+                    }
+                    out.normalize && out.normalize();
+                    return out;
+                };
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            var tuned = tuner(value);
+            while (tuned.firstChild) {
+                node.appendChild(tuned.firstChild);
+            }
+        }
+        eve.stop();
+    })(-1);
+    function setFontSize(value) {
+        eve.stop();
+        if (value == +value) {
+            value += "px";
+        }
+        this.node.style.fontSize = value;
+    }
+    eve.on("snap.util.attr.fontSize", setFontSize)(-1);
+    eve.on("snap.util.attr.font-size", setFontSize)(-1);
+
+
+    eve.on("snap.util.getattr.transform", function () {
+        eve.stop();
+        return this.transform();
+    })(-1);
+    eve.on("snap.util.getattr.textpath", function () {
+        eve.stop();
+        return this.textPath;
+    })(-1);
+    // Markers
+    (function () {
+        function getter(end) {
+            return function () {
+                eve.stop();
+                var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end);
+                if (style == "none") {
+                    return style;
+                } else {
+                    return Snap(glob.doc.getElementById(style.match(reURLValue)[1]));
+                }
+            };
+        }
+        function setter(end) {
+            return function (value) {
+                eve.stop();
+                var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1);
+                if (value == "" || !value) {
+                    this.node.style[name] = "none";
+                    return;
+                }
+                if (value.type == "marker") {
+                    var id = value.node.id;
+                    if (!id) {
+                        $(value.node, {id: value.id});
+                    }
+                    this.node.style[name] = URL(id);
+                    return;
+                }
+            };
+        }
+        eve.on("snap.util.getattr.marker-end", getter("end"))(-1);
+        eve.on("snap.util.getattr.markerEnd", getter("end"))(-1);
+        eve.on("snap.util.getattr.marker-start", getter("start"))(-1);
+        eve.on("snap.util.getattr.markerStart", getter("start"))(-1);
+        eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1);
+        eve.on("snap.util.getattr.markerMid", getter("mid"))(-1);
+        eve.on("snap.util.attr.marker-end", setter("end"))(-1);
+        eve.on("snap.util.attr.markerEnd", setter("end"))(-1);
+        eve.on("snap.util.attr.marker-start", setter("start"))(-1);
+        eve.on("snap.util.attr.markerStart", setter("start"))(-1);
+        eve.on("snap.util.attr.marker-mid", setter("mid"))(-1);
+        eve.on("snap.util.attr.markerMid", setter("mid"))(-1);
+    }());
+    eve.on("snap.util.getattr.r", function () {
+        if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
+            eve.stop();
+            return $(this.node, "rx");
+        }
+    })(-1);
+    function textExtract(node) {
+        var out = [];
+        var children = node.childNodes;
+        for (var i = 0, ii = children.length; i < ii; i++) {
+            var chi = children[i];
+            if (chi.nodeType == 3) {
+                out.push(chi.nodeValue);
+            }
+            if (chi.tagName == "tspan") {
+                if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) {
+                    out.push(chi.firstChild.nodeValue);
+                } else {
+                    out.push(textExtract(chi));
+                }
+            }
+        }
+        return out;
+    }
+    eve.on("snap.util.getattr.text", function () {
+        if (this.type == "text" || this.type == "tspan") {
+            eve.stop();
+            var out = textExtract(this.node);
+            return out.length == 1 ? out[0] : out;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.#text", function () {
+        return this.node.textContent;
+    })(-1);
+    eve.on("snap.util.getattr.viewBox", function () {
+        eve.stop();
+        var vb = $(this.node, "viewBox");
+        if (vb) {
+            vb = vb.split(separator);
+            return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.points", function () {
+        var p = $(this.node, "points");
+        eve.stop();
+        if (p) {
+            return p.split(separator);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.path", function () {
+        var p = $(this.node, "d");
+        eve.stop();
+        return p;
+    })(-1);
+    eve.on("snap.util.getattr.class", function () {
+        return this.node.className.baseVal;
+    })(-1);
+    function getFontSize() {
+        eve.stop();
+        return this.node.style.fontSize;
+    }
+    eve.on("snap.util.getattr.fontSize", getFontSize)(-1);
+    eve.on("snap.util.getattr.font-size", getFontSize)(-1);
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var rgNotSpace = /\S+/g,
+        rgBadSpace = /[\t\r\n\f]/g,
+        rgTrim = /(^\s+|\s+$)/g,
+        Str = String,
+        elproto = Element.prototype;
+    /*\
+     * Element.addClass
+     [ method ]
+     **
+     * Adds given class name or list of class names to the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.addClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+
+        if (classes.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (!~pos) {
+                    curClasses.push(clazz);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.removeClass
+     [ method ]
+     **
+     * Removes given class name or list of class names from the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.removeClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        if (curClasses.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (~pos) {
+                    curClasses.splice(pos, 1);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.hasClass
+     [ method ]
+     **
+     * Checks if the element has a given class name in the list of class names applied to it.
+     - value (string) class name
+     **
+     = (boolean) `true` if the element has given class
+    \*/
+    elproto.hasClass = function (value) {
+        var elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [];
+        return !!~curClasses.indexOf(value);
+    };
+    /*\
+     * Element.toggleClass
+     [ method ]
+     **
+     * Add or remove one or more classes from the element, depending on either
+     * the class’s presence or the value of the `flag` argument.
+     - value (string) class name or space separated list of class names
+     - flag (boolean) value to determine whether the class should be added or removed
+     **
+     = (Element) original element.
+    \*/
+    elproto.toggleClass = function (value, flag) {
+        if (flag != null) {
+            if (flag) {
+                return this.addClass(value);
+            } else {
+                return this.removeClass(value);
+            }
+        }
+        var classes = (value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        j = 0;
+        while ((clazz = classes[j++])) {
+            pos = curClasses.indexOf(clazz);
+            if (~pos) {
+                curClasses.splice(pos, 1);
+            } else {
+                curClasses.push(clazz);
+            }
+        }
+
+        finalValue = curClasses.join(" ");
+        if (className != finalValue) {
+            elem.className.baseVal = finalValue;
+        }
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var operators = {
+            "+": function (x, y) {
+                    return x + y;
+                },
+            "-": function (x, y) {
+                    return x - y;
+                },
+            "/": function (x, y) {
+                    return x / y;
+                },
+            "*": function (x, y) {
+                    return x * y;
+                }
+        },
+        Str = String,
+        reUnit = /[a-z]+$/i,
+        reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    eve.on("snap.util.attr", function (val) {
+        var plus = Str(val).match(reAddon);
+        if (plus) {
+            var evnt = eve.nt(),
+                name = evnt.substring(evnt.lastIndexOf(".") + 1),
+                a = this.attr(name),
+                atr = {};
+            eve.stop();
+            var unit = plus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[plus[1]];
+            if (aUnit && aUnit == unit) {
+                val = op(parseFloat(a), +plus[2]);
+            } else {
+                a = this.asPX(name);
+                val = op(this.asPX(name), this.asPX(name, plus[2] + unit));
+            }
+            if (isNaN(a) || isNaN(val)) {
+                return;
+            }
+            atr[name] = val;
+            this.attr(atr);
+        }
+    })(-10);
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this,
+            bplus = Str(b).match(reAddon);
+        if (bplus) {
+            eve.stop();
+            var unit = bplus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[bplus[1]];
+            if (aUnit && aUnit == unit) {
+                return {
+                    from: parseFloat(a),
+                    to: op(parseFloat(a), +bplus[2]),
+                    f: getUnit(aUnit)
+                };
+            } else {
+                a = this.asPX(name);
+                return {
+                    from: a,
+                    to: op(a, this.asPX(name, bplus[2] + unit)),
+                    f: getNumber
+                };
+            }
+        }
+    })(-10);
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var proto = Paper.prototype,
+        is = Snap.is;
+    /*\
+     * Paper.rect
+     [ method ]
+     *
+     * Draws a rectangle
+     **
+     - x (number) x coordinate of the top left corner
+     - y (number) y coordinate of the top left corner
+     - width (number) width
+     - height (number) height
+     - rx (number) #optional horizontal radius for rounded corners, default is 0
+     - ry (number) #optional vertical radius for rounded corners, default is rx or 0
+     = (object) the `rect` element
+     **
+     > Usage
+     | // regular rectangle
+     | var c = paper.rect(10, 10, 50, 50);
+     | // rectangle with rounded corners
+     | var c = paper.rect(40, 40, 50, 50, 10);
+    \*/
+    proto.rect = function (x, y, w, h, rx, ry) {
+        var attr;
+        if (ry == null) {
+            ry = rx;
+        }
+        if (is(x, "object") && x == "[object Object]") {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                width: w,
+                height: h
+            };
+            if (rx != null) {
+                attr.rx = rx;
+                attr.ry = ry;
+            }
+        }
+        return this.el("rect", attr);
+    };
+    /*\
+     * Paper.circle
+     [ method ]
+     **
+     * Draws a circle
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - r (number) radius
+     = (object) the `circle` element
+     **
+     > Usage
+     | var c = paper.circle(50, 50, 40);
+    \*/
+    proto.circle = function (cx, cy, r) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr = {
+                cx: cx,
+                cy: cy,
+                r: r
+            };
+        }
+        return this.el("circle", attr);
+    };
+
+    var preload = (function () {
+        function onerror() {
+            this.parentNode.removeChild(this);
+        }
+        return function (src, f) {
+            var img = glob.doc.createElement("img"),
+                body = glob.doc.body;
+            img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+            img.onload = function () {
+                f.call(img);
+                img.onload = img.onerror = null;
+                body.removeChild(img);
+            };
+            img.onerror = onerror;
+            body.appendChild(img);
+            img.src = src;
+        };
+    }());
+
+    /*\
+     * Paper.image
+     [ method ]
+     **
+     * Places an image on the surface
+     **
+     - src (string) URI of the source image
+     - x (number) x offset position
+     - y (number) y offset position
+     - width (number) width of the image
+     - height (number) height of the image
+     = (object) the `image` element
+     * or
+     = (object) Snap element object with type `image`
+     **
+     > Usage
+     | var c = paper.image("apple.png", 10, 10, 80, 80);
+    \*/
+    proto.image = function (src, x, y, width, height) {
+        var el = this.el("image");
+        if (is(src, "object") && "src" in src) {
+            el.attr(src);
+        } else if (src != null) {
+            var set = {
+                "xlink:href": src,
+                preserveAspectRatio: "none"
+            };
+            if (x != null && y != null) {
+                set.x = x;
+                set.y = y;
+            }
+            if (width != null && height != null) {
+                set.width = width;
+                set.height = height;
+            } else {
+                preload(src, function () {
+                    Snap._.$(el.node, {
+                        width: this.offsetWidth,
+                        height: this.offsetHeight
+                    });
+                });
+            }
+            Snap._.$(el.node, set);
+        }
+        return el;
+    };
+    /*\
+     * Paper.ellipse
+     [ method ]
+     **
+     * Draws an ellipse
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - rx (number) horizontal radius
+     - ry (number) vertical radius
+     = (object) the `ellipse` element
+     **
+     > Usage
+     | var c = paper.ellipse(50, 50, 40, 20);
+    \*/
+    proto.ellipse = function (cx, cy, rx, ry) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr ={
+                cx: cx,
+                cy: cy,
+                rx: rx,
+                ry: ry
+            };
+        }
+        return this.el("ellipse", attr);
+    };
+    // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier.
+    /*\
+     * Paper.path
+     [ method ]
+     **
+     * Creates a `<path>` element using the given string as the path's definition
+     - pathString (string) #optional path string in SVG format
+     * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:
+     | "M10,20L30,40"
+     * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.
+     *
+     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
+     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
+     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
+     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
+     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
+     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
+     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
+     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
+     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
+     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
+     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
+     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
+     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
+     * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier.
+     * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point.
+     > Usage
+     | var c = paper.path("M10 10L90 90");
+     | // draw a diagonal line:
+     | // move to 10,10, line to 90,90
+    \*/
+    proto.path = function (d) {
+        var attr;
+        if (is(d, "object") && !is(d, "array")) {
+            attr = d;
+        } else if (d) {
+            attr = {d: d};
+        }
+        return this.el("path", attr);
+    };
+    /*\
+     * Paper.g
+     [ method ]
+     **
+     * Creates a group element
+     **
+     - varargs (…) #optional elements to nest within the group
+     = (object) the `g` element
+     **
+     > Usage
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g(c2, c1); // note that the order of elements is different
+     * or
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g();
+     | g.add(c2, c1);
+    \*/
+    /*\
+     * Paper.group
+     [ method ]
+     **
+     * See @Paper.g
+    \*/
+    proto.group = proto.g = function (first) {
+        var attr,
+            el = this.el("g");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.svg
+     [ method ]
+     **
+     * Creates a nested SVG element.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `svg` element
+     **
+    \*/
+    proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) {
+        var attrs = {};
+        if (is(x, "object") && y == null) {
+            attrs = x;
+        } else {
+            if (x != null) {
+                attrs.x = x;
+            }
+            if (y != null) {
+                attrs.y = y;
+            }
+            if (width != null) {
+                attrs.width = width;
+            }
+            if (height != null) {
+                attrs.height = height;
+            }
+            if (vbx != null && vby != null && vbw != null && vbh != null) {
+                attrs.viewBox = [vbx, vby, vbw, vbh];
+            }
+        }
+        return this.el("svg", attrs);
+    };
+    /*\
+     * Paper.mask
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a mask.
+     **
+     = (object) the `mask` element
+     **
+    \*/
+    proto.mask = function (first) {
+        var attr,
+            el = this.el("mask");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.ptrn
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a pattern.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `pattern` element
+     **
+    \*/
+    proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) {
+        if (is(x, "object")) {
+            var attr = x;
+        } else {
+            attr = {patternUnits: "userSpaceOnUse"};
+            if (x) {
+                attr.x = x;
+            }
+            if (y) {
+                attr.y = y;
+            }
+            if (width != null) {
+                attr.width = width;
+            }
+            if (height != null) {
+                attr.height = height;
+            }
+            if (vx != null && vy != null && vw != null && vh != null) {
+                attr.viewBox = [vx, vy, vw, vh];
+            } else {
+                attr.viewBox = [x || 0, y || 0, width || 0, height || 0];
+            }
+        }
+        return this.el("pattern", attr);
+    };
+    /*\
+     * Paper.use
+     [ method ]
+     **
+     * Creates a <use> element.
+     - id (string) @optional id of element to link
+     * or
+     - id (Element) @optional element to link
+     **
+     = (object) the `use` element
+     **
+    \*/
+    proto.use = function (id) {
+        if (id != null) {
+            if (id instanceof Element) {
+                if (!id.attr("id")) {
+                    id.attr({id: Snap._.id(id)});
+                }
+                id = id.attr("id");
+            }
+            if (String(id).charAt() == "#") {
+                id = id.substring(1);
+            }
+            return this.el("use", {"xlink:href": "#" + id});
+        } else {
+            return Element.prototype.use.call(this);
+        }
+    };
+    /*\
+     * Paper.symbol
+     [ method ]
+     **
+     * Creates a <symbol> element.
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     = (object) the `symbol` element
+     **
+    \*/
+    proto.symbol = function (vx, vy, vw, vh) {
+        var attr = {};
+        if (vx != null && vy != null && vw != null && vh != null) {
+            attr.viewBox = [vx, vy, vw, vh];
+        }
+
+        return this.el("symbol", attr);
+    };
+    /*\
+     * Paper.text
+     [ method ]
+     **
+     * Draws a text string
+     **
+     - x (number) x coordinate position
+     - y (number) y coordinate position
+     - text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
+     = (object) the `text` element
+     **
+     > Usage
+     | var t1 = paper.text(50, 50, "Snap");
+     | var t2 = paper.text(50, 50, ["S","n","a","p"]);
+     | // Text path usage
+     | t1.attr({textpath: "M10,10L100,100"});
+     | // or
+     | var pth = paper.path("M10,10L100,100");
+     | t1.attr({textpath: pth});
+    \*/
+    proto.text = function (x, y, text) {
+        var attr = {};
+        if (is(x, "object")) {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                text: text || ""
+            };
+        }
+        return this.el("text", attr);
+    };
+    /*\
+     * Paper.line
+     [ method ]
+     **
+     * Draws a line
+     **
+     - x1 (number) x coordinate position of the start
+     - y1 (number) y coordinate position of the start
+     - x2 (number) x coordinate position of the end
+     - y2 (number) y coordinate position of the end
+     = (object) the `line` element
+     **
+     > Usage
+     | var t1 = paper.line(50, 50, 100, 100);
+    \*/
+    proto.line = function (x1, y1, x2, y2) {
+        var attr = {};
+        if (is(x1, "object")) {
+            attr = x1;
+        } else if (x1 != null) {
+            attr = {
+                x1: x1,
+                x2: x2,
+                y1: y1,
+                y2: y2
+            };
+        }
+        return this.el("line", attr);
+    };
+    /*\
+     * Paper.polyline
+     [ method ]
+     **
+     * Draws a polyline
+     **
+     - points (array) array of points
+     * or
+     - varargs (…) points
+     = (object) the `polyline` element
+     **
+     > Usage
+     | var p1 = paper.polyline([10, 10, 100, 100]);
+     | var p2 = paper.polyline(10, 10, 100, 100);
+    \*/
+    proto.polyline = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polyline", attr);
+    };
+    /*\
+     * Paper.polygon
+     [ method ]
+     **
+     * Draws a polygon. See @Paper.polyline
+    \*/
+    proto.polygon = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polygon", attr);
+    };
+    // gradients
+    (function () {
+        var $ = Snap._.$;
+        // gradients' helpers
+        function Gstops() {
+            return this.selectAll("stop");
+        }
+        function GaddStop(color, offset) {
+            var stop = $("stop"),
+                attr = {
+                    offset: +offset + "%"
+                };
+            color = Snap.color(color);
+            attr["stop-color"] = color.hex;
+            if (color.opacity < 1) {
+                attr["stop-opacity"] = color.opacity;
+            }
+            $(stop, attr);
+            this.node.appendChild(stop);
+            return this;
+        }
+        function GgetBBox() {
+            if (this.type == "linearGradient") {
+                var x1 = $(this.node, "x1") || 0,
+                    x2 = $(this.node, "x2") || 1,
+                    y1 = $(this.node, "y1") || 0,
+                    y2 = $(this.node, "y2") || 0;
+                return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1));
+            } else {
+                var cx = this.node.cx || .5,
+                    cy = this.node.cy || .5,
+                    r = this.node.r || 0;
+                return Snap._.box(cx - r, cy - r, r * 2, r * 2);
+            }
+        }
+        function gradient(defs, str) {
+            var grad = eve("snap.util.grad.parse", null, str).firstDefined(),
+                el;
+            if (!grad) {
+                return null;
+            }
+            grad.params.unshift(defs);
+            if (grad.type.toLowerCase() == "l") {
+                el = gradientLinear.apply(0, grad.params);
+            } else {
+                el = gradientRadial.apply(0, grad.params);
+            }
+            if (grad.type != grad.type.toLowerCase()) {
+                $(el.node, {
+                    gradientUnits: "userSpaceOnUse"
+                });
+            }
+            var stops = grad.stops,
+                len = stops.length,
+                start = 0,
+                j = 0;
+            function seed(i, end) {
+                var step = (end - start) / (i - j);
+                for (var k = j; k < i; k++) {
+                    stops[k].offset = +(+start + step * (k - j)).toFixed(2);
+                }
+                j = i;
+                start = end;
+            }
+            len--;
+            for (var i = 0; i < len; i++) if ("offset" in stops[i]) {
+                seed(i, stops[i].offset);
+            }
+            stops[len].offset = stops[len].offset || 100;
+            seed(len, stops[len].offset);
+            for (i = 0; i <= len; i++) {
+                var stop = stops[i];
+                el.addStop(stop.color, stop.offset);
+            }
+            return el;
+        }
+        function gradientLinear(defs, x1, y1, x2, y2) {
+            var el = Snap._.make("linearGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (x1 != null) {
+                $(el.node, {
+                    x1: x1,
+                    y1: y1,
+                    x2: x2,
+                    y2: y2
+                });
+            }
+            return el;
+        }
+        function gradientRadial(defs, cx, cy, r, fx, fy) {
+            var el = Snap._.make("radialGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (cx != null) {
+                $(el.node, {
+                    cx: cx,
+                    cy: cy,
+                    r: r
+                });
+            }
+            if (fx != null && fy != null) {
+                $(el.node, {
+                    fx: fx,
+                    fy: fy
+                });
+            }
+            return el;
+        }
+        /*\
+         * Paper.gradient
+         [ method ]
+         **
+         * Creates a gradient element
+         **
+         - gradient (string) gradient descriptor
+         > Gradient Descriptor
+         * The gradient descriptor is an expression formatted as
+         * follows: `<type>(<coords>)<colors>`.  The `<type>` can be
+         * either linear or radial.  The uppercase `L` or `R` letters
+         * indicate absolute coordinates offset from the SVG surface.
+         * Lowercase `l` or `r` letters indicate coordinates
+         * calculated relative to the element to which the gradient is
+         * applied.  Coordinates specify a linear gradient vector as
+         * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`,
+         * `r` and optional `fx`, `fy` specifying a focal point away
+         * from the center of the circle. Specify `<colors>` as a list
+         * of dash-separated CSS color values.  Each color may be
+         * followed by a custom offset value, separated with a colon
+         * character.
+         > Examples
+         * Linear gradient, relative from top-left corner to bottom-right
+         * corner, from black through red to white:
+         | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
+         * Linear gradient, absolute from (0, 0) to (100, 100), from black
+         * through red at 25% to white:
+         | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");
+         * Radial gradient, relative from the center of the element with radius
+         * half the width, from black to white:
+         | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
+         * To apply the gradient:
+         | paper.circle(50, 50, 40).attr({
+         |     fill: g
+         | });
+         = (object) the `gradient` element
+        \*/
+        proto.gradient = function (str) {
+            return gradient(this.defs, str);
+        };
+        proto.gradientLinear = function (x1, y1, x2, y2) {
+            return gradientLinear(this.defs, x1, y1, x2, y2);
+        };
+        proto.gradientRadial = function (cx, cy, r, fx, fy) {
+            return gradientRadial(this.defs, cx, cy, r, fx, fy);
+        };
+        /*\
+         * Paper.toString
+         [ method ]
+         **
+         * Returns SVG code for the @Paper
+         = (string) SVG code for the @Paper
+        \*/
+        proto.toString = function () {
+            var doc = this.node.ownerDocument,
+                f = doc.createDocumentFragment(),
+                d = doc.createElement("div"),
+                svg = this.node.cloneNode(true),
+                res;
+            f.appendChild(d);
+            d.appendChild(svg);
+            Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"});
+            res = d.innerHTML;
+            f.removeChild(f.firstChild);
+            return res;
+        };
+        /*\
+         * Paper.toDataURL
+         [ method ]
+         **
+         * Returns SVG code for the @Paper as Data URI string.
+         = (string) Data URI string
+        \*/
+        proto.toDataURL = function () {
+            if (window && window.btoa) {
+                return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this)));
+            }
+        };
+        /*\
+         * Paper.clear
+         [ method ]
+         **
+         * Removes all child nodes of the paper, except <defs>.
+        \*/
+        proto.clear = function () {
+            var node = this.node.firstChild,
+                next;
+            while (node) {
+                next = node.nextSibling;
+                if (node.tagName != "defs") {
+                    node.parentNode.removeChild(node);
+                } else {
+                    proto.clear.call({node: node});
+                }
+                node = next;
+            }
+        };
+    }());
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        clone = Snap._.clone,
+        has = "hasOwnProperty",
+        p2s = /,?([a-z]),?/gi,
+        toFloat = parseFloat,
+        math = Math,
+        PI = math.PI,
+        mmin = math.min,
+        mmax = math.max,
+        pow = math.pow,
+        abs = math.abs;
+    function paths(ps) {
+        var p = paths.ps = paths.ps || {};
+        if (p[ps]) {
+            p[ps].sleep = 100;
+        } else {
+            p[ps] = {
+                sleep: 100
+            };
+        }
+        setTimeout(function () {
+            for (var key in p) if (p[has](key) && key != ps) {
+                p[key].sleep--;
+                !p[key].sleep && delete p[key];
+            }
+        });
+        return p[ps];
+    }
+    function box(x, y, width, height) {
+        if (x == null) {
+            x = y = width = height = 0;
+        }
+        if (y == null) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        return {
+            x: x,
+            y: y,
+            width: width,
+            w: width,
+            height: height,
+            h: height,
+            x2: x + width,
+            y2: y + height,
+            cx: x + width / 2,
+            cy: y + height / 2,
+            r1: math.min(width, height) / 2,
+            r2: math.max(width, height) / 2,
+            r0: math.sqrt(width * width + height * height) / 2,
+            path: rectPath(x, y, width, height),
+            vb: [x, y, width, height].join(" ")
+        };
+    }
+    function toString() {
+        return this.join(",").replace(p2s, "$1");
+    }
+    function pathClone(pathArray) {
+        var res = clone(pathArray);
+        res.toString = toString;
+        return res;
+    }
+    function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        if (length == null) {
+            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+        } else {
+            return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
+                getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+        }
+    }
+    function getLengthFactory(istotal, subpath) {
+        function O(val) {
+            return +(+val).toFixed(3);
+        }
+        return Snap._.cacher(function (path, length, onlystart) {
+            if (path instanceof Element) {
+                path = path.attr("d");
+            }
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            sp += [
+                                "C" + O(point.start.x),
+                                O(point.start.y),
+                                O(point.m.x),
+                                O(point.m.y),
+                                O(point.x),
+                                O(point.y)
+                            ];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = [
+                                "M" + O(point.x),
+                                O(point.y) + "C" + O(point.n.x),
+                                O(point.n.y),
+                                O(point.end.x),
+                                O(point.end.y),
+                                O(p[5]),
+                                O(p[6])
+                            ].join();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            return point;
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p.shift() + p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+            return point;
+        }, null, Snap._.clone);
+    }
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            t13 = pow(t1, 3),
+            t12 = pow(t1, 2),
+            t2 = t * t,
+            t3 = t2 * t,
+            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+            ax = t1 * p1x + t * c1x,
+            ay = t1 * p1y + t * c1y,
+            cx = t1 * c2x + t * p2x,
+            cy = t1 * c2y + t * p2y,
+            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+        // (mx > nx || my < ny) && (alpha += 180);
+        return {
+            x: x,
+            y: y,
+            m: {x: mx, y: my},
+            n: {x: nx, y: ny},
+            start: {x: ax, y: ay},
+            end: {x: cx, y: cy},
+            alpha: alpha
+        };
+    }
+    function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        if (!Snap.is(p1x, "array")) {
+            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+        }
+        var bbox = curveDim.apply(null, p1x);
+        return box(
+            bbox.min.x,
+            bbox.min.y,
+            bbox.max.x - bbox.min.x,
+            bbox.max.y - bbox.min.y
+        );
+    }
+    function isPointInsideBBox(bbox, x, y) {
+        return  x >= bbox.x &&
+                x <= bbox.x + bbox.width &&
+                y >= bbox.y &&
+                y <= bbox.y + bbox.height;
+    }
+    function isBBoxIntersect(bbox1, bbox2) {
+        bbox1 = box(bbox1);
+        bbox2 = box(bbox2);
+        return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2)
+            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
+                || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
+                || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+    }
+    function base3(t, p1, p2, p3, p4) {
+        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+        return t * t2 - 3 * p1 + 3 * p2;
+    }
+    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+        if (z == null) {
+            z = 1;
+        }
+        z = z > 1 ? 1 : z < 0 ? 0 : z;
+        var z2 = z / 2,
+            n = 12,
+            Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
+            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
+            sum = 0;
+        for (var i = 0; i < n; i++) {
+            var ct = z2 * Tvalues[i] + z2,
+                xbase = base3(ct, x1, x2, x3, x4),
+                ybase = base3(ct, y1, y2, y3, y4),
+                comb = xbase * xbase + ybase * ybase;
+            sum += Cvalues[i] * math.sqrt(comb);
+        }
+        return z2 * sum;
+    }
+    function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+            return;
+        }
+        var t = 1,
+            step = t / 2,
+            t2 = t - step,
+            l,
+            e = .01;
+        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        while (abs(l - ll) > e) {
+            step /= 2;
+            t2 += (l < ll ? 1 : -1) * step;
+            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        }
+        return t2;
+    }
+    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+        if (
+            mmax(x1, x2) < mmin(x3, x4) ||
+            mmin(x1, x2) > mmax(x3, x4) ||
+            mmax(y1, y2) < mmin(y3, y4) ||
+            mmin(y1, y2) > mmax(y3, y4)
+        ) {
+            return;
+        }
+        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+        if (!denominator) {
+            return;
+        }
+        var px = nx / denominator,
+            py = ny / denominator,
+            px2 = +px.toFixed(2),
+            py2 = +py.toFixed(2);
+        if (
+            px2 < +mmin(x1, x2).toFixed(2) ||
+            px2 > +mmax(x1, x2).toFixed(2) ||
+            px2 < +mmin(x3, x4).toFixed(2) ||
+            px2 > +mmax(x3, x4).toFixed(2) ||
+            py2 < +mmin(y1, y2).toFixed(2) ||
+            py2 > +mmax(y1, y2).toFixed(2) ||
+            py2 < +mmin(y3, y4).toFixed(2) ||
+            py2 > +mmax(y3, y4).toFixed(2)
+        ) {
+            return;
+        }
+        return {x: px, y: py};
+    }
+    function inter(bez1, bez2) {
+        return interHelper(bez1, bez2);
+    }
+    function interCount(bez1, bez2) {
+        return interHelper(bez1, bez2, 1);
+    }
+    function interHelper(bez1, bez2, justCount) {
+        var bbox1 = bezierBBox(bez1),
+            bbox2 = bezierBBox(bez2);
+        if (!isBBoxIntersect(bbox1, bbox2)) {
+            return justCount ? 0 : [];
+        }
+        var l1 = bezlen.apply(0, bez1),
+            l2 = bezlen.apply(0, bez2),
+            n1 = ~~(l1 / 8),
+            n2 = ~~(l2 / 8),
+            dots1 = [],
+            dots2 = [],
+            xy = {},
+            res = justCount ? 0 : [];
+        for (var i = 0; i < n1 + 1; i++) {
+            var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
+            dots1.push({x: p.x, y: p.y, t: i / n1});
+        }
+        for (i = 0; i < n2 + 1; i++) {
+            p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
+            dots2.push({x: p.x, y: p.y, t: i / n2});
+        }
+        for (i = 0; i < n1; i++) {
+            for (var j = 0; j < n2; j++) {
+                var di = dots1[i],
+                    di1 = dots1[i + 1],
+                    dj = dots2[j],
+                    dj1 = dots2[j + 1],
+                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+                if (is) {
+                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+                        continue;
+                    }
+                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
+                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+                        if (justCount) {
+                            res++;
+                        } else {
+                            res.push({
+                                x: is.x,
+                                y: is.y,
+                                t1: t1,
+                                t2: t2
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function pathIntersection(path1, path2) {
+        return interPathHelper(path1, path2);
+    }
+    function pathIntersectionNumber(path1, path2) {
+        return interPathHelper(path1, path2, 1);
+    }
+    function interPathHelper(path1, path2, justCount) {
+        path1 = path2curve(path1);
+        path2 = path2curve(path2);
+        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+            res = justCount ? 0 : [];
+        for (var i = 0, ii = path1.length; i < ii; i++) {
+            var pi = path1[i];
+            if (pi[0] == "M") {
+                x1 = x1m = pi[1];
+                y1 = y1m = pi[2];
+            } else {
+                if (pi[0] == "C") {
+                    bez1 = [x1, y1].concat(pi.slice(1));
+                    x1 = bez1[6];
+                    y1 = bez1[7];
+                } else {
+                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+                    x1 = x1m;
+                    y1 = y1m;
+                }
+                for (var j = 0, jj = path2.length; j < jj; j++) {
+                    var pj = path2[j];
+                    if (pj[0] == "M") {
+                        x2 = x2m = pj[1];
+                        y2 = y2m = pj[2];
+                    } else {
+                        if (pj[0] == "C") {
+                            bez2 = [x2, y2].concat(pj.slice(1));
+                            x2 = bez2[6];
+                            y2 = bez2[7];
+                        } else {
+                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+                            x2 = x2m;
+                            y2 = y2m;
+                        }
+                        var intr = interHelper(bez1, bez2, justCount);
+                        if (justCount) {
+                            res += intr;
+                        } else {
+                            for (var k = 0, kk = intr.length; k < kk; k++) {
+                                intr[k].segment1 = i;
+                                intr[k].segment2 = j;
+                                intr[k].bez1 = bez1;
+                                intr[k].bez2 = bez2;
+                            }
+                            res = res.concat(intr);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function isPointInsidePath(path, x, y) {
+        var bbox = pathBBox(path);
+        return isPointInsideBBox(bbox, x, y) &&
+               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+    }
+    function pathBBox(path) {
+        var pth = paths(path);
+        if (pth.bbox) {
+            return clone(pth.bbox);
+        }
+        if (!path) {
+            return box();
+        }
+        path = path2curve(path);
+        var x = 0,
+            y = 0,
+            X = [],
+            Y = [],
+            p;
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            p = path[i];
+            if (p[0] == "M") {
+                x = p[1];
+                y = p[2];
+                X.push(x);
+                Y.push(y);
+            } else {
+                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                X = X.concat(dim.min.x, dim.max.x);
+                Y = Y.concat(dim.min.y, dim.max.y);
+                x = p[5];
+                y = p[6];
+            }
+        }
+        var xmin = mmin.apply(0, X),
+            ymin = mmin.apply(0, Y),
+            xmax = mmax.apply(0, X),
+            ymax = mmax.apply(0, Y),
+            bb = box(xmin, ymin, xmax - xmin, ymax - ymin);
+        pth.bbox = clone(bb);
+        return bb;
+    }
+    function rectPath(x, y, w, h, r) {
+        if (r) {
+            return [
+                ["M", +x + (+r), y],
+                ["l", w - r * 2, 0],
+                ["a", r, r, 0, 0, 1, r, r],
+                ["l", 0, h - r * 2],
+                ["a", r, r, 0, 0, 1, -r, r],
+                ["l", r * 2 - w, 0],
+                ["a", r, r, 0, 0, 1, -r, -r],
+                ["l", 0, r * 2 - h],
+                ["a", r, r, 0, 0, 1, r, -r],
+                ["z"]
+            ];
+        }
+        var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+        res.toString = toString;
+        return res;
+    }
+    function ellipsePath(x, y, rx, ry, a) {
+        if (a == null && ry == null) {
+            ry = rx;
+        }
+        x = +x;
+        y = +y;
+        rx = +rx;
+        ry = +ry;
+        if (a != null) {
+            var rad = Math.PI / 180,
+                x1 = x + rx * Math.cos(-ry * rad),
+                x2 = x + rx * Math.cos(-a * rad),
+                y1 = y + rx * Math.sin(-ry * rad),
+                y2 = y + rx * Math.sin(-a * rad),
+                res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]];
+        } else {
+            res = [
+                ["M", x, y],
+                ["m", 0, -ry],
+                ["a", rx, ry, 0, 1, 1, 0, 2 * ry],
+                ["a", rx, ry, 0, 1, 1, 0, -2 * ry],
+                ["z"]
+            ];
+        }
+        res.toString = toString;
+        return res;
+    }
+    var unit2px = Snap._unit2px,
+        getPath = {
+        path: function (el) {
+            return el.attr("path");
+        },
+        circle: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx, attr.cy, attr.r);
+        },
+        ellipse: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry);
+        },
+        rect: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry);
+        },
+        image: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height);
+        },
+        line: function (el) {
+            return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")];
+        },
+        polyline: function (el) {
+            return "M" + el.attr("points");
+        },
+        polygon: function (el) {
+            return "M" + el.attr("points") + "z";
+        },
+        deflt: function (el) {
+            var bbox = el.node.getBBox();
+            return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+        }
+    };
+    function pathToRelative(pathArray) {
+        var pth = paths(pathArray),
+            lowerCase = String.prototype.toLowerCase;
+        if (pth.rel) {
+            return pathClone(pth.rel);
+        }
+        if (!Snap.is(pathArray, "array") || !Snap.is(pathArray && pathArray[0], "array")) {
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0;
+        if (pathArray[0][0] == "M") {
+            x = pathArray[0][1];
+            y = pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res.push(["M", x, y]);
+        }
+        for (var i = start, ii = pathArray.length; i < ii; i++) {
+            var r = res[i] = [],
+                pa = pathArray[i];
+            if (pa[0] != lowerCase.call(pa[0])) {
+                r[0] = lowerCase.call(pa[0]);
+                switch (r[0]) {
+                    case "a":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +(pa[6] - x).toFixed(3);
+                        r[7] = +(pa[7] - y).toFixed(3);
+                        break;
+                    case "v":
+                        r[1] = +(pa[1] - y).toFixed(3);
+                        break;
+                    case "m":
+                        mx = pa[1];
+                        my = pa[2];
+                    default:
+                        for (var j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+                        }
+                }
+            } else {
+                r = res[i] = [];
+                if (pa[0] == "m") {
+                    mx = pa[1] + x;
+                    my = pa[2] + y;
+                }
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    res[i][k] = pa[k];
+                }
+            }
+            var len = res[i].length;
+            switch (res[i][0]) {
+                case "z":
+                    x = mx;
+                    y = my;
+                    break;
+                case "h":
+                    x += +res[i][len - 1];
+                    break;
+                case "v":
+                    y += +res[i][len - 1];
+                    break;
+                default:
+                    x += +res[i][len - 2];
+                    y += +res[i][len - 1];
+            }
+        }
+        res.toString = toString;
+        pth.rel = pathClone(res);
+        return res;
+    }
+    function pathToAbsolute(pathArray) {
+        var pth = paths(pathArray);
+        if (pth.abs) {
+            return pathClone(pth.abs);
+        }
+        if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        if (!pathArray || !pathArray.length) {
+            return [["M", 0, 0]];
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0,
+            pa0;
+        if (pathArray[0][0] == "M") {
+            x = +pathArray[0][1];
+            y = +pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res[0] = ["M", x, y];
+        }
+        var crz = pathArray.length == 3 &&
+            pathArray[0][0] == "M" &&
+            pathArray[1][0].toUpperCase() == "R" &&
+            pathArray[2][0].toUpperCase() == "Z";
+        for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+            res.push(r = []);
+            pa = pathArray[i];
+            pa0 = pa[0];
+            if (pa0 != pa0.toUpperCase()) {
+                r[0] = pa0.toUpperCase();
+                switch (r[0]) {
+                    case "A":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +pa[6] + x;
+                        r[7] = +pa[7] + y;
+                        break;
+                    case "V":
+                        r[1] = +pa[1] + y;
+                        break;
+                    case "H":
+                        r[1] = +pa[1] + x;
+                        break;
+                    case "R":
+                        var dots = [x, y].concat(pa.slice(1));
+                        for (var j = 2, jj = dots.length; j < jj; j++) {
+                            dots[j] = +dots[j] + x;
+                            dots[++j] = +dots[j] + y;
+                        }
+                        res.pop();
+                        res = res.concat(catmullRom2bezier(dots, crz));
+                        break;
+                    case "O":
+                        res.pop();
+                        dots = ellipsePath(x, y, pa[1], pa[2]);
+                        dots.push(dots[0]);
+                        res = res.concat(dots);
+                        break;
+                    case "U":
+                        res.pop();
+                        res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                        r = ["U"].concat(res[res.length - 1].slice(-2));
+                        break;
+                    case "M":
+                        mx = +pa[1] + x;
+                        my = +pa[2] + y;
+                    default:
+                        for (j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +pa[j] + ((j % 2) ? x : y);
+                        }
+                }
+            } else if (pa0 == "R") {
+                dots = [x, y].concat(pa.slice(1));
+                res.pop();
+                res = res.concat(catmullRom2bezier(dots, crz));
+                r = ["R"].concat(pa.slice(-2));
+            } else if (pa0 == "O") {
+                res.pop();
+                dots = ellipsePath(x, y, pa[1], pa[2]);
+                dots.push(dots[0]);
+                res = res.concat(dots);
+            } else if (pa0 == "U") {
+                res.pop();
+                res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                r = ["U"].concat(res[res.length - 1].slice(-2));
+            } else {
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    r[k] = pa[k];
+                }
+            }
+            pa0 = pa0.toUpperCase();
+            if (pa0 != "O") {
+                switch (r[0]) {
+                    case "Z":
+                        x = +mx;
+                        y = +my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
+                    default:
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
+                }
+            }
+        }
+        res.toString = toString;
+        pth.abs = pathClone(res);
+        return res;
+    }
+    function l2c(x1, y1, x2, y2) {
+        return [x1, y1, x2, y2, x2, y2];
+    }
+    function q2c(x1, y1, ax, ay, x2, y2) {
+        var _13 = 1 / 3,
+            _23 = 2 / 3;
+        return [
+                _13 * x1 + _23 * ax,
+                _13 * y1 + _23 * ay,
+                _13 * x2 + _23 * ax,
+                _13 * y2 + _23 * ay,
+                x2,
+                y2
+            ];
+    }
+    function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+        // for more information of where this math came from visit:
+        // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+        var _120 = PI * 120 / 180,
+            rad = PI / 180 * (+angle || 0),
+            res = [],
+            xy,
+            rotate = Snap._.cacher(function (x, y, rad) {
+                var X = x * math.cos(rad) - y * math.sin(rad),
+                    Y = x * math.sin(rad) + y * math.cos(rad);
+                return {x: X, y: Y};
+            });
+        if (!recursive) {
+            xy = rotate(x1, y1, -rad);
+            x1 = xy.x;
+            y1 = xy.y;
+            xy = rotate(x2, y2, -rad);
+            x2 = xy.x;
+            y2 = xy.y;
+            var cos = math.cos(PI / 180 * angle),
+                sin = math.sin(PI / 180 * angle),
+                x = (x1 - x2) / 2,
+                y = (y1 - y2) / 2;
+            var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+            if (h > 1) {
+                h = math.sqrt(h);
+                rx = h * rx;
+                ry = h * ry;
+            }
+            var rx2 = rx * rx,
+                ry2 = ry * ry,
+                k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                    math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                cx = k * rx * y / ry + (x1 + x2) / 2,
+                cy = k * -ry * x / rx + (y1 + y2) / 2,
+                f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+                f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+            f1 = x1 < cx ? PI - f1 : f1;
+            f2 = x2 < cx ? PI - f2 : f2;
+            f1 < 0 && (f1 = PI * 2 + f1);
+            f2 < 0 && (f2 = PI * 2 + f2);
+            if (sweep_flag && f1 > f2) {
+                f1 = f1 - PI * 2;
+            }
+            if (!sweep_flag && f2 > f1) {
+                f2 = f2 - PI * 2;
+            }
+        } else {
+            f1 = recursive[0];
+            f2 = recursive[1];
+            cx = recursive[2];
+            cy = recursive[3];
+        }
+        var df = f2 - f1;
+        if (abs(df) > _120) {
+            var f2old = f2,
+                x2old = x2,
+                y2old = y2;
+            f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+            x2 = cx + rx * math.cos(f2);
+            y2 = cy + ry * math.sin(f2);
+            res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+        }
+        df = f2 - f1;
+        var c1 = math.cos(f1),
+            s1 = math.sin(f1),
+            c2 = math.cos(f2),
+            s2 = math.sin(f2),
+            t = math.tan(df / 4),
+            hx = 4 / 3 * rx * t,
+            hy = 4 / 3 * ry * t,
+            m1 = [x1, y1],
+            m2 = [x1 + hx * s1, y1 - hy * c1],
+            m3 = [x2 + hx * s2, y2 - hy * c2],
+            m4 = [x2, y2];
+        m2[0] = 2 * m1[0] - m2[0];
+        m2[1] = 2 * m1[1] - m2[1];
+        if (recursive) {
+            return [m2, m3, m4].concat(res);
+        } else {
+            res = [m2, m3, m4].concat(res).join().split(",");
+            var newres = [];
+            for (var i = 0, ii = res.length; i < ii; i++) {
+                newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+            }
+            return newres;
+        }
+    }
+    function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t;
+        return {
+            x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+            y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+        };
+    }
+
+    // Returns bounding box of cubic bezier curve.
+    // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+    // Original version: NISHIO Hirokazu
+    // Modifications: https://github.com/timo22345
+    function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) {
+        var tvalues = [],
+            bounds = [[], []],
+            a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+        for (var i = 0; i < 2; ++i) {
+            if (i == 0) {
+                b = 6 * x0 - 12 * x1 + 6 * x2;
+                a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+                c = 3 * x1 - 3 * x0;
+            } else {
+                b = 6 * y0 - 12 * y1 + 6 * y2;
+                a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+                c = 3 * y1 - 3 * y0;
+            }
+            if (abs(a) < 1e-12) {
+                if (abs(b) < 1e-12) {
+                    continue;
+                }
+                t = -c / b;
+                if (0 < t && t < 1) {
+                    tvalues.push(t);
+                }
+                continue;
+            }
+            b2ac = b * b - 4 * c * a;
+            sqrtb2ac = math.sqrt(b2ac);
+            if (b2ac < 0) {
+                continue;
+            }
+            t1 = (-b + sqrtb2ac) / (2 * a);
+            if (0 < t1 && t1 < 1) {
+                tvalues.push(t1);
+            }
+            t2 = (-b - sqrtb2ac) / (2 * a);
+            if (0 < t2 && t2 < 1) {
+                tvalues.push(t2);
+            }
+        }
+
+        var x, y, j = tvalues.length,
+            jlen = j,
+            mt;
+        while (j--) {
+            t = tvalues[j];
+            mt = 1 - t;
+            bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
+            bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
+        }
+
+        bounds[0][jlen] = x0;
+        bounds[1][jlen] = y0;
+        bounds[0][jlen + 1] = x3;
+        bounds[1][jlen + 1] = y3;
+        bounds[0].length = bounds[1].length = jlen + 2;
+
+
+        return {
+          min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])},
+          max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])}
+        };
+    }
+
+    function path2curve(path, path2) {
+        var pth = !path2 && paths(path);
+        if (!path2 && pth.curve) {
+            return pathClone(pth.curve);
+        }
+        var p = pathToAbsolute(path),
+            p2 = path2 && pathToAbsolute(path2),
+            attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            processPath = function (path, d, pcom) {
+                var nx, ny;
+                if (!path) {
+                    return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+                }
+                !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
+                switch (path[0]) {
+                    case "M":
+                        d.X = path[1];
+                        d.Y = path[2];
+                        break;
+                    case "A":
+                        path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
+                        break;
+                    case "S":
+                        if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
+                            nx = d.x * 2 - d.bx;          // And reflect the previous
+                            ny = d.y * 2 - d.by;          // command's control point relative to the current point.
+                        }
+                        else {                            // or some else or nothing
+                            nx = d.x;
+                            ny = d.y;
+                        }
+                        path = ["C", nx, ny].concat(path.slice(1));
+                        break;
+                    case "T":
+                        if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
+                            d.qx = d.x * 2 - d.qx;        // And make a reflection similar
+                            d.qy = d.y * 2 - d.qy;        // to case "S".
+                        }
+                        else {                            // or something else or nothing
+                            d.qx = d.x;
+                            d.qy = d.y;
+                        }
+                        path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+                        break;
+                    case "Q":
+                        d.qx = path[1];
+                        d.qy = path[2];
+                        path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                        break;
+                    case "L":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
+                        break;
+                    case "H":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
+                        break;
+                    case "V":
+                        path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
+                        break;
+                    case "Z":
+                        path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
+                        break;
+                }
+                return path;
+            },
+            fixArc = function (pp, i) {
+                if (pp[i].length > 7) {
+                    pp[i].shift();
+                    var pi = pp[i];
+                    while (pi.length) {
+                        pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved
+                        p2 && (pcoms2[i] = "A"); // the same as above
+                        pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
+                    }
+                    pp.splice(i, 1);
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            fixM = function (path1, path2, a1, a2, i) {
+                if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                    path2.splice(i, 0, ["M", a2.x, a2.y]);
+                    a1.bx = 0;
+                    a1.by = 0;
+                    a1.x = path1[i][1];
+                    a1.y = path1[i][2];
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            pcoms1 = [], // path commands of original path p
+            pcoms2 = [], // path commands of original path p2
+            pfirst = "", // temporary holder for original path command
+            pcom = ""; // holder for previous path command of original path
+        for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+            p[i] && (pfirst = p[i][0]); // save current path command
+
+            if (pfirst != "C") // C is not saved yet, because it may be result of conversion
+            {
+                pcoms1[i] = pfirst; // Save current path command
+                i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom
+            }
+            p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
+
+            if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command
+            // which may produce multiple C:s
+            // so we have to make sure that C is also C in original path
+
+            fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
+
+            if (p2) { // the same procedures is done to p2
+                p2[i] && (pfirst = p2[i][0]);
+                if (pfirst != "C") {
+                    pcoms2[i] = pfirst;
+                    i && (pcom = pcoms2[i - 1]);
+                }
+                p2[i] = processPath(p2[i], attrs2, pcom);
+
+                if (pcoms2[i] != "A" && pfirst == "C") {
+                    pcoms2[i] = "C";
+                }
+
+                fixArc(p2, i);
+            }
+            fixM(p, p2, attrs, attrs2, i);
+            fixM(p2, p, attrs2, attrs, i);
+            var seg = p[i],
+                seg2 = p2 && p2[i],
+                seglen = seg.length,
+                seg2len = p2 && seg2.length;
+            attrs.x = seg[seglen - 2];
+            attrs.y = seg[seglen - 1];
+            attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+            attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+            attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+            attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+            attrs2.x = p2 && seg2[seg2len - 2];
+            attrs2.y = p2 && seg2[seg2len - 1];
+        }
+        if (!p2) {
+            pth.curve = pathClone(p);
+        }
+        return p2 ? [p, p2] : p;
+    }
+    function mapPath(path, matrix) {
+        if (!matrix) {
+            return path;
+        }
+        var x, y, i, j, ii, jj, pathi;
+        path = path2curve(path);
+        for (i = 0, ii = path.length; i < ii; i++) {
+            pathi = path[i];
+            for (j = 1, jj = pathi.length; j < jj; j += 2) {
+                x = matrix.x(pathi[j], pathi[j + 1]);
+                y = matrix.y(pathi[j], pathi[j + 1]);
+                pathi[j] = x;
+                pathi[j + 1] = y;
+            }
+        }
+        return path;
+    }
+
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp, z) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+            var p = [
+                        {x: +crp[i - 2], y: +crp[i - 1]},
+                        {x: +crp[i],     y: +crp[i + 1]},
+                        {x: +crp[i + 2], y: +crp[i + 3]},
+                        {x: +crp[i + 4], y: +crp[i + 5]}
+                    ];
+            if (z) {
+                if (!i) {
+                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+                } else if (iLen - 4 == i) {
+                    p[3] = {x: +crp[0], y: +crp[1]};
+                } else if (iLen - 2 == i) {
+                    p[2] = {x: +crp[0], y: +crp[1]};
+                    p[3] = {x: +crp[2], y: +crp[3]};
+                }
+            } else {
+                if (iLen - 4 == i) {
+                    p[3] = p[2];
+                } else if (!i) {
+                    p[0] = {x: +crp[i], y: +crp[i + 1]};
+                }
+            }
+            d.push(["C",
+                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                  (p[1].y + 6*p[2].y - p[3].y) / 6,
+                  p[2].x,
+                  p[2].y
+            ]);
+        }
+
+        return d;
+    }
+
+    // export
+    Snap.path = paths;
+
+    /*\
+     * Snap.path.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the given path in pixels
+     **
+     - path (string) SVG path string
+     **
+     = (number) length
+    \*/
+    Snap.path.getTotalLength = getTotalLength;
+    /*\
+     * Snap.path.getPointAtLength
+     [ method ]
+     **
+     * Returns the coordinates of the point located at the given length along the given path
+     **
+     - path (string) SVG path string
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    Snap.path.getPointAtLength = getPointAtLength;
+    /*\
+     * Snap.path.getSubpath
+     [ method ]
+     **
+     * Returns the subpath of a given path between given start and end lengths
+     **
+     - path (string) SVG path string
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    Snap.path.getSubpath = function (path, from, to) {
+        if (this.getTotalLength(path) - to < 1e-6) {
+            return getSubpathsAtLength(path, from).end;
+        }
+        var a = getSubpathsAtLength(path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+    /*\
+     * Element.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the path in pixels (only works for `path` elements)
+     = (number) length
+    \*/
+    elproto.getTotalLength = function () {
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+    };
+    // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a <path> is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length?
+    /*\
+     * Element.getPointAtLength
+     [ method ]
+     **
+     * Returns coordinates of the point located at the given length on the given path (only works for `path` elements)
+     **
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    elproto.getPointAtLength = function (length) {
+        return getPointAtLength(this.attr("d"), length);
+    };
+    // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear.
+    /*\
+     * Element.getSubpath
+     [ method ]
+     **
+     * Returns subpath of a given element from given start and end lengths (only works for `path` elements)
+     **
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    elproto.getSubpath = function (from, to) {
+        return Snap.path.getSubpath(this.attr("d"), from, to);
+    };
+    Snap._.box = box;
+    /*\
+     * Snap.path.findDotsAtSegment
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds dot coordinates on the given cubic beziér curve at the given t
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     - t (number) position on the curve (0..1)
+     = (object) point information in format:
+     o {
+     o     x: (number) x coordinate of the point,
+     o     y: (number) y coordinate of the point,
+     o     m: {
+     o         x: (number) x coordinate of the left anchor,
+     o         y: (number) y coordinate of the left anchor
+     o     },
+     o     n: {
+     o         x: (number) x coordinate of the right anchor,
+     o         y: (number) y coordinate of the right anchor
+     o     },
+     o     start: {
+     o         x: (number) x coordinate of the start of the curve,
+     o         y: (number) y coordinate of the start of the curve
+     o     },
+     o     end: {
+     o         x: (number) x coordinate of the end of the curve,
+     o         y: (number) y coordinate of the end of the curve
+     o     },
+     o     alpha: (number) angle of the curve derivative at the point
+     o }
+    \*/
+    Snap.path.findDotsAtSegment = findDotsAtSegment;
+    /*\
+     * Snap.path.bezierBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given cubic beziér curve
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     * or
+     - bez (array) array of six points for beziér curve
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.bezierBBox = bezierBBox;
+    /*\
+     * Snap.path.isPointInsideBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside bounding box
+     - bbox (string) bounding box
+     - x (string) x coordinate of the point
+     - y (string) y coordinate of the point
+     = (boolean) `true` if point is inside
+    \*/
+    Snap.path.isPointInsideBBox = isPointInsideBBox;
+    Snap.closest = function (x, y, X, Y) {
+        var r = 100,
+            b = box(x - r / 2, y - r / 2, r, r),
+            inside = [],
+            getter = X[0].hasOwnProperty("x") ? function (i) {
+                return {
+                    x: X[i].x,
+                    y: X[i].y
+                };
+            } : function (i) {
+                return {
+                    x: X[i],
+                    y: Y[i]
+                };
+            },
+            found = 0;
+        while (r <= 1e6 && !found) {
+            for (var i = 0, ii = X.length; i < ii; i++) {
+                var xy = getter(i);
+                if (isPointInsideBBox(b, xy.x, xy.y)) {
+                    found++;
+                    inside.push(xy);
+                    break;
+                }
+            }
+            if (!found) {
+                r *= 2;
+                b = box(x - r / 2, y - r / 2, r, r)
+            }
+        }
+        if (r == 1e6) {
+            return;
+        }
+        var len = Infinity,
+            res;
+        for (i = 0, ii = inside.length; i < ii; i++) {
+            var l = Snap.len(x, y, inside[i].x, inside[i].y);
+            if (len > l) {
+                len = l;
+                inside[i].len = l;
+                res = inside[i];
+            }
+        }
+        return res;
+    };
+    /*\
+     * Snap.path.isBBoxIntersect
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if two bounding boxes intersect
+     - bbox1 (string) first bounding box
+     - bbox2 (string) second bounding box
+     = (boolean) `true` if bounding boxes intersect
+    \*/
+    Snap.path.isBBoxIntersect = isBBoxIntersect;
+    /*\
+     * Snap.path.intersection
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds intersections of two paths
+     - path1 (string) path string
+     - path2 (string) path string
+     = (array) dots of intersection
+     o [
+     o     {
+     o         x: (number) x coordinate of the point,
+     o         y: (number) y coordinate of the point,
+     o         t1: (number) t value for segment of path1,
+     o         t2: (number) t value for segment of path2,
+     o         segment1: (number) order number for segment of path1,
+     o         segment2: (number) order number for segment of path2,
+     o         bez1: (array) eight coordinates representing beziér curve for the segment of path1,
+     o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
+     o     }
+     o ]
+    \*/
+    Snap.path.intersection = pathIntersection;
+    Snap.path.intersectionNumber = pathIntersectionNumber;
+    /*\
+     * Snap.path.isPointInside
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside a given closed path.
+     *
+     * Note: fill mode doesn’t affect the result of this method.
+     - path (string) path string
+     - x (number) x of the point
+     - y (number) y of the point
+     = (boolean) `true` if point is inside the path
+    \*/
+    Snap.path.isPointInside = isPointInsidePath;
+    /*\
+     * Snap.path.getBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given path
+     - path (string) path string
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.getBBox = pathBBox;
+    Snap.path.get = getPath;
+    /*\
+     * Snap.path.toRelative
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into relative values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toRelative = pathToRelative;
+    /*\
+     * Snap.path.toAbsolute
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into absolute values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toAbsolute = pathToAbsolute;
+    /*\
+     * Snap.path.toCubic
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path to a new path where all segments are cubic beziér curves
+     - pathString (string|array) path string or array of segments
+     = (array) array of segments
+    \*/
+    Snap.path.toCubic = path2curve;
+    /*\
+     * Snap.path.map
+     [ method ]
+     **
+     * Transform the path string with the given matrix
+     - path (string) path string
+     - matrix (object) see @Matrix
+     = (string) transformed path string
+    \*/
+    Snap.path.map = mapPath;
+    Snap.path.toString = toString;
+    Snap.path.clone = pathClone;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var mmax = Math.max,
+        mmin = Math.min;
+
+    // Set
+    var Set = function (items) {
+        this.items = [];
+	this.bindings = {};
+        this.length = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items.length; i < ii; i++) {
+                if (items[i]) {
+                    this[this.items.length] = this.items[this.items.length] = items[i];
+                    this.length++;
+                }
+            }
+        }
+    },
+    setproto = Set.prototype;
+    /*\
+     * Set.push
+     [ method ]
+     **
+     * Adds each argument to the current set
+     = (object) original element
+    \*/
+    setproto.push = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments.length; i < ii; i++) {
+            item = arguments[i];
+            if (item) {
+                len = this.items.length;
+                this[len] = this.items[len] = item;
+                this.length++;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.pop
+     [ method ]
+     **
+     * Removes last element and returns it
+     = (object) element
+    \*/
+    setproto.pop = function () {
+        this.length && delete this[this.length--];
+        return this.items.pop();
+    };
+    /*\
+     * Set.forEach
+     [ method ]
+     **
+     * Executes given function for each element in the set
+     *
+     * If the function returns `false`, the loop stops running.
+     **
+     - callback (function) function to run
+     - thisArg (object) context object for the callback
+     = (object) Set object
+    \*/
+    setproto.forEach = function (callback, thisArg) {
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
+                return this;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.animate
+     [ method ]
+     **
+     * Animates each element in set in sync.
+     *
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     * or
+     - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]`
+     > Usage
+     | // animate all elements in set to radius 10
+     | set.animate({r: 10}, 500, mina.easein);
+     | // or
+     | // animate first element to radius 10, but second to radius 20 and in different time
+     | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]);
+     = (Element) the current element
+    \*/
+    setproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Snap._.Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = easing.dur;
+            attrs = attrs.attr;
+        }
+        var args = arguments;
+        if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) {
+            var each = true;
+        }
+        var begin,
+            handler = function () {
+                if (begin) {
+                    this.b = begin;
+                } else {
+                    begin = this.b;
+                }
+            },
+            cb = 0,
+            set = this,
+            callbacker = callback && function () {
+                if (++cb == set.length) {
+                    callback.call(this);
+                }
+            };
+        return this.forEach(function (el, i) {
+            eve.once("snap.animcreated." + el.id, handler);
+            if (each) {
+                args[i] && el.animate.apply(el, args[i]);
+            } else {
+                el.animate(attrs, ms, easing, callbacker);
+            }
+        });
+    };
+    setproto.remove = function () {
+        while (this.length) {
+            this.pop().remove();
+        }
+        return this;
+    };
+    /*\
+     * Set.bind
+     [ method ]
+     **
+     * Specifies how to handle a specific attribute when applied
+     * to a set.
+     *
+     **
+     - attr (string) attribute name
+     - callback (function) function to run
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     - eattr (string) attribute on the element to bind the attribute to
+     = (object) Set object
+    \*/
+    setproto.bind = function (attr, a, b) {
+        var data = {};
+        if (typeof a == "function") {
+            this.bindings[attr] = a;
+        } else {
+            var aname = b || attr;
+            this.bindings[attr] = function (v) {
+                data[aname] = v;
+                a.attr(data);
+            };
+        }
+        return this;
+    };
+    setproto.attr = function (value) {
+        var unbound = {};
+        for (var k in value) {
+            if (this.bindings[k]) {
+                this.bindings[k](value[k]);
+            } else {
+                unbound[k] = value[k];
+            }
+        }
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            this.items[i].attr(unbound);
+        }
+        return this;
+    };
+    /*\
+     * Set.clear
+     [ method ]
+     **
+     * Removes all elements from the set
+    \*/
+    setproto.clear = function () {
+        while (this.length) {
+            this.pop();
+        }
+    };
+    /*\
+     * Set.splice
+     [ method ]
+     **
+     * Removes range of elements from the set
+     **
+     - index (number) position of the deletion
+     - count (number) number of element to remove
+     - insertion… (object) #optional elements to insert
+     = (object) set elements that were deleted
+    \*/
+    setproto.splice = function (index, count, insertion) {
+        index = index < 0 ? mmax(this.length + index, 0) : index;
+        count = mmax(0, mmin(this.length - index, count));
+        var tail = [],
+            todel = [],
+            args = [],
+            i;
+        for (i = 2; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        }
+        for (i = 0; i < count; i++) {
+            todel.push(this[index + i]);
+        }
+        for (; i < this.length - index; i++) {
+            tail.push(this[index + i]);
+        }
+        var arglen = args.length;
+        for (i = 0; i < arglen + tail.length; i++) {
+            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+        }
+        i = this.items.length = this.length -= count - arglen;
+        while (this[i]) {
+            delete this[i++];
+        }
+        return new Set(todel);
+    };
+    /*\
+     * Set.exclude
+     [ method ]
+     **
+     * Removes given element from the set
+     **
+     - element (object) element to remove
+     = (boolean) `true` if object was found and removed from the set
+    \*/
+    setproto.exclude = function (el) {
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
+            return true;
+        }
+        return false;
+    };
+    setproto.insertAfter = function (el) {
+        var i = this.items.length;
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    setproto.getBBox = function () {
+        var x = [],
+            y = [],
+            x2 = [],
+            y2 = [];
+        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+            var box = this.items[i].getBBox();
+            x.push(box.x);
+            y.push(box.y);
+            x2.push(box.x + box.width);
+            y2.push(box.y + box.height);
+        }
+        x = mmin.apply(0, x);
+        y = mmin.apply(0, y);
+        x2 = mmax.apply(0, x2);
+        y2 = mmax.apply(0, y2);
+        return {
+            x: x,
+            y: y,
+            x2: x2,
+            y2: y2,
+            width: x2 - x,
+            height: y2 - y,
+            cx: x + (x2 - x) / 2,
+            cy: y + (y2 - y) / 2
+        };
+    };
+    setproto.clone = function (s) {
+        s = new Set;
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            s.push(this.items[i].clone());
+        }
+        return s;
+    };
+    setproto.toString = function () {
+        return "Snap\u2018s set";
+    };
+    setproto.type = "set";
+    // export
+    Snap.Set = Set;
+    Snap.set = function () {
+        var set = new Set;
+        if (arguments.length) {
+            set.push.apply(set, Array.prototype.slice.call(arguments, 0));
+        }
+        return set;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var names = {},
+        reUnit = /[a-z]+$/i,
+        Str = String;
+    names.stroke = names.fill = "colour";
+    function getEmpty(item) {
+        var l = item[0];
+        switch (l.toLowerCase()) {
+            case "t": return [l, 0, 0];
+            case "m": return [l, 1, 0, 0, 1, 0, 0];
+            case "r": if (item.length == 4) {
+                return [l, 0, item[2], item[3]];
+            } else {
+                return [l, 0];
+            }
+            case "s": if (item.length == 5) {
+                return [l, 1, 1, item[3], item[4]];
+            } else if (item.length == 3) {
+                return [l, 1, 1];
+            } else {
+                return [l, 1];
+            }
+        }
+    }
+    function equaliseTransform(t1, t2, getBBox) {
+        t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+        t1 = Snap.parseTransformString(t1) || [];
+        t2 = Snap.parseTransformString(t2) || [];
+        var maxlength = Math.max(t1.length, t2.length),
+            from = [],
+            to = [],
+            i = 0, j, jj,
+            tt1, tt2;
+        for (; i < maxlength; i++) {
+            tt1 = t1[i] || getEmpty(t2[i]);
+            tt2 = t2[i] || getEmpty(tt1);
+            if ((tt1[0] != tt2[0]) ||
+                (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                ) {
+                    t1 = Snap._.transform2matrix(t1, getBBox());
+                    t2 = Snap._.transform2matrix(t2, getBBox());
+                    from = [["m", t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]];
+                    to = [["m", t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]];
+                    break;
+            }
+            from[i] = [];
+            to[i] = [];
+            for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) {
+                j in tt1 && (from[i][j] = tt1[j]);
+                j in tt2 && (to[i][j] = tt2[j]);
+            }
+        }
+        return {
+            from: path2array(from),
+            to: path2array(to),
+            f: getPath(from)
+        };
+    }
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    function getViewBox(val) {
+        return val.join(" ");
+    }
+    function getColour(clr) {
+        return Snap.rgb(clr[0], clr[1], clr[2]);
+    }
+    function getPath(path) {
+        var k = 0, i, ii, j, jj, out, a, b = [];
+        for (i = 0, ii = path.length; i < ii; i++) {
+            out = "[";
+            a = ['"' + path[i][0] + '"'];
+            for (j = 1, jj = path[i].length; j < jj; j++) {
+                a[j] = "val[" + (k++) + "]";
+            }
+            out += a + "]";
+            b[i] = out;
+        }
+        return Function("val", "return Snap.path.toString.call([" + b + "])");
+    }
+    function path2array(path) {
+        var out = [];
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            for (var j = 1, jj = path[i].length; j < jj; j++) {
+                out.push(path[i][j]);
+            }
+        }
+        return out;
+    }
+    function isNumeric(obj) {
+        return isFinite(parseFloat(obj));
+    }
+    function arrayEqual(arr1, arr2) {
+        if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) {
+            return false;
+        }
+        return arr1.toString() == arr2.toString();
+    }
+    Element.prototype.equal = function (name, b) {
+        return eve("snap.util.equal", this, name, b).firstDefined();
+    };
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this;
+        if (isNumeric(a) && isNumeric(b)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getNumber
+            };
+        }
+        if (names[name] == "colour") {
+            A = Snap.color(a);
+            B = Snap.color(b);
+            return {
+                from: [A.r, A.g, A.b, A.opacity],
+                to: [B.r, B.g, B.b, B.opacity],
+                f: getColour
+            };
+        }
+        if (name == "viewBox") {
+            A = this.attr(name).vb.split(" ").map(Number);
+            B = b.split(" ").map(Number);
+            return {
+                from: A,
+                to: B,
+                f: getViewBox
+            };
+        }
+        if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
+            if (b instanceof Snap.Matrix) {
+                b = b.toTransformString();
+            }
+            if (!Snap._.rgTransform.test(b)) {
+                b = Snap._.svgTransform2string(b);
+            }
+            return equaliseTransform(a, b, function () {
+                return el.getBBox(1);
+            });
+        }
+        if (name == "d" || name == "path") {
+            A = Snap.path.toCubic(a, b);
+            return {
+                from: path2array(A[0]),
+                to: path2array(A[1]),
+                f: getPath(A[0])
+            };
+        }
+        if (name == "points") {
+            A = Str(a).split(Snap._.separator);
+            B = Str(b).split(Snap._.separator);
+            return {
+                from: A,
+                to: B,
+                f: function (val) { return val; }
+            };
+        }
+        var aUnit = a.match(reUnit),
+            bUnit = Str(b).match(reUnit);
+        if (aUnit && arrayEqual(aUnit, bUnit)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getUnit(aUnit)
+            };
+        } else {
+            return {
+                from: this.asPX(name),
+                to: this.asPX(name, b),
+                f: getNumber
+            };
+        }
+    });
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+    has = "hasOwnProperty",
+    supportsTouch = "createTouch" in glob.doc,
+    events = [
+        "click", "dblclick", "mousedown", "mousemove", "mouseout",
+        "mouseover", "mouseup", "touchstart", "touchmove", "touchend",
+        "touchcancel"
+    ],
+    touchMap = {
+        mousedown: "touchstart",
+        mousemove: "touchmove",
+        mouseup: "touchend"
+    },
+    getScroll = function (xy, el) {
+        var name = xy == "y" ? "scrollTop" : "scrollLeft",
+            doc = el && el.node ? el.node.ownerDocument : glob.doc;
+        return doc[name in doc.documentElement ? "documentElement" : "body"][name];
+    },
+    preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = function (obj, type, fn, element) {
+        var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+            f = function (e) {
+                var scrollY = getScroll("y", element),
+                    scrollX = getScroll("x", element);
+                if (supportsTouch && touchMap[has](type)) {
+                    for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                        if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) {
+                            var olde = e;
+                            e = e.targetTouches[i];
+                            e.originalEvent = olde;
+                            e.preventDefault = preventTouch;
+                            e.stopPropagation = stopTouch;
+                            break;
+                        }
+                    }
+                }
+                var x = e.clientX + scrollX,
+                    y = e.clientY + scrollY;
+                return fn.call(element, e, x, y);
+            };
+
+        if (type !== realName) {
+            obj.addEventListener(type, f, false);
+        }
+
+        obj.addEventListener(realName, f, false);
+
+        return function () {
+            if (type !== realName) {
+                obj.removeEventListener(type, f, false);
+            }
+
+            obj.removeEventListener(realName, f, false);
+            return true;
+        };
+    },
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = getScroll("y"),
+            scrollX = getScroll("x"),
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches && e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            var node = dragi.el.node,
+                o,
+                next = node.nextSibling,
+                parent = node.parentNode,
+                display = node.style.display;
+            // glob.win.opera && parent.removeChild(node);
+            // node.style.display = "none";
+            // o = dragi.el.paper.getElementByPoint(x, y);
+            // node.style.display = display;
+            // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+            // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o);
+            x += scrollX;
+            y += scrollY;
+            eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        Snap.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+            eve.off("snap.drag.*." + dragi.el.id);
+        }
+        drag = [];
+    };
+    /*\
+     * Element.click
+     [ method ]
+     **
+     * Adds a click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unclick
+     [ method ]
+     **
+     * Removes a click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.dblclick
+     [ method ]
+     **
+     * Adds a double click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.undblclick
+     [ method ]
+     **
+     * Removes a double click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousedown
+     [ method ]
+     **
+     * Adds a mousedown event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousedown
+     [ method ]
+     **
+     * Removes a mousedown event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousemove
+     [ method ]
+     **
+     * Adds a mousemove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousemove
+     [ method ]
+     **
+     * Removes a mousemove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseout
+     [ method ]
+     **
+     * Adds a mouseout event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseout
+     [ method ]
+     **
+     * Removes a mouseout event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseover
+     [ method ]
+     **
+     * Adds a mouseover event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseover
+     [ method ]
+     **
+     * Removes a mouseover event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseup
+     [ method ]
+     **
+     * Adds a mouseup event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseup
+     [ method ]
+     **
+     * Removes a mouseup event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchstart
+     [ method ]
+     **
+     * Adds a touchstart event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchstart
+     [ method ]
+     **
+     * Removes a touchstart event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchmove
+     [ method ]
+     **
+     * Adds a touchmove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchmove
+     [ method ]
+     **
+     * Removes a touchmove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchend
+     [ method ]
+     **
+     * Adds a touchend event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchend
+     [ method ]
+     **
+     * Removes a touchend event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchcancel
+     [ method ]
+     **
+     * Adds a touchcancel event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchcancel
+     [ method ]
+     **
+     * Removes a touchcancel event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    for (var i = events.length; i--;) {
+        (function (eventName) {
+            Snap[eventName] = elproto[eventName] = function (fn, scope) {
+                if (Snap.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({
+                        name: eventName,
+                        f: fn,
+                        unbind: addEvent(this.node || document, eventName, fn, scope || this)
+                    });
+                } else {
+                    for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) {
+                        try {
+                            this.events[i].f.call(this);
+                        } catch (e) {}
+                    }
+                }
+                return this;
+            };
+            Snap["un" + eventName] =
+            elproto["un" + eventName] = function (fn) {
+                var events = this.events || [],
+                    l = events.length;
+                while (l--) if (events[l].name == eventName &&
+                               (events[l].f == fn || !fn)) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    /*\
+     * Element.hover
+     [ method ]
+     **
+     * Adds hover event handlers to the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     - icontext (object) #optional context for hover in handler
+     - ocontext (object) #optional context for hover out handler
+     = (object) @Element
+    \*/
+    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+    };
+    /*\
+     * Element.unhover
+     [ method ]
+     **
+     * Removes hover event handlers from the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     = (object) @Element
+    \*/
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    var draggable = [];
+    // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture.
+    // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from?
+    // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason.
+    // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start.<id> on start, drag.end.<id> on end and drag.move.<id> on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID?
+    /*\
+     * Element.drag
+     [ method ]
+     **
+     * Adds event handlers for an element's drag gesture
+     **
+     - onmove (function) handler for moving
+     - onstart (function) handler for drag start
+     - onend (function) handler for drag end
+     - mcontext (object) #optional context for moving handler
+     - scontext (object) #optional context for drag start handler
+     - econtext (object) #optional context for drag end handler
+     * Additionaly following `drag` events are triggered: `drag.start.<id>` on start,
+     * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element is dragged over another element
+     * `drag.over.<id>` fires as well.
+     *
+     * Start event and start handler are called in specified context or in context of the element with following parameters:
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * Move event and move handler are called in specified context or in context of the element with following parameters:
+     o dx (number) shift by x from the start point
+     o dy (number) shift by y from the start point
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * End event and end handler are called in specified context or in context of the element with following parameters:
+     o event (object) DOM event object
+     = (object) @Element
+    \*/
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        var el = this;
+        if (!arguments.length) {
+            var origTransform;
+            return el.drag(function (dx, dy) {
+                this.attr({
+                    transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
+                });
+            }, function () {
+                origTransform = this.transform().local;
+            });
+        }
+        function start(e, x, y) {
+            (e.originalEvent || e).preventDefault();
+            el._drag.x = x;
+            el._drag.y = y;
+            el._drag.id = e.identifier;
+            !drag.length && Snap.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+            onstart && eve.on("snap.drag.start." + el.id, onstart);
+            onmove && eve.on("snap.drag.move." + el.id, onmove);
+            onend && eve.on("snap.drag.end." + el.id, onend);
+            eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e);
+        }
+        function init(e, x, y) {
+            eve("snap.draginit." + el.id, el, e, x, y);
+        }
+        eve.on("snap.draginit." + el.id, start);
+        el._drag = {};
+        draggable.push({el: el, start: start, init: init});
+        el.mousedown(init);
+        return el;
+    };
+    /*
+     * Element.onDragOver
+     [ method ]
+     **
+     * Shortcut to assign event handler for `drag.over.<id>` event, where `id` is the element's `id` (see @Element.id)
+     - f (function) handler for event, first argument would be the element you are dragging over
+    \*/
+    // elproto.onDragOver = function (f) {
+    //     f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id);
+    // };
+    /*\
+     * Element.undrag
+     [ method ]
+     **
+     * Removes all drag event handlers from the given element
+    \*/
+    elproto.undrag = function () {
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].init);
+            draggable.splice(i, 1);
+            eve.unbind("snap.drag.*." + this.id);
+            eve.unbind("snap.draginit." + this.id);
+        }
+        !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp);
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        pproto = Paper.prototype,
+        rgurl = /^\s*url\((.+)\)/,
+        Str = String,
+        $ = Snap._.$;
+    Snap.filter = {};
+    /*\
+     * Paper.filter
+     [ method ]
+     **
+     * Creates a `<filter>` element
+     **
+     - filstr (string) SVG fragment of filter provided as a string
+     = (object) @Element
+     * Note: It is recommended to use filters embedded into the page inside an empty SVG element.
+     > Usage
+     | var f = paper.filter('<feGaussianBlur stdDeviation="2"/>'),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    pproto.filter = function (filstr) {
+        var paper = this;
+        if (paper.type != "svg") {
+            paper = paper.paper;
+        }
+        var f = Snap.parse(Str(filstr)),
+            id = Snap._.id(),
+            width = paper.node.offsetWidth,
+            height = paper.node.offsetHeight,
+            filter = $("filter");
+        $(filter, {
+            id: id,
+            filterUnits: "userSpaceOnUse"
+        });
+        filter.appendChild(f.node);
+        paper.defs.appendChild(filter);
+        return new Element(filter);
+    };
+
+    eve.on("snap.util.getattr.filter", function () {
+        eve.stop();
+        var p = $(this.node, "filter");
+        if (p) {
+            var match = Str(p).match(rgurl);
+            return match && Snap.select(match[1]);
+        }
+    });
+    eve.on("snap.util.attr.filter", function (value) {
+        if (value instanceof Element && value.type == "filter") {
+            eve.stop();
+            var id = value.node.id;
+            if (!id) {
+                $(value.node, {id: value.id});
+                id = value.id;
+            }
+            $(this.node, {
+                filter: Snap.url(id)
+            });
+        }
+        if (!value || value == "none") {
+            eve.stop();
+            this.node.removeAttribute("filter");
+        }
+    });
+    /*\
+     * Snap.filter.blur
+     [ method ]
+     **
+     * Returns an SVG markup string for the blur filter
+     **
+     - x (number) amount of horizontal blur, in pixels
+     - y (number) #optional amount of vertical blur, in pixels
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.blur(5, 10)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.blur = function (x, y) {
+        if (x == null) {
+            x = 2;
+        }
+        var def = y == null ? x : [x, y];
+        return Snap.format('\<feGaussianBlur stdDeviation="{def}"/>', {
+            def: def
+        });
+    };
+    Snap.filter.blur.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.shadow
+     [ method ]
+     **
+     * Returns an SVG markup string for the shadow filter
+     **
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - blur (number) #optional amount of blur
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * which makes blur default to `4`. Or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - opacity (number) #optional `0..1` opacity of the shadow
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.shadow(0, 2, 3)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.shadow = function (dx, dy, blur, color, opacity) {
+        if (typeof blur == "string") {
+            color = blur;
+            opacity = color;
+            blur = 4;
+        }
+        if (typeof color != "string") {
+            opacity = color;
+            color = "#000";
+        }
+        color = color || "#000";
+        if (blur == null) {
+            blur = 4;
+        }
+        if (opacity == null) {
+            opacity = 1;
+        }
+        if (dx == null) {
+            dx = 0;
+            dy = 2;
+        }
+        if (dy == null) {
+            dy = dx;
+        }
+        color = Snap.color(color);
+        return Snap.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', {
+            color: color,
+            dx: dx,
+            dy: dy,
+            blur: blur,
+            opacity: opacity
+        });
+    };
+    Snap.filter.shadow.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.grayscale
+     [ method ]
+     **
+     * Returns an SVG markup string for the grayscale filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.grayscale = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>', {
+            a: 0.2126 + 0.7874 * (1 - amount),
+            b: 0.7152 - 0.7152 * (1 - amount),
+            c: 0.0722 - 0.0722 * (1 - amount),
+            d: 0.2126 - 0.2126 * (1 - amount),
+            e: 0.7152 + 0.2848 * (1 - amount),
+            f: 0.0722 - 0.0722 * (1 - amount),
+            g: 0.2126 - 0.2126 * (1 - amount),
+            h: 0.0722 + 0.9278 * (1 - amount)
+        });
+    };
+    Snap.filter.grayscale.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.sepia
+     [ method ]
+     **
+     * Returns an SVG markup string for the sepia filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.sepia = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>', {
+            a: 0.393 + 0.607 * (1 - amount),
+            b: 0.769 - 0.769 * (1 - amount),
+            c: 0.189 - 0.189 * (1 - amount),
+            d: 0.349 - 0.349 * (1 - amount),
+            e: 0.686 + 0.314 * (1 - amount),
+            f: 0.168 - 0.168 * (1 - amount),
+            g: 0.272 - 0.272 * (1 - amount),
+            h: 0.534 - 0.534 * (1 - amount),
+            i: 0.131 + 0.869 * (1 - amount)
+        });
+    };
+    Snap.filter.sepia.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.saturate
+     [ method ]
+     **
+     * Returns an SVG markup string for the saturate filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.saturate = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="saturate" values="{amount}"/>', {
+            amount: 1 - amount
+        });
+    };
+    Snap.filter.saturate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.hueRotate
+     [ method ]
+     **
+     * Returns an SVG markup string for the hue-rotate filter
+     **
+     - angle (number) angle of rotation
+     = (string) filter representation
+    \*/
+    Snap.filter.hueRotate = function (angle) {
+        angle = angle || 0;
+        return Snap.format('<feColorMatrix type="hueRotate" values="{angle}"/>', {
+            angle: angle
+        });
+    };
+    Snap.filter.hueRotate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.invert
+     [ method ]
+     **
+     * Returns an SVG markup string for the invert filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.invert = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+//        <feColorMatrix type="matrix" values="-1 0 0 0 1  0 -1 0 0 1  0 0 -1 0 1  0 0 0 1 0" color-interpolation-filters="sRGB"/>
+        return Snap.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: 1 - amount
+        });
+    };
+    Snap.filter.invert.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.brightness
+     [ method ]
+     **
+     * Returns an SVG markup string for the brightness filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.brightness = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>', {
+            amount: amount
+        });
+    };
+    Snap.filter.brightness.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.contrast
+     [ method ]
+     **
+     * Returns an SVG markup string for the contrast filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.contrast = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: .5 - amount / 2
+        });
+    };
+    Snap.filter.contrast.toString = function () {
+        return this();
+    };
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var box = Snap._.box,
+        is = Snap.is,
+        firstLetter = /^[^a-z]*([tbmlrc])/i,
+        toString = function () {
+            return "T" + this.dx + "," + this.dy;
+        };
+    /*\
+     * Element.getAlign
+     [ method ]
+     **
+     * Returns shift needed to align the element relatively to given element.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string
+     > Usage
+     | el.transform(el.getAlign(el2, "top"));
+     * or
+     | var dy = el.getAlign(el2, "top").dy;
+    \*/
+    Element.prototype.getAlign = function (el, way) {
+        if (way == null && is(el, "string")) {
+            way = el;
+            el = null;
+        }
+        el = el || this.paper;
+        var bx = el.getBBox ? el.getBBox() : box(el),
+            bb = this.getBBox(),
+            out = {};
+        way = way && way.match(firstLetter);
+        way = way ? way[1].toLowerCase() : "c";
+        switch (way) {
+            case "t":
+                out.dx = 0;
+                out.dy = bx.y - bb.y;
+            break;
+            case "b":
+                out.dx = 0;
+                out.dy = bx.y2 - bb.y2;
+            break;
+            case "m":
+                out.dx = 0;
+                out.dy = bx.cy - bb.cy;
+            break;
+            case "l":
+                out.dx = bx.x - bb.x;
+                out.dy = 0;
+            break;
+            case "r":
+                out.dx = bx.x2 - bb.x2;
+                out.dy = 0;
+            break;
+            default:
+                out.dx = bx.cx - bb.cx;
+                out.dy = 0;
+            break;
+        }
+        out.toString = toString;
+        return out;
+    };
+    /*\
+     * Element.align
+     [ method ]
+     **
+     * Aligns the element relatively to given one via transformation.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object) this element
+     > Usage
+     | el.align(el2, "top");
+     * or
+     | el.align("middle");
+    \*/
+    Element.prototype.align = function (el, way) {
+        return this.transform("..." + this.getAlign(el, way));
+    };
+});
+
+return Snap;
+}));
diff --git a/web/pgadmin/misc/templates/explain/js/explain.js b/web/pgadmin/misc/templates/explain/js/explain.js
new file mode 100644
index 0000000..bedc51e
--- /dev/null
+++ b/web/pgadmin/misc/templates/explain/js/explain.js
@@ -0,0 +1,688 @@
+define (
+  'pgadmin.misc.explain',
+  ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'backbone', 'snap.svg'],
+  function($, _, S, pgAdmin, Backbone, Snap) {
+
+pgAdmin = pgAdmin || window.pgAdmin || {};
+var pgExplain = pgAdmin.Explain;
+
+// Snap.svg plug-in to write multitext as image name
+Snap.plugin(function (Snap, Element, Paper, glob) {
+  Paper.prototype.multitext = function (x, y, txt, max_width, attributes) {
+    var svg = Snap(),
+        abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+        isWordBroken = false,
+        temp = svg.text(0, 0, abc);
+
+    temp.attr(attributes);
+
+    /*
+     * Find letter width in pixels and
+     * index from where the text should be broken
+     */
+    var letter_width = temp.getBBox().width / abc.length,
+        word_break_index = Math.round((max_width / letter_width)) - 1;
+
+    svg.remove();
+
+    var words = txt.split(" "),
+        width_so_far = 0,
+        lines=[], curr_line = '',
+        /*
+         * Function to divide string into multiple lines
+         * and store them in an array if it size crosses
+         * the max-width boundary.
+         */
+        splitTextInMultiLine = function(leading, so_far, line) {
+          var l = line.length,
+              res = [];
+
+          if (l == 0)
+            return res;
+
+          if (so_far && (so_far + (l * letter_width) > max_width)) {
+            res.push(leading);
+            res = res.concat(splitTextInMultiLine('', 0, line));
+          } else if (so_far) {
+            res.push(leading + ' ' + line);
+          } else {
+            if (leading)
+                res.push(leading);
+            if (line.length > word_break_index + 1)
+                res.push(line.slice(0, word_break_index) + '-');
+            else
+                res.push(line);
+            res = res.concat(splitTextInMultiLine('', 0, line.slice(word_break_index)));
+          }
+
+          return res;
+        };
+
+    for (var i = 0; i < words.length; i++) {
+      var tmpArr = splitTextInMultiLine(
+            curr_line, width_so_far, words[i]
+          );
+
+      if (curr_line) {
+        lines = lines.slice(0, lines.length - 2);
+      }
+      lines = lines.concat(tmpArr);
+      curr_line = lines[lines.length - 1];
+      width_so_far = (curr_line.length * letter_width);
+    }
+
+    // Create multiple tspan for each string in array
+    var t = this.text(x,y,lines).attr(attributes);
+    t.selectAll("tspan:nth-child(n+2)").attr({
+      dy: "1.2em",
+      x: x
+    });
+    return t;
+  };
+});
+
+if (pgAdmin.Explain)
+    return pgAdmin.Explain;
+
+var pgExplain = pgAdmin.Explain = {
+   // Prefix path where images are stored
+   prefix: '{{ url_for('misc.static', filename='explain/img') }}/'
+};
+
+/*
+ * A map which is used to fetch the image to be drawn and
+ * text which will appear below it
+ */
+var imageMapper = {
+    "Aggregate" : {
+        "image":"ex_aggregate.png", "image_text":"Aggregate"
+    },
+    'Append' : {
+        "image":"ex_append.png","image_text":"Append"
+    },
+    "Bitmap Index Scan" : function(data) {
+        return {
+            "image":"ex_bmp_index.png", "image_text":data['Index Name']
+        };
+    },
+    "Bitmap Heap Scan" : function(data) {
+  return {"image":"ex_bmp_heap.png","image_text":data['Relation Name']};
+},
+"BitmapAnd" : {"image":"ex_bmp_and.png","image_text":"Bitmap AND"},
+"BitmapOr" : {"image":"ex_bmp_or.png","image_text":"Bitmap OR"},
+"CTE Scan" : {"image":"ex_cte_scan.png","image_text":"CTE Scan"},
+"Function Scan" : {"image":"ex_result.png","image_text":"Function Scan"},
+"Foreign Scan" : {"image":"ex_foreign_scan.png","image_text":"Foreign Scan"},
+"Gather" : {"image":"ex_gather_motion.png","image_text":"Gather"},
+"Group" : {"image":"ex_group.png","image_text":"Group"},
+"GroupAggregate": {"image":"ex_aggregate.png","image_text":"Group Aggregate"},
+"Hash" : {"image":"ex_hash.png","image_text":"Hash"},
+"Hash Join": function(data) {
+  if (!data['Join Type']) return {"image":"ex_join.png","image_text":"Join"};
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_hash_anti_join.png","image_text":"Hash Anti Join"};
+    case 'Semi': return {"image":"ex_hash_semi_join.png","image_text":"Hash Semi Join"};
+    default: return {"image":"ex_hash.png","image_text":String("Hash " + data['Join Type'] + " Join" )};
+  }
+},
+"HashAggregate" : {"image":"ex_aggregate.png","image_text":"Hash Aggregate"},
+"Index Only Scan" : function(data) {
+  return {"image":"ex_index_only_scan.png","image_text":data['Index Name']};
+},
+"Index Scan" : function(data) {
+  return {"image":"ex_index_scan.png","image_text":data['Index Name']};
+},
+"Index Scan Backword" : {"image":"ex_index_scan.png","image_text":"Index Backward Scan"},
+"Limit" : {"image":"ex_limit.png","image_text":"Limit"},
+"LockRows" : {"image":"ex_lock_rows.png","image_text":"Lock Rows"},
+"Materialize" : {"image":"ex_materialize.png","image_text":"Materialize"},
+"Merge Append": {"image":"ex_merge_append.png","image_text":"Merge Append"},
+"Merge Join": function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_merge_anti_join.png","image_text":"Merge Anti Join"};
+    case 'Semi': return {"image":"ex_merge_semi_join.png","image_text":"Merge Semi Join"};
+    default: return {"image":"ex_merge.png","image_text":String("Merge " + data['Join Type'] + " Join" )};
+  }
+},
+"ModifyTable" : function(data) {
+  switch (data['Operaton']) {
+    case "insert": return { "image":"ex_insert.png",
+                            "image_text":"Insert"
+                           };
+    case "update": return {"image":"ex_update.png","image_text":"Update"};
+    case "Delete": return {"image":"ex_delete.png","image_text":"Delete"};
+  }
+},
+'Nested Loop' : function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_nested_loop_anti_join.png","image_text":"Nested Loop Anti Join"};
+    case 'Semi': return {"image":"ex_nested_loop_semi_join.png","image_text":"Nested Loop Semi Join"};
+    default: return {"image":"ex_nested.png","image_text":"Nested Loop " + data['Join Type'] + " Join"};
+  }
+},
+"Recursive Union" : {"image":"ex_recursive_union.png","image_text":"Recursive Union"},
+"Result" : {"image":"ex_result.png","image_text":"Result"},
+"Sample Scan" : {"image":"ex_scan.png","image_text":"Sample Scan"},
+"Scan" : {"image":"ex_scan.png","image_text":"Scan"},
+"Seek" : {"image":"ex_seek.png","image_text":"Seek"},
+"SetOp" : function(data) {
+  var strategy = data['Strategy'],
+      command = data['Command'];
+
+  if(strategy == "Hashed") {
+    if(command.startsWith("Intersect")) {
+      if(command == "Intersect All")
+        return {"image":"ex_hash_setop_intersect_all.png","image_text":"Hashed Intersect All"};
+      return {"image":"ex_hash_setop_intersect.png","image_text":"Hashed Intersect"};
+    }
+    else if (command.startsWith("Except")) {
+      if(command == "Except All")
+        return {"image":"ex_hash_setop_except_all.png","image_text":"Hashed Except All"};
+      return {"image":"ex_hash_setop_except.png","image_text":"Hash Except"};
+    }
+    return {"image":"ex_hash_setop_unknown.png","image_text":"Hashed SetOp Unknown"};
+  }
+  return {"image":"ex_setop.png","image_text":"SetOp"};
+},
+"Seq Scan": function(data) {
+  return {"image":"ex_scan.png","image_text":data['Relation Name']};
+},
+"Subquery Scan" : {"image":"ex_subplan.png","image_text":"SubQuery Scan"},
+"Sort" : {"image":"ex_sort.png","image_text":"Sort"},
+"Tid Scan" : {"image":"ex_tid_scan.png","image_text":"Tid Scan"},
+"Unique" : {"image":"ex_unique.png","image_text":"Unique"},
+"Values Scan" : {"image":"ex_values_scan.png","image_text":"Values Scan"},
+"WindowAgg" : {"image":"ex_window_aggregate.png","image_text":"Window Aggregate"},
+"WorkTable Scan" : {"image":"ex_worktable_scan.png","image_text":"WorkTable Scan"},
+"Undefined" : {"image":"ex_unknown.png","image_text":"Undefined"},
+}
+
+// Some predefined constants used to calculate image location and its border
+var pWIDTH = pHEIGHT = 100.
+    IMAGE_WIDTH = IMAGE_HEIGHT = 50;
+var offsetX = 200,
+    offsetY = 60;
+var ARROW_WIDTH = 10,
+    ARROW_HEIGHT = 10,
+    DEFAULT_ARROW_SIZE = 2;
+var TXT_ALLIGN = 5,
+    TXT_SIZE = "15px";
+var TOTAL_WIDTH = undefined,
+    TOTAL_HEIGHT = undefined;
+var xMargin = 25,
+    yMargin = 25;
+var MIN_ZOOM_FACTOR = 0.01,
+    MAX_ZOOM_FACTOR = 2,
+    INIT_ZOOM_FACTOR = 1;
+    ZOOM_RATIO = 0.05;
+
+
+// Backbone model for each plan property of input JSON object
+var PlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plans": [],
+        level: [],
+        "image": undefined,
+        "image_text": undefined,
+        xpos: undefined,
+        ypos: undefined,
+        width: pWIDTH,
+        height: pHEIGHT
+    },
+    parse: function(data) {
+        var idx = 1,
+            lvl = data.level = data.level || [idx],
+            plans = [],
+            node_type = data['Node Type'],
+            // Calculating relative xpos of current node from top node
+            xpos = data.xpos = data.xpos - pWIDTH,
+            // Calculating relative ypos of current node from top node
+            ypos = data.ypos,
+            maxChildWidth = 0;
+
+        data['width'] = pWIDTH;
+        data['height'] = pHEIGHT;
+
+        /*
+         * calculating xpos, ypos, width and height if current node is a subplan
+         */
+        if (data['Parent Relationship'] === "SubPlan") {
+            data['width'] += (xMargin * 2) + (xMargin / 2);
+            data['height'] += (yMargin * 2);
+            data['ypos'] += yMargin;
+            xpos -= xMargin;
+            ypos += yMargin;
+        }
+
+        if(node_type.startsWith("(slice"))
+            node_type = node_type.substring(0,7);
+
+        // Get the image information for current node
+        var mapperObj = (_.isFunction(imageMapper[node_type]) &&
+                imageMapper[node_type].apply(undefined, [data])) ||
+                imageMapper[node_type] || 'Undefined';
+
+        data["image"] = mapperObj["image"];
+        data["image_text"] = mapperObj["image_text"];
+
+        // Start calculating xpos, ypos, width and height for child plans if any
+        if ('Plans' in data) {
+
+            data['width'] += offsetX;
+
+            _.each(data['Plans'], function(p) {
+                var level = _.clone(lvl),
+                    plan = new PlanModel();
+
+                level.push(idx);
+                plan.set(plan.parse(_.extend(
+                    p, {
+                        "level": level,
+                        xpos: xpos - offsetX,
+                        ypos: ypos
+                    })));
+
+                if (maxChildWidth < plan.get('width')) {
+                    maxChildWidth = plan.get('width');
+                }
+
+                var childHeight = plan.get('height');
+
+                if (idx !== 1) {
+                    data['height'] = data['height'] + childHeight + offsetY;
+                } else if (childHeight > data['height']) {
+                    data['height'] = childHeight;
+                }
+                ypos += childHeight + offsetY;
+
+                plans.push(plan);
+                idx++;
+            });
+        }
+
+        // Final Width and Height of current node
+        data['width'] += maxChildWidth;
+        data['Plans'] = plans;
+
+        return data;
+    },
+
+    /*
+     * Required to parse and include non-default params of
+     * plan into backbone model
+     */
+    toJSON: function(non_recursive) {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (non_recursive) {
+            delete res['Plans'];
+      } else {
+            var plans = [];
+            _.each(res['Plans'], function(p) {
+              plans.push(p.toJSON());
+            });
+            res['Plans'] = plans;
+      }
+      return res;
+    },
+
+    // Draw an arrow to parent node
+    drawPolyLine: function(g, startX, startY, endX, endY, opts, arrowOpts) {
+      // Calculate end point of first starting straight line (startx1, starty1)
+      // Calculate start point of 2nd straight line (endx1, endy1)
+      var midX1 = startX + ((endX - startX) / 3),
+          midX2 = startX + (2 * ((endX - startX) / 3));
+
+      //create arrow head
+      var arrow = g.polygon(
+                    [0, ARROW_HEIGHT,
+                    (ARROW_WIDTH / 2),ARROW_HEIGHT,
+                    (ARROW_HEIGHT / 4), 0,
+                    0, ARROW_WIDTH]
+                    ).transform("r90");
+      var marker = arrow.marker(
+                         0, 0, ARROW_WIDTH, ARROW_HEIGHT, 0, (ARROW_WIDTH / 2)
+                         ).attr(arrowOpts);
+
+      // First straight line
+      g.line(
+        startX, startY, midX1, startY
+        ).attr(opts);
+
+      // Diagonal line
+      g.line(
+        midX1-1, startY, midX2, endY
+        ).attr(opts);
+
+      // Last straight line
+      var line = g.line(
+                   midX2, endY, endX, endY
+                   ).attr(opts);
+      line.attr({markerEnd: marker})
+    },
+
+    // Draw image, its name and its tooltip
+    draw: function(s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer) {
+        var g = s.g();
+        var currentXpos = xpos + this.get('xpos') ,
+            currentYpos = ypos + this.get('ypos'),
+            isSubPlan = (this.get('Parent Relationship') === "SubPlan");
+
+        // Draw the subplan rectangle
+        if (isSubPlan) {
+          g.rect(
+            currentXpos - this.get('width') + pWIDTH + xMargin,
+            currentYpos - yMargin,
+            this.get('width') - xMargin,
+            this.get('height'), 5
+          ).attr({
+              stroke: '#444444',
+              'strokeWidth': 1.2,
+              fill: 'gray',
+              fillOpacity: 0.2
+          });
+
+          //provide subplan name
+          var text = g.text(
+            currentXpos  + pWIDTH - ( this.get('width') / 2) - xMargin,
+            currentYpos + pHEIGHT  - (this.get('height') / 2) - yMargin,
+            this.get('Subplan Name')
+          ).attr({
+            fontSize: TXT_SIZE, "text-anchor":"start",
+            fill: 'red'
+          });
+        }
+
+        // Draw the actual image for current node
+        var image = g.image(
+            pgExplain.prefix + this.get('image'),
+            currentXpos + (pWIDTH - IMAGE_WIDTH) / 2,
+            currentYpos + (pHEIGHT - IMAGE_HEIGHT) / 2,
+            IMAGE_WIDTH,
+            IMAGE_HEIGHT
+        );
+
+        // Draw tooltip
+        var image_data = this.toJSON();
+        image.mouseover(function(evt){
+
+          // Empty the tooltip content if it has any and add new data
+          toolTipContainer.empty();
+          var tooltip = $('<table></table>',{
+                           class: "pgadmin-tooltip-table"
+                        }).appendTo(toolTipContainer);
+          _.each(image_data, function(value,key) {
+            if(key !== 'image' && key !== 'Plans' &&
+               key !== 'level' && key !== 'image' &&
+               key !== 'image_text' && key !== 'xpos' &&
+               key !== 'ypos' && key !== 'width' &&
+               key !== 'height') {
+              tooltip.append( '<tr><td class="label explain-tooltip">' + key + '</td><td class="label explain-tooltip-val">' + value + '</td></tr>' );
+            };
+          });
+
+          var zoomFactor = graphContainer.data('zoom-factor');
+
+          // Calculate co-ordinates for tooltip
+          var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft());
+          var toolTipY = ((currentYpos + pHEIGHT) * zoomFactor - graphContainer.scrollTop());
+
+          // Recalculate x.y if tooltip is going out of screen
+          if(graphContainer.width() < (toolTipX + toolTipContainer[0].clientWidth))
+            toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH*zoomFactor));
+          //if(document.children[0].clientHeight < (toolTipY + toolTipContainer[0].clientHeight))
+          if(graphContainer.height() < (toolTipY + toolTipContainer[0].clientHeight))
+            toolTipY -= (toolTipContainer[0].clientHeight + ((pHEIGHT/2)*zoomFactor));
+
+          toolTipX = toolTipX < 0 ? 0 : (toolTipX);
+          toolTipY = toolTipY < 0 ? 0 : (toolTipY);
+
+          // Show toolTip at respective x,y coordinates
+          toolTipContainer.css({'opacity': '0.8'});
+          toolTipContainer.css('left', toolTipX);
+          toolTipContainer.css( 'top', toolTipY);
+        });
+
+        // Remove tooltip when mouse is out from node's area
+        image.mouseout(function() {
+          toolTipContainer.empty();
+          toolTipContainer.css({'opacity': '0'});
+          toolTipContainer.css('left', 0);
+          toolTipContainer.css( 'top', 0);
+        });
+
+        // Draw text below the node
+        var label = g.g();
+        g.multitext(
+          currentXpos + (pWIDTH / 2),
+          currentYpos + pHEIGHT - TXT_ALLIGN,
+          this.get('image_text'),
+          150,
+          {"font-size": TXT_SIZE ,"text-anchor":"middle"}
+        );
+
+        // Draw Arrow to parent only its not the first node
+        if (!_.isUndefined(pYpos)) {
+            var startx = currentXpos + pWIDTH;
+            var starty = currentYpos + (pHEIGHT / 2);
+            var endx = pXpos - ARROW_WIDTH;
+            var endy = pYpos + (pHEIGHT / 2);
+            var start_cost = this.get("Startup Cost"),
+                total_cost = this.get("Total Cost");
+            var arrow_size = DEFAULT_ARROW_SIZE;
+            // Calculate arrow width according to cost of a particular plan
+            if(start_cost != undefined && total_cost != undefined) {
+              var arrow_size = Math.round(Math.log((start_cost+total_cost)/2 + start_cost));
+              arrow_size = arrow_size < 1 ? 1 : arrow_size > 10 ? 10 : arrow_size;
+            }
+
+
+            var arrow_view_box = [0, 0, 2*ARROW_WIDTH, 2*ARROW_HEIGHT];
+            var opts = {stroke: "#000000", strokeWidth: arrow_size + 1},
+                subplanOpts = {stroke: "#866486", strokeWidth: arrow_size + 1},
+                arrowOpts = {viewBox: arrow_view_box.join(" ")};
+
+            // Draw an arrow from current node to its parent
+            this.drawPolyLine(
+              g, startx, starty, endx, endy,
+              isSubPlan ? subplanOpts : opts, arrowOpts
+            );
+        }
+
+        var plans = this.get('Plans');
+
+        // Draw nodes for current plan's children
+        _.each(plans, function(p) {
+            p.draw(s, xpos, ypos, currentXpos, currentYpos, graphContainer, toolTipContainer);
+        });
+    }
+});
+
+// Main backbone model to store JSON object
+var MainPlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plan": undefined,
+        xpos: 0,
+        ypos: 0,
+    },
+    initialize: function() {
+        this.set("Plan", new PlanModel());
+    },
+
+    // Parse the JSON data and fetch its children plans
+    parse: function(data) {
+        if (data && 'Plan' in data) {
+           var plan = this.get("Plan");
+           plan.set(
+             plan.parse(
+               _.extend(
+                 data['Plan'], {
+                   xpos: 0,
+                   ypos: 0
+                 })));
+
+           data['xpos'] = 0;
+           data['ypos'] = 0;
+           data['width'] = plan.get('width') + (xMargin * 2);
+           data['height'] = plan.get('height') + (yMargin * 2);
+
+           delete data['Plan'];
+        }
+
+      return data;
+    },
+    toJSON: function() {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (res.Plan) {
+        res.Plan = res.Plan.toJSON();
+      }
+
+      return res;
+    },
+    draw: function(s, xpos, ypos, graphContainer, toolTipContainer) {
+        var g = s.g();
+
+        //draw the border
+        g.rect(
+	        0, 0, this.get('width') - 10, this.get('height') - 10, 5
+	    ).attr({
+            stroke: '#FFEBCD', 'strokeWidth': 1.2,
+            fill: '#FFF8DC', fillOpacity: 0.5
+        });
+
+        //Fetch total width, height
+        TOTAL_WIDTH = this.get('width');
+        TOTAL_HEIGHT = this.get('height');
+        var plan = this.get('Plan');
+
+        //Draw explain graph
+        plan.draw(g, xpos, ypos, undefined, undefined, graphContainer, toolTipContainer);
+    }
+});
+
+// Parse and draw full graphical explain
+_.extend(
+    pgExplain, {
+        // Assumption container is a jQuery object
+        DrawJSONPlan: function(container, plan) {
+          var my_plans = [];
+          container.empty();
+          var curr_zoom_factor = 1.0;
+
+          var zoomArea =$('<div></div>', {
+                class: 'pg-explain-zoom-area btn-group',
+                role: 'group'
+                }).appendTo(container),
+              zoomInBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom in'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-search-plus'
+                  })),
+              zoomToNormal = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom to original'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-arrows-alt'
+                  }))
+              zoomOutBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom out'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>', {
+                    class: 'fa fa-search-minus'
+                  }));
+
+          // Main div to be drawn all images on
+          var planDiv = $('<div></div>',
+                           {class: "pgadmin-explain-container"}
+                         ).appendTo(container),
+              // Div to draw tool-tip on
+              toolTip = $('<div></div>',
+                           {id: "toolTip",
+                           class: "pgadmin-explain-tooltip"
+                           }
+                         ).appendTo(container);
+          toolTip.empty();
+          planDiv.data('zoom-factor', curr_zoom_factor);
+
+          var w = 0, h = 0,
+              x = xMargin, h = yMargin;
+
+          _.each(plan, function(p) {
+            var main_plan = new MainPlanModel();
+
+            // Parse JSON data to backbone model
+            main_plan.set(main_plan.parse(p));
+            w = main_plan.get('width');
+            h = main_plan.get('height');
+
+            var s = Snap(w, h),
+                $svg = $(s.node).detach();
+            planDiv.append($svg);
+
+            main_plan.draw(s, w - xMargin, yMargin, planDiv, toolTip);
+
+            var initPanelWidth = planDiv.width(),
+                initPanelHeight = planDiv.height();
+
+             /*
+              * Scale graph in case its width is bigger than panel width
+              * in which the graph is displayed
+              */
+            if(initPanelWidth < w) {
+              var width_ratio = initPanelWidth / w;
+
+              curr_zoom_factor = width_ratio;
+              curr_zoom_factor = curr_zoom_factor < MIN_ZOOM_FACTOR ? MIN_ZOOM_FACTOR : curr_zoom_factor;
+              curr_zoom_factor = curr_zoom_factor > INIT_ZOOM_FACTOR ? INIT_ZOOM_FACTOR : curr_zoom_factor;
+
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+            }
+
+            zoomInBtn.on('click', function(e){
+              curr_zoom_factor = ((curr_zoom_factor + ZOOM_RATIO) > MAX_ZOOM_FACTOR) ? MAX_ZOOM_FACTOR : (curr_zoom_factor + ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomInBtn.blur();
+            });
+
+            zoomOutBtn.on('click', function(e) {
+              curr_zoom_factor = ((curr_zoom_factor - ZOOM_RATIO) < MIN_ZOOM_FACTOR) ? MIN_ZOOM_FACTOR : (curr_zoom_factor - ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomOutBtn.blur();
+            });
+
+            zoomToNormal.on('click', function(e) {
+              curr_zoom_factor = INIT_ZOOM_FACTOR;
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomToNormal.blur();
+            });
+          });
+        }
+    });
+
+    return pgExplain;
+});
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index 4e08baf..c748900 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -217,3 +217,9 @@
   background: #5B9CEF;
   color: white;
 }
+
+.sql-editor-explain {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index 3e9bc5c..6d777d5 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -1,9 +1,9 @@
 define(
-  ['jquery', 'underscore', 'alertify', 'pgadmin', 'backbone', 'backgrid', 'codemirror',
-   'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection', 'codemirror/addon/selection/active-line',
-   'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator', 'backgrid.filter',
-   'bootstrap', 'pgadmin.browser', 'wcdocker'],
-  function($, _, alertify, pgAdmin, Backbone, Backgrid, CodeMirror) {
+  ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', 'pgadmin.misc.explain',
+   'backbone', 'backgrid', 'codemirror', 'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
+   'codemirror/addon/selection/active-line', 'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator',
+   'backgrid.filter', 'bootstrap', 'pgadmin.browser', 'wcdocker'],
+  function($, _, S, alertify, pgAdmin, pgExplain, Backbone, Backgrid, CodeMirror) {
 
     // Some scripts do export their object in the window only.
     // Generally the one, which do no have AMD support.
@@ -160,6 +160,12 @@ define(
         "click #btn-auto-rollback": "on_auto_rollback",
         "click #btn-clear-history": "on_clear_history",
         "click .noclose": 'do_not_close_menu',
+        "click #btn-explain": "on_explain",
+        "click #btn-explain-analyze": "on_explain_analyze",
+        "click #btn-explain-verbose": "on_explain_verbose",
+        "click #btn-explain-costs": "on_explain_costs",
+        "click #btn-explain-buffers": "on_explain_buffers",
+        "click #btn-explain-timing": "on_explain_timing",
         "change .limit": "on_limit_change"
       },
 
@@ -218,10 +224,53 @@ define(
             '</button>',
             '<ul class="dropdown-menu dropdown-menu">',
               '<li>',
+                '<a id="btn-explain" href="#">',
+                  '<span>{{ _('Explain') }}</span>',
+                '</a>',
+              '</li>',
+              '<li>',
+                '<a id="btn-explain-analyze" href="#">',
+                    '<span>{{ _('Explain analyze') }}</span>',
+                '</a>',
+              '</li>',
+              '<li class="divider"></li>',
+              '<li class="dropdown-submenu dropdown-submenu">',
+                '<a href="#">{{ _('Explain Options') }}</a>',
+                '<ul class="dropdown-menu">',
+                  '<li>',
+                    '<a id="btn-explain-verbose" href="#" class="noclose">',
+                      '<i class="explain-verbose fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Verbose') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-costs" href="#" class="noclose">',
+                      '<i class="explain-costs fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Costs') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-buffers" href="#" class="noclose">',
+                      '<i class="explain-buffers fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Buffers') }} </span>',
+                    '</a>',
+                  '</li>',
+                  '<li>',
+                    '<a id="btn-explain-timing" href="#" class="noclose">',
+                      '<i class="explain-timing fa fa-check visibility-hidden" aria-hidden="true"></i>',
+                      '<span> {{ _('Timing') }} </span>',
+                    '</a>',
+                  '</li>',
+                '</ul>',
+              '</li>',
+              '<li class="divider"></li>',
+              '<li>',
                 '<a id="btn-auto-commit" href="#" class="noclose">',
                     '<i class="auto-commit fa fa-check" aria-hidden="true"></i>',
                     '<span> {{ _('Auto Commit') }} </span>',
                 '</a>',
+              '</li>',
+              '<li>',
                 '<a id="btn-auto-rollback" href="#" class="noclose">',
                     '<i class="auto-rollback fa fa-check visibility-hidden" aria-hidden="true"></i>',
                     '<span> {{ _('Auto Rollback') }} </span>',
@@ -378,7 +427,7 @@ define(
           height:'100%',
           isCloseable: false,
           isPrivate: true,
-          content: '<div class="sql-editor-explian"></div>'
+          content: '<div class="sql-editor-explain"></div>'
         })
 
         var messages = new pgAdmin.Browser.Panel({
@@ -770,6 +819,79 @@ define(
             self.handler
         );
       },
+
+      // Callback function for explain button click.
+      on_explain: function() {
+        var self = this;
+
+        // Trigger the explain signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain analyze button click.
+      on_explain_analyze: function() {
+        var self = this;
+
+        // Trigger the explain analyze signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-analyze',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "verbose" button click
+      on_explain_verbose: function() {
+        var self = this;
+
+        // Trigger the explain "verbose" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-verbose',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "costs" button click
+      on_explain_costs: function() {
+        var self = this;
+
+        // Trigger the explain "costs" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-costs',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "buffers" button click
+      on_explain_buffers: function() {
+        var self = this;
+
+        // Trigger the explain "buffers" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-buffers',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "timing" button click
+      on_explain_timing: function() {
+        var self = this;
+
+        // Trigger the explain "timing" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-timing',
+            self,
+            self.handler
+        );
+      },
+
       do_not_close_menu: function(ev) {
         ev.stopPropagation();
       }
@@ -834,6 +956,12 @@ define(
           self.on('pgadmin-sqleditor:button:download', self._download, self);
           self.on('pgadmin-sqleditor:button:auto_rollback', self._auto_rollback, self);
           self.on('pgadmin-sqleditor:button:auto_commit', self._auto_commit, self);
+          self.on('pgadmin-sqleditor:button:explain', self._explain, self);
+          self.on('pgadmin-sqleditor:button:explain-analyze', self._explain_analyze, self);
+          self.on('pgadmin-sqleditor:button:explain-verbose', self._explain_verbose, self);
+          self.on('pgadmin-sqleditor:button:explain-costs', self._explain_costs, self);
+          self.on('pgadmin-sqleditor:button:explain-buffers', self._explain_buffers, self);
+          self.on('pgadmin-sqleditor:button:explain-timing', self._explain_timing, self);
 
           if (self.is_query_tool) {
             self.gridView.query_tool_obj.refresh();
@@ -1083,14 +1211,28 @@ define(
 
           // Show message in message and history tab in case of query tool
           self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time);
-          self.update_msg_history(true, "", false);
+          //self.update_msg_history(true, "", false);
           var message = 'Total query runtime: ' + self.total_time + '\n' + self.rows_affected + ' rows retrieved.';
           $('.sql-editor-message').text(message);
 
-          // Add the data to the collection and render the grid.
-          self.collection.add(data.result, {parse: true});
-          self.gridView.render_grid(self.collection, self.columns);
-          self.gridView.data_output_panel.focus();
+          /* Add the data to the collection and render the grid.
+           * In case of Explain draw the graph on explain panel
+           * and add json formatted data to collection and render.
+           */
+          var explain_data_array = [];
+          if('QUERY PLAN' in data.result[0] && _.isObject(data.result[0]['QUERY PLAN'])) {
+              var explain_data = {'QUERY PLAN' : JSON.stringify(data.result[0]['QUERY PLAN'], null, 2)};
+              explain_data_array.push(explain_data);
+              self.gridView.explain_panel.focus();
+              pgExplain.DrawJSONPlan($('.sql-editor-explain'), data.result[0]['QUERY PLAN']);
+              self.collection.add(explain_data_array, {parse: true});
+              self.gridView.render_grid(self.collection, self.columns);
+          }
+          else {
+            self.collection.add(data.result, {parse: true});
+            self.gridView.render_grid(self.collection, self.columns);
+            self.gridView.data_output_panel.focus();
+          }
 
           // Hide the loading icon
           self.trigger('pgadmin-sqleditor:loading-icon:hide');
@@ -1816,16 +1958,11 @@ define(
 
         // This function will fetch the sql query from the text box
         // and execute the query.
-        _execute: function () {
+        _execute: function (explain_prefix) {
           var self = this,
               sql = '',
               history_msg = '';
 
-          self.trigger(
-            'pgadmin-sqleditor:loading-icon:show',
-            '{{ _('Initializing the query execution!') }}'
-          );
-
           /* If code is selected in the code mirror then execute
            * the selected part else execute the complete code.
            */
@@ -1835,6 +1972,17 @@ define(
           else
             sql = self.gridView.query_tool_obj.getValue();
 
+          // If it is an empty query, do nothing.
+          if (sql.length <= 0) return;
+
+          self.trigger(
+            'pgadmin-sqleditor:loading-icon:show',
+            '{{ _('Initializing the query execution!') }}'
+          );
+
+          if (explain_prefix != undefined)
+            sql = explain_prefix + ' ' + sql;
+
           self.query_start_time = new Date();
           self.query = sql;
           self.rows_affected = 0;
@@ -2153,6 +2301,66 @@ define(
               alertify.alert('Auto Commit Error', msg);
             }
           });
+        },
+
+        // This function will
+        _explain: function() {
+          var self = this;
+          var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          // No need to check for buffers and timing option value in explain
+          var explain_query = 'EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE %s, COSTS %s, BUFFERS OFF, TIMING OFF) ';
+          explain_query = S(explain_query).sprintf(verbose, costs).value();
+          self._execute(explain_query);
+        },
+
+        // This function will
+        _explain_analyze: function() {
+          var self = this;var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var buffers = $('.explain-buffers').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var timing = $('.explain-timing').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          var explain_query = 'Explain (FORMAT JSON, ANALYZE ON, VERBOSE %s, COSTS %s, BUFFERS %s, TIMING %s) ';
+          explain_query = S(explain_query).sprintf(verbose, costs, buffers, timing).value();
+          self._execute(explain_query);
+        },
+
+        // This function will toggle "verbose" option in explain
+        _explain_verbose: function() {
+          if ($('.explain-verbose').hasClass('visibility-hidden') === true)
+            $('.explain-verbose').removeClass('visibility-hidden');
+          else {
+            $('.explain-verbose').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "costs" option in explain
+        _explain_costs: function() {
+          if ($('.explain-costs').hasClass('visibility-hidden') === true)
+            $('.explain-costs').removeClass('visibility-hidden');
+          else {
+            $('.explain-costs').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "buffers" option in explain
+        _explain_buffers: function() {
+          if ($('.explain-buffers').hasClass('visibility-hidden') === true)
+            $('.explain-buffers').removeClass('visibility-hidden');
+          else {
+            $('.explain-buffers').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "timing" option in explain
+        _explain_timing: function() {
+          if ($('.explain-timing').hasClass('visibility-hidden') === true)
+            $('.explain-timing').removeClass('visibility-hidden');
+          else {
+            $('.explain-timing').addClass('visibility-hidden');
+          }
         }
       }
     );


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  2016-05-09 15:19     ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-09 18:26       ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
@ 2016-05-10 06:51         ` Sanket Mehta <[email protected]>
  2016-05-10 09:24           ` Re: PATCH: Graphincal explain integrated in sql editor Akshay Joshi <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Sanket Mehta @ 2016-05-10 06:51 UTC (permalink / raw)
  To: pgadmin-hackers

Hi,

As previous patch was not applicable to latest pgadmin4 source code, here
is the new patch accommodating latest code.
Please do review it and send comments.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Mon, May 9, 2016 at 11:56 PM, Sanket Mehta <[email protected]
> wrote:

> Hi,
>
> Please ignore previous patch as there was an error in it.
>
> Error:
> Tooltip was not getting disappear when user moves cursor out of image.
>
> I have attached a proper patch with this mail.
> Please consider it for testing.
>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Mon, May 9, 2016 at 8:49 PM, Sanket Mehta <
> [email protected]> wrote:
>
>> Hi,
>>
>> PFA revised patch according to Ashesh's comments.
>> Please find my response inline.
>>
>> I am currently adding minimap feature in graphical explain.
>> I will send a new patch for the same.
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>> On Mon, Apr 25, 2016 at 4:36 PM, Ashesh Vashi <
>> [email protected]> wrote:
>>
>>> Hi Sanket,
>>>
>>> Please find the review comments.
>>> - Please add the missing 'explain.css'.
>>>
>> Done
>>
>>> - The application should be smart enough to handle conflict in options.
>>>    i.e.
>>>    Buffer is not a valid options without EXPLAIN ANALYZE.
>>>
>> Done
>>
>>> - A statement having EXPLAIN keywords with different format should at
>>> least render the output in the data-grid.
>>>   i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
>>>
>> Done
>>
>>> - Please use the keywords used in the EXPLAIN statement in capital.
>>>
>> Done
>>
>>> - Explain should not work with empty string.
>>>
>> Done
>>
>>> - Font size in the tooltip is very small.
>>>
>> Done
>>
>>>
>>>
>> - Smoothing the zoom functionality.
>>>
>> Minimap will be added and zoom functionality will be removed. So it is
>> ignored.
>>
>> - Arrow marker is hardly visible.
>>>
>> Done.
>>
>>>
>>>
>>> --
>>>
>>> Thanks & Regards,
>>>
>>> Ashesh Vashi
>>> EnterpriseDB INDIA: Enterprise PostgreSQL Company
>>> <http://www.enterprisedb.com;
>>>
>>>
>>> *http://www.linkedin.com/in/asheshvashi*
>>> <http://www.linkedin.com/in/asheshvashi;
>>>
>>> On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> This patch includes the patch sent earlier for stand alone graphical
>>>> explain.
>>>>
>>>> And also "horizontal lines are not proper" bug is fixed in the same
>>>> which was reported by Dave in previous patch.
>>>>
>>>> Regards,
>>>> Sanket Mehta
>>>> Sr Software engineer
>>>> Enterprisedb
>>>>
>>>> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Team,
>>>>>
>>>>> PFA the first patch for graphical explain integrated in sql editor.
>>>>>
>>>>> Below are the few things which are different from previous patch which
>>>>> was sent for stand alone graphical explain.
>>>>>
>>>>>  -  Now user can select Explain/Explain Analyze with four optional
>>>>> properties (Verbose, costs, timing and buffers)
>>>>>
>>>>>  - Initially graph will be scale (according to only its width not
>>>>> height) to fit to screen so no blank space will be there in case of very
>>>>> large graph.
>>>>>
>>>>> - Along with zoom in/out button, "zoom to original" button is also
>>>>> provided, by clicking on which graph will be scale to its original size
>>>>> (not same as initial one which is according to screen size).
>>>>>
>>>>> Please do review this patch and let me know in case you have any
>>>>> comments.
>>>>>
>>>>>
>>>>> Regards,
>>>>> Sanket Mehta
>>>>> Sr Software engineer
>>>>> Enterprisedb
>>>>>
>>>>
>>>>
>>>
>>
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/x-patch] integrated_graphical_explainV5.patch (483.8K, 3-integrated_graphical_explainV5.patch)
  download | inline diff:
diff --git a/libraries.txt b/libraries.txt
index 9fcf755..ad1c004 100644
--- a/libraries.txt
+++ b/libraries.txt
@@ -27,3 +27,4 @@ backgrid-filter     01b2b21     MIT          https://github.com/wyuenho/backgrid
 backbone.paginator  2.0.3       MIT          http://github.com/backbone-paginator/backbone.paginator
 backgrid-paginator  03632df     MIT          https://github.com/wyuenho/backgrid-paginator
 backgrid-select-all 1a00053     MIT          https://github.com/wyuenho/backgrid-select-all
+Snap.svg            0.4.1       APACHE       http://snapsvg.io/
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index f461cbe..33c693e 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -6,28 +6,59 @@
 # This software is released under the PostgreSQL Licence
 #
 ##########################################################################
-
 """A blueprint module providing utility functions for the application."""
 
-import datetime
-from flask import session, current_app
+from flask import url_for, render_template
 from pgadmin.utils import PgAdminModule
 import pgadmin.utils.driver as driver
+import config
 
 MODULE_NAME = 'misc'
 
-# Initialise the module
-blueprint = PgAdminModule(MODULE_NAME, __name__,
-                          url_prefix='')
+class MiscModule(PgAdminModule):
 
-##########################################################################
-# A special URL used to "ping" the server
-##########################################################################
+    def get_own_javascripts(self):
+        scripts = [{
+                'name': 'pgadmin.misc.explain',
+                'path': url_for('misc.index') + 'explain/explain',
+                'preloaded': False
+                },{
+                'name': 'snap.svg',
+                'path': url_for(
+                    'misc.static', filename='explain/js/' + (
+                        'snap.svg' if  config.DEBUG else 'snap.svg-min'
+                        )),
+                'preloaded': False
+                 }]
+        return scripts
+
+    def get_own_stylesheets(self):
+        stylesheets = []
+        stylesheets.append(url_for('misc.static', filename='explain/css/explain.css'))
+        return stylesheets
 
+ # Initialise the module
+blueprint = MiscModule(MODULE_NAME, __name__, static_url_path="/static")
+
+ ##########################################################################
+ # A special URL used to "ping" the server
+ ##########################################################################
+
[email protected]("/")
+def index():
+    return ''
 
 @blueprint.route("/ping", methods=('get', 'post'))
 def ping():
-    """Generate a "PING" response to indicate that the server is alive."""
-    driver.ping()
+     driver.ping()
+
+def demo():
+    return render_template('demo_explain.html')
+
[email protected]("/explain/explain.js")
+def explain_js():
+    return render_template("explain/js/explain.js")
 
-    return "PING"
[email protected]("/sample")
+def sample():
+    return render_template('sample.html')
diff --git a/web/pgadmin/misc/static/explain/css/explain.css b/web/pgadmin/misc/static/explain/css/explain.css
new file mode 100644
index 0000000..49a4bc4
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/css/explain.css
@@ -0,0 +1,56 @@
+.pg-explain-zoom-area {
+    position: absolute;
+    top: 5px;
+    left: 5px;
+    opacity: 0.5;
+    cursor: pointer;
+}
+
+.pg-explain-zoom-btn {
+    top: 5px;
+    min-width: 25px;
+    cursor: pointer;
+    border: 1px solid transparent;
+}
+
+.pg-explain-zoom-area:hover {
+   opacity: 1;
+}
+
+.explain-tooltip {
+  display: table-cell;
+  text-align: left;
+  white-space: nowrap;
+  line-height: 10px !important;
+  padding: 2px !important;
+  font-size: small;
+}
+
+td.explain-tooltip-val {
+  display: table-cell;
+  text-align: left;
+  white-space: pre-wrap;
+  font-size: smaller;
+}
+
+.pgadmin-explain-tooltip {
+  position: absolute;
+  padding:5px;
+  border: 1px solid white;
+  opacity:0;
+  color: cornsilk;
+  background-color: #010125;
+}
+
+.pgadmin-tooltip-table {
+  border-collapse: collapse;
+  border-spacing: 1px;
+  top: auto;
+  left: auto;
+}
+
+.pgadmin-explain-container {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
\ No newline at end of file
diff --git a/web/pgadmin/misc/static/explain/img/ex_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bfe75d909f5092c4d3ba8978c75fbc57d7f606d
GIT binary patch
literal 574
zcmV-E0>S->P)<h;3K|Lk000e1NJLTq001%o001%w1^@s69zTe&00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10lP^=K~!jg?U_w#!!Qs=pNrR76nc`JAiEyfyPUu$F?5lg!9`tE
zrVdorjI6O#*B=N1Q4~GTk7uODImZ$7QhEcqbb{2T!+^AsNlnvqz}0v!OZCpVcg+u)
zSl03oH-ylcGy!)Fj09u=UN>$mMIX+&H|b<ajP!gzp{f<N2t38e1(}OYz(X)^Z9SDm
zaL$Pb&;cXx85twc3D+9}YYwWtX(n61tgLAZVhA&A0ZDox`m}f_o&;Lp=3^|TZAm4?
zB8D-uw2Hk&77xL~GD+H8Yh{L+-D~onRU64N$mC{z9Z`Z<4$%uyDn(tUuBBqiTE>@*
zne6>YDVVIT^|Y|u&2%+YKxQ4H!ZKN8+Uk0kwJKPjW&<(>@$PjAe4RCOnSn%Nr0(=P
zYi|fJ04V_hnL$cH0K3&%;sz_V=BgQD)cm$)2vvhs6@*_isdrBfc8kD{yg=7gktITF
z+PGFu2!0OeLWgu>5LFp3D9xourL!bQu%a?wd{rRqFIvi++{=Q!&>aaV%KTa{dS;2c
z$5o3IhEOTyT37x61pK30-JX4KbAS7Pk<5;R_SRus>jbGyCrEAj0!#X?PWurOy8r+H
M07*qoM6N<$f>VU-$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_append.png b/web/pgadmin/misc/static/explain/img/ex_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..017a2068b735f13cb89a3bcb2832e5880d17df56
GIT binary patch
literal 1162
zcmV;51a<p~P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(LcQrnzUg(&?|IPgRl@0p)bM7=>}tvEZ_4YI+3|bM?Sj$mrrz?7)b5hj
z@2}zW1*hRn!RZ^W<6Fh+V#n(^x8+d2=WEC4a+aaNk2du1$nfm9@9MSh>9q3g#qH&>
z?c}iR<FN7S!|>?B>));G-mL1~tMBB$>*k;D>$d6On&{t|?&h=S+m`0ol<eWI<I<1f
z(2eQZsp{jL=-rs*){^1Oi|N^?<kgYn(~#oMjOf><@94Ab;k?JHb@1lE=hUR;)1&O&
zx!}r)-o=I9!-U<zg67eo-^hpM&!OhgqTIiL<j$Y(=D_CCqwCqW<jtPr%%0=Rp4+{D
z+PZy)sbo@(8Kui)sL*Ju)oiZXakAicwdHz}mC<Hv(aX&7yuR$dzwOS>^3l=q!NTst
z#P8G8^VQY#$jI=?$??j|@!8t--ro1u*Y)Ay_q@IAr>W+ss^`DI?yaxsud(UH#qY<+
z@3pq;x47%Lx$M-`^vB2WiI(>{00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0zpYcK~zY`?U3tJ5>Xh&?SzGeqJ$LLD=fGO%SB*ek((>YO)i2W3vOk#CSYi4
znRfSIZ+9mfuyZIJXZldjhj-?kd4K0Q&oeUeU#e~(MZ}3i&`phI^cK3U)oRDk*qyt&
zaWp=m*H5C!DTCo2!Xg@@(Kw2xO(xTQ^uTO3>(InQtyYhwW@dB-G{|N8&s`p=e<n^G
z0<#v2W%goPDar;m`yB0nd8dnU0~WD(JRXZWy+HYV3x2Q%f<YFXp-`9`j6@<<8Ch7g
z109S;oxp^{vG^dw8;L|H@Gk}eG_cV`k^ych7US{Aj}#QOtfZjT6pWn09q0KJ7I_Sc
zh!gMP^;&1a=J(qj;9yzj3b;8go`O_5oyp=qCa3T%U!+JRLvo5(EXPASzgj5bk-lP+
zO0n@=u9Sw%YN1djA(xBgOQn1U)(VwM6_42b_BpvF*6CW8Q^eI2nT;%D%hhV_+8T4v
zEISV?48yr0#q(+T{k3Ab2DQz)fOi2p8cn$56iaG~e0~Fpl}e)u^=7jv;1M>FxKwPp
z(r9dgKt_How%TaO#{-achU4Ux__UIuSXNThl@v8W5U#DUt7}W#B5ow&NzYaPMkJm-
z`+3#B5v1H~KNqnZ(M8-Adt7=qvQOWu;_p1vqZcA^BYthzl84euNfB}45NYSt?ruwJ
zclP#POWpm0H;2+;tKB}5j=J6Bw-Oe4cXIOnRO+0aefTJS`uyeVj?_BsblTFl^ZkS4
zzljW=<qD1clll(R-{b2g;$(6F001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@z
zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUh
cIxsdXFf}?bFv^!ztN;K207*qoM6N<$g1#AbUjP6A

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_and.png b/web/pgadmin/misc/static/explain/img/ex_bmp_and.png
new file mode 100644
index 0000000000000000000000000000000000000000..64d5869dcfcf9d94d031fa068cff93ea929180b2
GIT binary patch
literal 1006
zcmV<K0}=d*P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004)P)t-smD2kF
z003rd(R6flb#--ic6N7ncY1nyHi*G{dwYC*e13j@fPjF4f`WsCgM@^HhlhuVh=_}e
zi;azqj*gCGmC%omkC2d%k&%&&j=z$UlEIHQm6er`lHGcqshOFXoSdAVo}QbW#Gjv^
zrlzK+r>D86a;T`NPQK>5s&lKWtGldpyRCMut*u(Y=dP}<ys&q#udltad8@4GzOs6W
zx!ba`vSh{Rx3{;mwCZuo?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?Zd;v-NJ*w!S2S!
z#*5VM-^PaD$cK{E@5aaP;L3^0%F2}3@Z-y#<IJAq&7R`WjpWXs%+2x5&hq8Zq2|$|
z)6>(Y-ty(vljhW<)z#JK)upZ9^VHPy=+~y`*Qe^(w&~ia>DsB<+S=*ds_NXT>)W{8
z+}z#W-Rs`0?A^KE-rnrst?b~g?cclJ-uCR`ui@e0?BlTF;^Ob*zvboS@aDkq=fUUa
z=jrL`@$1Cu>gw?D@bU5S^78WY^Yiuf_4fAm`1ttw`T70*{r~^}C9&%N00001bW%=J
z06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0q;pfK~zY`?UY$l6G0S(b8}-Di3kcP
z+Oo)^h$4bwltoqvL?DD{Mjf_nBqD~O2?A~ymixDzo*9a*Gm})MC?7Zv{c`K8Gv{>Q
zvDq$ko~o9(s?QF(wL!N4k*52GJqwax@WJEtiUu~Rr}eShD?&VOcaK*pj!Vugb=sg#
zfY{%@e)DeKes;iqrqk&yCPD_D!r-C^Nk4<GWMRlx_z0E==`xVDE_fnFj<OJC>PwuJ
zQ#!=9lF84TBBa*NRjVOi9LP1IA^nW2-@@fKw*1L<;8opaGaip`h_l>+MlB1G6SG9S
z=+u$;BX}4UBk&Ro<O=>E`h+(O1ZE*><<f@Dv{HsEI<ou#?nSH``|ZN(SURV-%r>Ht
zNXoo1qP+&hYdgl><kJ{stMHBkrzooyKsJ^Ng_cqlS=#YAjp3zO3@bPinuH0(v@xPG
z-th~(T!n2MS(<=x#ngpg%P%#>ef9pot6A9mcrN5H3(-xi$?R{pAeQfQB&8_Is%c|H
zG5v{QDc(Jx{2HTgO)iJ4;r-tV>{PR?%CelW*cUn`^~2;*7z$c$rLkbz!Q>%$6)bF#
zgDMhW1^r<X!9XBkv6Uy4*H)f(3HbeC^EY-H@%KXSjQLiI5MN;~GdYo*S;9V_FI=R?
cF7zMiA0?@qIju>9W&i*H07*qoM6N<$f<qfLO8@`>

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png b/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png
new file mode 100644
index 0000000000000000000000000000000000000000..2657d8c39328bfc80751c60db9d3ab4341c0de35
GIT binary patch
literal 1106
zcmV-Y1g-mtP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s00000
z003rd(Nc{WHi*Gol_FY@Wn`8)38~@|tKwvp&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPGu5^~NHbTAWN5AP#zUE86=}y7vTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q^_*?AyJGyuIvu(eH!N?S#?pz`^c>
z)9|d;ZHv_Ik=5>!)$gv_amL5*l-Tf>+3~XAcCFUw%+2wj+wtexE9lxP&Cc@b+bZna
zD(>4V@7yZ!+$!?iD)ihcx!dfw=Y00vDy`r1`Q0h{-6_=6^RD3Y``#)1-YNdxDZbwA
zx$A$x-|oWT?z`@S?c-qY<6p($@9^Ybyzqqb<X`jTU&rI{$m8(y<zMvWU&`a~_2pmp
z<zM*aU(4k2-rn~3=3n~eU(Mz6{N`W&=3mk0^3&+^^6H1w>GRa-^z`e8*6H;2>xcO3
zhu7-$`s|1O?1ujAhT-q`mEcQQ00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94
zoEQKA0&7V`K~zY`?Ud_N(?Ar(NfQ+@uBZsUQR*A(3k7^uiA_xlwb7<XX$T2Wl46zw
z3Z<omf1TZggd}Y;GR`=D;C$H3?B=&;&b@cGr{_ffM12%iU&sC(Yu~pzN7RRBuO_}z
z9SHSg=<4;C)SHp}k3Ldg#wI3zy8lp*uid=+whJ=U+k5r~HGFAc;3+jWJaTW6`U{t^
zEElbP-|8I2KEHnd^>^&(-vmu3&<E7Y<#G+wL{Y4Rx+F<;;36;16PJH@5PE-~#z=W;
ziYWt;VNw#1SeBJN2=S2cA-lUa!Z3^o#8j#a5_H+w@gP!)Wi2G_L4sKv#G9fn%W}g-
z`eRt9$y)*BIl%I*K9?(GoOF@xZQI4ZUy^0CDl3(yC(CW(0vV5!j_A!z8h-}~mT*$6
za2%)l5JX@-7#$sd_le%vpcj!ymO#PfbULko3dd#C2p5DnE;2^GRe;K6G8zcaf)Jd=
zPSH>*C`D7%v}T}UXUKCdvcCQ&74!AQMsP2b2D)EWk&C8PTM^wqM7$}qY%Zrq%-GtR
zg(zuUST%!@YA%<95iYB%7Gn28&1ADxp!<=IEX&Il;!V+l5Vj&_Y-#D(Gq$jB=z@FE
z)OFo<X@_?I6h_E^1__=pLT79oBQrC<=I0lWMDj$zh?ucbsf$a&>(Y@0AyyV$hfa#N
zVHoX*8Jn9s_Kewxb3cRzb}`Mpu<oOWZ+aR(egNk4c?ck1K*3ExD4Jn2MsLj~le1g2
zh42s<1wlYavFJkrE~R9$WV`UZ0SrM9%pelTlE5>D9%WyOJ=2@Tu2_G^GagZ~6a9ZW
Y0Mz-{B6#`Cr~m)}07*qoM6N<$f){{JGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_index.png b/web/pgadmin/misc/static/explain/img/ex_bmp_index.png
new file mode 100644
index 0000000000000000000000000000000000000000..23b9733b56792533e4db25ca9890a0a0140c6ff9
GIT binary patch
literal 1172
zcmV;F1Z(?=P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004@P)t-s000{R
z003rd(Nc{WHi*Gol_FY@Wn`8)45{LDla^$a&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPPu{MmZbjzYxL%irozv)iC=1#%sTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q|V#etORA+r5d~zJR>F?0eDg-NAx`
z(d~rM?%l(Kz`^c>)9|d;ZQjL&i`4Gl$A^*C?%>FXlGX37+HuCm@8HUbl-Tg$%!-%U
z@#4;m<IJA2;C8Ln>CDaX<<6kv(T?ZZE6vXG=Fy>}-SXtrk>=B+^4u!t)TFuF?6>E9
zt>5$1)bp<3^XA!==+~zI-YMzWr@r3px$A%D+?T-L?&;d7!r<=c-k9s!x9Z%g?c-qY
z<6p($@9N#Gyzqqc<X`FGo9x}X$K&wG<M7Jk@a*8N%jEIi-uC(CU(Mz6?BlTi=3nmO
zz3t_((dY8+=Caf1^Y7)q^6H1w>GRa-^w#P0@aMtr>9p7C_3`P${_KYG?Z)Bn_g>Mo
zbpQYW0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_00N9jL_t(Y$L*BsQxZ`a
zhh26nvhB^vO1oLo-JmFv5|N5XBA|_nRxB8D7XcMR7F@v#0{+$QVK1_a?Bq1l^uhPT
z*_pHZ%x|7^ezOAuC-YBckU%rovwx?vFI&Y|#D{0EroRzA2=QcObo3?hX8iu6kHpu>
z>6stWH^k%XH}AeZ0vXY2wKs^dOT)uYiOI3?do#per1Wz++u7&Wi~K6S(tLjX{>v}T
z;kSB{)N>E0r_<>=v>S~^8`><(wn0K(oX0MI??T9f0}>%=uh*M~Mn0c!0Gmiet6d28
z5R)PM`wD~wHX4nVRZ@0$Wk@2yLNyu+bs<U@5fNNE7R_?GyeA<;8Z@WzTMFbFpyAi&
z{3=ViitBI*+1Zh$RI5#B7K_EbE|=Tb1ze}Y#UZ!0Nc6mdd9k!$QS|wISsB6+XdX;V
zOuhR=Zf=sx+~8h})31g?q2dvUJcUEVlnj&w#N$ape-{qnT{4-vU{TAaQZ>biox#sZ
z$i~K>oS5uhcm(RXT&@m##cZ|)wNxxtQMr8q$pr#|oL~`iI2^P}$JW;+qy$+HkJ#<B
zO3K=rgfNiu%+AIjVz=9ZDji#0?I5jEsnmj63|UlYnl7kxY-Q!x9a~xwQW&NMl}LoH
zKp`0P7y91*DTMI1AI2EY!p2zyEfzD?w_{TXnV<i;jEAu>1GLR%4T9fnRv_|@#p7Km
zwAE@sh{;5$bc{nfE(~(vEeaGxB~?1MxOnW2@ran>r>FuX-EMcX-|cfhzPUN{^8+Rv
z=Ja_Bx6xp5_UjnAz2I^!Y?C5FnM@`(xD9edkrH>g;)f}e$!P3B6fSzyF}>u%TO^%M
mr}D&xdVb?7Cw4Ob-~0w&gyX$CR{j?N0000<MNUMnLSTYde~VB6

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_or.png b/web/pgadmin/misc/static/explain/img/ex_bmp_or.png
new file mode 100644
index 0000000000000000000000000000000000000000..c22fc31eee32e53abf634278f9d57751181f15c0
GIT binary patch
literal 685
zcmV;e0#f~nP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700030P)t-s00000
z003B6SY~U{Hi*GwmC%ijzrl|-kCNSbo~fFenwy-&xu<eYzUI5CbGxi`yRCLw!RNfN
zcfGNBtE}k0vU-WR+hoP)v$X2Ey1H@8?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?cKtI
zz`^c|)b8KLhTq7ClGX3V$M4|EiImvz<IA4o%%0@Up5oAr<j$YW&GF67^3l=J<<Oz#
z(W0i_^5xc(=G3I;)upZ9^VHPy=+~y`*Qe^(w&~ia>DsC3+^Xu_s_Wah>)x#F-MQ@G
zt?b~g?cclJ-uCR`uk7Qn@8rMa<mB+?!0_k6@$1Cw?CkIF@AUNa|NsAqxAFG?0004W
zQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkPM@d9MR7l6|)NN0KU=#*mj$o+0
zXGv;SRwxw`6%>^SK@x>8z5V~+lCW40^I>$hY<=+l#CCS=?A!_rGtXZp&xOfP4=T~1
zLLuDg&VhK#Q3h9{B+&*8S6f~eBpML~p(b&^vn6@U$0T2m#b{8Z5cd4&_~MEE7O~-9
zf*=_4G_tn|`*$&UE0u;Z3AUi@Ws_kpcNvpsxCSJ7EW-w!ByJ(e*z+DnG*V#06sAdo
z57R(x899zKpx?3pi_}}3HCVOj1h#=r;0$csmirZ0vT%(JY|HXz-k5KiT_1Ogc>-+%
z*I2g=Ed#gZrj<t0Z!rv`Kl8@=x~{vp_eDR1riLU<*hLa;LR4I1uBNJPc4P0=>MO1>
z^3%t=s-pBVfBn$JPrOoxdMEQgMkXTi54I4blS&e|kfbNeaxb$nGU<)Y^N;cg@O4qi
TF88uo00000NkvXXu0mjfLbGwY

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png b/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..e99f57478842f61cf0582433b659d37f0c532d5c
GIT binary patch
literal 334
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_@3?!2S6O@1yXMj(LE06{PXIHn!e64$cEXI-`
zzhDN3XE)M-9L@rd$YLPv0mg18v+aP48c!F;5RLO&CtC9zP~dS+JaMn}cL58V&ewnH
znS#1o7niJ>^>*TX*`QzgMdD7POzPaTo+w<9uwpLL)1F%W#!IqdhoMSxhwp~0@cf02
zhkMjmROfZc`EZ)w`S;|i<*e@Qr8gQ^@9R11|6Rc8|AQ9IRLjSmUOFeWtM)%%ZozPh
zRpl$&!*g?h?ocgpjVMV;EJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0
rRpn4L<mRVjrd2{T7+8WefK*!<m_an0njX3asDZ)L)z4*}Q$iB}@40B!

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_cte_scan.png b/web/pgadmin/misc/static/explain/img/ex_cte_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e1d779372f7fd212d2096e7701b0f1af05a5297
GIT binary patch
literal 1955
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_delete.png b/web/pgadmin/misc/static/explain/img/ex_delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca051cd5d01ee2f3ac17779b362999d82a9285b5
GIT binary patch
literal 1129
zcmV-v1eW`WP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003^P)t-sKLD^>
zTU+Qs2lh=8Hi*I5b0^MyED5RN(19;<mZ6f8lEIHQ*N;guwdHlD$Lf$-LA>b8qFCae
zX-dH9$EtPXqi$Wq>SM<0Ysctl$n0**>~+oT+Pi++y?@)iiQK+`+`oW((eK>Bf`rlT
z-NS=~)9~KJgx<x4-o}QE)b8KMhu_GEk=5?t$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc
z&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+mlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#
zr{>z0>DZ^~*{J8-m+9K6>e;sG+p6o@x9HxP>fNgB+_>rBn(N-I?A^NS->vN4y6oVs
z>f@a4-@EMLuI=Ev>g1j5;;-%DyzJw!?d7rW<i73ZvhL=y@8!Vo=D_dhv+wD(@aV$u
z>B8{rw(;x4^6ka*?Z)%)$Yy(c-~a#s0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkV
znw%H_00Om1L_t(Y$L*8bdl^v_#f8bG-PLL%nhMc~q#`mQ2qBD7)FomPgc62MQMdg6
zk3DnFFeXNthkjrCko~Zp{O0Vn_M*|e%s-g7lsHRO4WY~igJFl-+ccRDsDn>t^C@+@
zx4(Z*ogcZ~Uny0@u)e--pf-`3sE>NR-c0Qve4jen-Zs0bzx2v0yz^33L*QXJ96n67
z-|vrMEC|8~Mm*0Cfcyr)IFAhP?`wTY`?t5ZJrIhyTpRdgGF}BjOnw=S3Z#aS8bOKz
z2)SI2hh{Q9MZQla-3-V`zlMK|wO1@6q@DYg<e?sq#VQh#$r!5>kiFpjXD)tV7tH|-
zs+Ce#@?tf9p@%c(J&3b~b$3yv?^<i0Wd?NOY>az?FosdP&5vMEuU5Oz<v6Z0{jh=o
zomQ*WZOAT~qKnHCa6!L6FYiCcNQh7@l3<f{sKzcYIN<z#Z+<Q&%wTg<1P9CGU@I5F
zAO{7nm(GGl$rxFfd0BMP$!D`Im_IsL#iN4e&4xICNNma=3fVzdXx0akb}J=xi%ub*
z??5Y^PBlQ;M@VO0uV|$Gl`d%HB7Jh~V&PGT0ag%FkH=9lh;qT8zW{lfrqhr*b~zs-
z5-3Jml9U9SrB6;$kZ?Mkj5>n$=YC2mx@aL6sYC-BEW?yl5z?^_b4}Y?mYDns#}ztj
z_&TfPMvVl^-a$DU3E!wMhWB5?9atqzKrRj$h&b%_kUD~kp+H#Yp(6@myWO?~!I3Hu
z=Fs5?L&$Ek1=NvFHk)oKyp$u6@HJeA0)gu%65GN}d$-s()mK`n1iAvzJBEpt;U*Lc
zp;rPv-<A4eq!>gQ2X<i-UHB{(%b7ZoaEZkr8pXlNh({e^(G{YSM(iRIu-Pm=I9ra~
vF;TQX(RmkF9*^hhiNtoF^RIlF|7m^$H0#Fus?kFU00000NkvXXu0mjfhwW63

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png b/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..acba49c4fbfd9b871444c9e8d528deda3a37dda4
GIT binary patch
literal 1607
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
FV1m**ONRge

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_gather_motion.png b/web/pgadmin/misc/static/explain/img/ex_gather_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_group.png b/web/pgadmin/misc/static/explain/img/ex_group.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d5de31bc129a9abcf4a73e19a48739139f66271
GIT binary patch
literal 1228
zcmV;-1T*`IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=(EA5f2CVejTwfiWOA0Fgt11U#9qOVHuUhw@a(tm>9z9h#qH&>
z?c}iR<FN7S!|>?B?BK2I-mL1~tMBB$bEG!w=AY@{n&{q`?&h=R*_GtekmAse>DsC4
z<DBHxlHtsY>Di~^&Wz~SrlQAS@94Ab;k>5IWyh*@@aDkh)THLqqwL+ecBwj^wTIx!
ziQdJ9-ou35!Gh+|q2I`d<<Fq3)@|IrfaJ}di@s3r=D_ROwzcJZ<IJAp%bvUMgW9`&
zd$dE9%UpG!OlO!lj)kjcYthWj@w~q5zQ66y&+^gH^1{OI!^Q8@)brKV^vKBY$;t7`
z%kkOT_1@n1*Vpyo;rG0~?5C;bsH*3`zwW@o?yaxsud(UH#qY<+@3pq;x47%Lx$Mi#
z@y^cjz`^d+)bz*4@RF6$yGLoX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0)t6JK~zY`?UZR(+CUVB(*d`NQfXa6^~z!}1Oy^sF^V-vQ6PniB6gWpu~n<K
z*4lmj>zxTpNrZFE={Y_9;C#4z;o;8n<_!#Ueg5^if#~TvJ0ZwowOV^nZ(o1^02;Vy
zu?(WYUYl(Q4Gr7vw@^pKIx;e1MSTv3<2Jf8Iy!1WJp{L*;juA`9sTu|@d=i7GIzB*
zcbx`ea%zfYVA|z!O*g08?e>7jJHz>4miG$*&>@5fk}cqxo11erraKf0N5CtJA`kxg
zXjIZ8>{QT6K3YgD<|aru>;+DY$NeBgqlu&uVVO20Mv+K3EONld<MRSY3yFkmKo;vr
z%VLRIaMZ#t5Q{)kmQw~~SwrptJzZxz;S5eXn_Jw|;E6=MKJf88zXAbCN~R#CDCt$u
z5zCqTb(dHyv_vdKafVpTQ;S3*D}%DOp38&oS{ZE#LxT<JgH2|$1qIUUxtyxol6lbB
z@+Vln5v*J&tfe8J%N0vHeP`xj-Nh5}&d_Jg`!|S1QnN@Ci&a%El?{n)noX>UfRE$&
zSy&MSDF7*1R><$?^J*E&rLBr_%XS^%;`MqgXclXlMUhx+RjbvC5!ul?)~H8Yuxhbb
zEkWfnGVy48hc@|gu%#mXatCCbFywBqAxIKQNmeuqm0E03i*lu6bQyolvg{M)DW<3E
zG`wPq%cPn3+4J4qJzU%0fAJC@93H(o#>acL+6g{6I5>Tc4doBgcOSnwJ9~SM-(6h1
z|A0S!`uyc9{&xO-zlMMOJUluzB3ffiAkFe$9ch*S>MpJF|I(7_Iy;r$L}JbIn@VhR
z$7Es@G5cI-#kS2oefq~wwp=~2>+`>z-(ue+J2o$;LI3~&C3HntbYx+4WjbSWWnpw>
z05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppn
qF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTX@`Go)g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash.png b/web/pgadmin/misc/static/explain/img/ex_hash.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f35c76538c3a41920a7b93b94bdf97443df7aa7
GIT binary patch
literal 1169
zcmV;C1aA9@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001n
zsbo@(8Fi+|Hi*HY#9nXB?oYw#V#exs&h4h&@<F}n38~^Nv*by<=84npY=q5bYthWj
z@xa0E#>el?&hp;g_M^vPyuIzKtmw0}>eSTprp#qVzv+I_?y1pfi`DRv*YI-8?0nAc
zpxg0|)b5kj@2}wVOTX!?)@^0T>qEQdP`>AE$LX%xagvqMa+aa8;C8`}HuUky@a(s=
z<azV%$M5K~?&h-b>%{Eiu<YTk?BK2N=fUgdp10_I@9DMZ-I(RolH}8n>fNlm?Sban
zmF(iL<I#@k+^W3rgyPSQ>Ds94;H~fF!0zF_pG#z^7Z=B>b%2f~b#^4RQ$FKkW936b
z-9bUQ>wu4ZIPc`Y=+~#`)}`#;yM|jA(>_1o$cW#@hUV0yu-$XX5E0BgJmJiW-o%9F
z(xSc&4#hh=;K_*H!-U<zg67eo%w1jR)~4*;x#rQL<<Oz)+qdxO!sgSY<j$b#*|yug
zf7-fz<IJAizk%e<p5x1&-i$@K00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0!T?jK~zY`?UU<U5<wWn@pV8EQt5^jN;fSkZ!Z^txZea>#3ciXMG^ue5lb*`
zBLBL(BnC@M_{lzaKFl*S`~03W=gh1~bf??Z7>o&i8z-E`2MDn+LnITE(kU`Ph=my<
zli!=3;UOHsGB<N!4<_g`tKc-t-v&Vskw|g>!NW(76$6NJ2r~EN>D;qx<f=ddDj?lC
zzfvATB(nK=DOmtDJSQ(+N?*MuQ;UmBZ^#ftCYQ_Kk_phjJ0f{M{b7O3OfD}k^&$OP
zSQlBxWkuL!wbR&Yce&O%`5J+BAl-Rq6m~(aPN&nN7W801!-mmlLX-KU#frjNP-nB*
zbf^P6IMC>HI?ZTyx!j(ABM$Jw28;N7n`m*n{ee-CEij_l=W|)m<M#(C9G=C&reF~^
z#ik`>#q9t^g?o`5n$u2q89~HPuuHQD{TVq$evdr}9gW5IqV%<k!Qp^7d|{C&ZS{Bp
z2@Eq#a=+Jwj-`OFxEFRI^_BMcgKP@sAf3+iBOq2Pl`555t<kJ}Ti5ID_PBQove_IR
z2<Gz)Cew$9g+65OuP&+V_amCh=Zi_q93GcW`jBWYcl6^=7L(b`W$A2&nV)Qm)AC8>
zY=p)*2{p#QmtU5UXB!g~#e{J`Q!Sk0S*=#954-)4+s{4^DqlQEV)eLGs$i|rXr2#4
zu8J*k#g<F0Rt+y2&1QQT(pzk$)oRr6yvav+SrQb(rKlFlofH>!thd|kzAU`IGSb`9
zXpM~`UY7p|xp@{|V|TiBT>>)4pzcQhBc}iW03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfr*~9W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2a4e9369fa78c6fa19bf5c7814f8a586aed5565
GIT binary patch
literal 1571
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
VFfhuORjdF2002ovPDHLkV1kgxQ(pi8

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..0051f996783873a4a27db4b971135e95cac8d744
GIT binary patch
literal 1452
zcmY+CSx}P)6on&<ilC9wDWY}j1EW==VG&s^MyMc>VE_dIWsf2%OOQ>VP^3a>#ezbi
zQ$WfpN-;oW2^b&=k^l*WB?JiB_w`TyKVY?Ov`?Km=bn3K?)P@ef&wq>vfO70gTZ$B
z`}qW$(($7$%uVxaUUo4IW>yn~4)ryiwS?^8pZAsxthkhpJ|kUEnO0vS%3>*s%xPsS
z2k<7yY&!WTdW3!iaT!ycQNDmlo}HT35l7XO3C;8jz~MlDOvDm~m7Oo7J-u?muxfr@
zpW4Z+8|Svr5=g76*J7xhadTK|9F-Zn1z?W=#EU?ha)Ykg7?VO|88jh>7B!m<YlhB+
z+ihIKOFme=tgB-K^{c?37$i!dE}kC8*Y^qZlX7DhJFTT%m|NeG{<NMuQHSGZ&NB!^
z;v%k3I!Km}R=5~+lUn8L%?)9nD0kB+*n|W|{W4?{z}f(LBJsvL7vc+yV)2Gr{Z1in
zFoNuUS%J~8tXEIz)ik||2I-d8IN-v9etFrj#(`wAO|24dG_VI%MS2w#P*Oqdl0sB3
z7yho1^%ArX0Ocg0m;^P88U<b>@6$+o03~@7;%^vuMm@JJ<aWoNJuAayg^%p>Gmnsy
zuG1*Xb4;BYgOU392vO(P&!Kp6@p=_SEyk&Y-2;lk%6vCay<jj&HE-0dOV0b0N7*Ii
z>@A6~WuE;;C#7g56s?$SP%?QaR1+n-c`~kT8uNVa*2|@o?lo)=KN~N}8`2cd*w{>1
zTTgj<YDFZyg1O@D{UE}YyxV#N4ySl{7?kr`5m_T5X@ui?`P^!3EZxhC>gh@L@R+)G
zO(PhQvf3nzE#>#mKw1u{SyOTb#j{N!#xvnegJKyEY1bgl8X%hmqzq8Kx;j$0LMUJl
z<qK%_8X-v|7+3Q~)m)Q^fP}#wDqs)hYxrZTbs`|9t4#~%jZRFLv+$sL4Uo=)sui7h
z#-L;~yHiyh($Tl5Qd2dzWCRC>z`VsQjOo~s>UR}uO21G3b~5>_sl;f1pL3yp6Y(*2
zTg|QPYVFdf4n2i1ryt-~R&&EyEH~GIaAZa|e{p<ldYm><(Ym$Sy#1kbWFdS!sl4#8
zg+-&46R)?p=v<IrxKAkB`h2<TL4-|Z@SI~R;xX*W)A-68l_8JKw;U~tw3<);5q%L&
zO+WVhmV94&T?E^GFg?51+bpQUzhTmQhVhm~3Z0o+9h<ZCwR#@S#fJ;<<Zb)oT##J|
z)~QSg4M#(dpu=3@PP3E!chc@*v$C?VU<{A|;Ea$C)Mvphc3Q&ID5^bSMg}7&YYEw!
z+G=rs{cXF{PQvQnc;JJIsEn$r4gNJx`JpL<IafAY`AhBA`O0bGjYCz{Rlhy-wHk~D
ztSrl;%8Mf2s{-cqz(t1-!B^mBiO9Ppot??{A(!2P0&-5fl5P>dx>DZULg*TK_Nrt5
z-2iOB@sKY=Jc0s3a*{6G2s_h?5P1!wrHzP^j*5=&P&pEke^pz|yOz9TpDUboGQjBx
zMo!1otB@1eyV;Rc&(=*V+v==K^@N)awupf0xSm8APIR0a))g^~LD(V`iz%%B9lV<9
zNW9qx+^!S-`8sx=M=9LFn)pSWRENVZslD!B8wv}&N}w0~Q)Iq<_h*6eL%R>T<xbtB
zHVKX_vJbiD$PIhPc3|ksSjnS-lh?8Q_gnrdZaWp#?-x#cg*}W&rboNyF#et@q1F6p
z9T*>0alj*U0b~~`wx_Vx`_n~Y1o};*=weXmz#wPa!-WHU)K7MW;+k{KB^hrTOISzk
zQem*}7uv?l*3EgQg4t*IhG!(iW+b}Drze^M=7@AaA{-nMNQY2Iq`R||yR-9g<Y{*#
t(zVw9;QxR-DG5oKoc{o%+b7`Tw>#41KY&_u`GpC9`TGX?bfIFt{})E(L^%Ke

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png
new file mode 100644
index 0000000000000000000000000000000000000000..76c546a4dadd7fdae310dafcec2ecdef9649d4c5
GIT binary patch
literal 1377
zcmV-n1)lneP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006>P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZJydFPPaY
zjqWG6vC4&kzm|uj!nL^a^ZV4*@~N!lj*;EL!|c+nY?{_9i1sFWzH5(?-O$nT?A*cg
z?Z)orvhC!s?BcKR=fQ#6N$usa=Gm3y)|2GZkm}s3cd&fiz>e(VuH@B`<I<1f&W!2U
zr|sXy;?RxZ&5Pj5iRaa&`u6Vc<G$$Fr{~tC?A^KS-n!+`q2$h=>)E#4zJS}je%iZz
z<IA4fyMEfbeQRKz9{>OV0d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U00K`*
zL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U6fJ!G0)j%qd?aZR
z5fu}c7>rs-_lqPS2uKb<h(XgGuGEK33lp=Hw2Um1oV<df5)(cx$|@?VY7i|P>Kd9_
zOxilSdivP3!2P0PU|<N*L%<e9h$*WW0rh~yjKRJz!Ik=uLrmGoKm|mbnweWzTAAVs
zOBC;`SVOd!+t}LKo8n3rs9GE#S{$96U0jLO;s(*;?&0a>jav({i+z0kltEhjT>}Dx
zaQX!$jRpIKgaY-1nSww#aj8!^Gy>=})5xgkm{?PyQeSXrL>$l-)A)qMq-30aK}mc*
zDRE%Gq^6~3Wa89<QYfipK|(A$CpRx2w-)50Po)5&rLZW!xCEzPP;Ds%YatL~kR)gf
z39qtp6+&Uk08WDD=4PfK4Ju{j6{^H&F|VwwG&cpi3aCSkSS=P1TU3bB0n$=sQ*DYQ
zQd3)3UtibI*wozI)Y6L8FP3fY?MPZWI=i}idV2f%Crq3;Y4Vh*m|7qK8ivp@ZTgHE
zGiJ`3J!e`6kUM)8rXJI|^X4yD2)CuC6QpC&l-Udnb2@84dZu8w&2;gSrOTGXwbXV2
zb<9{X8N^;$3nC}CU}#yjdd=E(a4mJ+AT3RkK<xE(AaYU@hL*q$8#ir+YpL%6X=$Da
zVsEJjkrSISv~1nBJ$na9TF^p_D2MG#-nDxVY2M$pci;X4qy_TsgNF_wv=9}Q2M(cV
zAtJ$;mK->6poFAkiJr!&3jm~}-@X0I9(w=)03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfh#BXQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..ba24ed16aeb0d93bd133902b0b2218e674f84528
GIT binary patch
literal 1402
zcmV-=1%>*FP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006^P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YN)&~
znAt0h?kBgg%7uZymWQOmwYc*0`_$F)sjTIWk=?<=?9#1ln$|0b_9lA1YmbuM(9!Yi
z+`;qh#_r~_?c}iR;;-=M!GYRI?d7rN*_GwiljPHo>fEY#uzcLWj_l&D<kgYm(vRZK
zjOo~??cc`Y(2e2Ei{Q$M=hdb9_U`ZFzUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyHkc~n00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~
z0%b`=K~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4
zBxw;56%&^jj9OrRk(88VkYoTMLO_ZS1f)P}iPb`eEpWepZ2==#y2F+Fuz8<}Sz1O`
zj!9lYQAwEzpB5EWRW)^p77h(fEo~+pT|IpRY+B&4uWD#$1kppl7I=sOZ7~Mw0g0J_
zePN0#^&zJ*6=OqH5N&2|VQFP;hAZ`<cwf~9qQ%12&fdWcSGqve;t0{=<m}?=Mx+*Z
zh!zh|FK-{*T994r>*ucm(h}en7!-`tFNhEWf)M}EFrc1rGZ2VCmcb2F!XklAGmDCj
ziH$QOD!+z=MaBbdF-u5HN>0J)7nH>3n;H-HOImtHW)@B@D20-0HYCJya`W;FaBD#>
z`cw-cT8fGbN=kA11=W@^uoeO#21$Y@knk$6P$d+m4B#YaVPS3t(x6&iQK?3Z7K^H?
zDho5PtAIMxiPd5Wu|<_A9Uv{$wl!u*BDHn(4Gr~;P0cMW&8=-%{bJSb(Sf9;v#YzO
zx3{l<!o*3FCQq3<4O0swK*JF_rq7r;bLOnsbLLL(1ajxh#?)grZ~lUXi{Q4@c7b#(
zo;rttVQyC~NY7LZx0x+jx@`FhxR$zZppKa<r-0b2>Oka_Rtznx*Q{N)9<HUn2c)HW
zGKjsQ9z;%V#?TV9ant54a4ikJAT2GEK<upzAaYU*hL&yHcjW9ONefzt5#_L5DZBUV
zCC&T0_w7G$khDPFbLjA4gchQr^59_<Ekq<3v(keH50;XYEYZ^#bpZet8{@o}32r3-
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f<D*lWB>pF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb536b11b68fca6c2feda14a8011003678204d62
GIT binary patch
literal 1389
zcmV-z1(N!SP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006~P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZLlAj_u#Z
z`u6U(vC4OVr-z4&!nL^a^ZV4*@~N!lj*;EL!|c+nZ0y{@kCNSe%Uhb(E6~yL^X<m&
z=CbYNu<YWm@aMsZ_9pG+vF6#8<<^tr(~#=is&%M_sJt)i;;!V?k>k>j;?9ie*r$Ha
zR+!l<;?RxZ&5Pj5iRaa&h3hnp?kDf#zUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyg@M18hosTq4gLTC00DGTPE!Ct=GbNc000SaNLh0L002k;002k;
zM#*bF0006~Nkl<ZNXKJfAR90;fdC^ZT9}wwSlQT_NYcW=$;Hh>nikS+;RXAJmlQ30
z`~reP!h9rY5fK#=ml%v%NcW2*9|%YeK!`!p9j?@eO$!sVl(dX2lbpPQq7oB6Ey^k?
zs%j7|9O@dHT1?tHx_bK9w7~tMVqjnh(L=x%M2IP?7y<Qw#EikdFu|4jkV8z_$Up@|
zo0^$hSX!Cl3QH94t5`#{nA_Oe*_+}@7pPhsAX*%qoLyXr)Zzxw;_l(;<&9elvWtCu
z{ggpk{9OYAgK+u<C5;99g@gk2gqeasIB}^@IWz+3G}Fka=$KejqEcUQXha;)7Ss5I
z#H3`LenClmJ}Gfvzoe$6XJq2kf>J1{WI;kKJ0~|UAGa3dqEDp&qNT7XzqkaaUr=o+
z1#2M?Vvr<g3<<BYauq^h$^cG+=H_OmAPp*I<rS*LXfdy>tTZ<Ty9%g7jaV%f5L;A;
z(gD&^Wm9d6BvMmbS6^S((Ad=6+|<&F)i0K9?(IlgIy$?$dwP2N`X@}BIBD{fshC<I
z0UCzTF>N}KoH}FXtZ5xU#_SoGdQ9icoi~30+?JY7kdD?VGZ!wL)ma15GX=wKri&IY
zS-K3arM3%1wk%%=1S@Jm?8z+{T2`)Fy=E<3OI<gJY+47>vc3+)p45b)C2+&WO`G9b
z>U%(B^A?bnt@R-G#AXaF+qUn>-bs=cv=Af8VY`xd@7YV5_jm8xf8Zc#fxPF?;ll_m
zL`CJn!zfx{3FgRAoC(IX<lw=BB_t(F^fX3Y007N*+;lfqX*B=<03~!qSaf7zbY(hY
za%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0
vW_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfuI2JN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..0018157f64a5ae601a2db08fb1e4a553385c33b9
GIT binary patch
literal 1417
zcmV;41$O$0P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700072P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YTUq%
z?cc`w_U^Z_%6EXLhlh*8wYc*0`_$F)sjTIWk=?<=?9#1l?A*bRlHGmFTbkA@(9!Yp
z?Z)orvhC!s?BcKR=fQ~fChg_1=Gm3y)|2GZkm}s3b*P4@yf5tHuH@B`<I<1f&W!2U
zr+&{?nAt1h(2e2Ei{Q$M=hdZ!>okq-C-39F=-8*{)}`#-x$NG$<<Ozz&Y$bqw%ope
z+q{0-yM5!!p4z*9+PZy(fxniAqyuE0`2YX_0d!JMQvg8b*k%9#010qNS#tmY07w7;
z07w8v$!k6U00L`CL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U
z6fJ!G0)j%qd?aZR5fu}c7>rtAevy=vWRPS4Awock4+Nw@YKhfChAnWvfNcRISh~ZN
z`mlMQiCJ1kR*p$tK~YJW37-}fRaG^0h!zeFO)YIE9bG+r18iF0v9D@qXavziz!rFj
z0c|k`>H&$FfPG<#EA=6#F%@G&RS<1vZeeL<ZH6oLp?F`_2BO8n*3RC+3|G29)#3=z
z;^ge&>PDm%cZe1bPcLsD+**)b?Ca;R0@4!T78n$a(=Uh+1A-9$&@iB$a5E5yK$gJ`
zRKg;GPBV*&j){#kBPzd!ghj>!Z81woOiE6{=@*p5=bIW2_DfoNMrIaHEhvSOYBnUq
za&q(X3vg>eF8WjpAzF%x3rb3H`UTaNGO!i`AqGi;CXnzduTUivrVQXDXklS)2GXEf
zUQww=j24TkswxXJu&aPN)QQz%39&_$C><az)wVTeNFud$^$iX6jZMuhEzPZMSp8zv
z?$LpyrL(KMr?<DSf5OB`lO|7@It^0`BtXLvI;PJ6lGA3+nmxS}$e1$|Q;*r)dGi-6
zgxgZv1=7(rb=IOqv%6|RdZuEy&1~_KrOTGXwbXTk$kr8$fM8`Eh&`nhL(8hwYu2uV
zYpL%6k<IHtS~k>!*pr(vv;=M3w0R3$OG7V+Y}pFZvaJEcp45V&W&4huIlD;Gf)-*#
zIc#^zp1u39dcPZ&_xJ2SaPSamfxP$dks}B#L`CJHBPd#63FhcAoC(IP^w6P0r6eUw
z^fX3Y000U{;7!~3Lt6j<03~!qSaf7zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4Fh
zG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bN
XH99ab%9mBF00000NkvXXu0mjfCZzUl

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a78fa6a1d35d8b2666ee3ffa5583db9662a67e4
GIT binary patch
literal 1490
zcmV;@1ugoCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70007fP)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j_U_>5)wA5djnuJo)yKTSzu&2=<&>D;#mDaG==ksIwesx6?A^lL!H?z8tMcd6
z`}p$9v%Rr~oVtpQ(zUVm_Wa-A^}oUFs;%YG((&EGk@4lw_t?(XP%76v4Bcit{POSI
z-Sn%j=JV~w?&h-X<go1Gukh!=<GQ)mQY+$cP1;Eu-)lwf<+0}3mF3ox<kOJq+^Wd6
z(8ivy+*T~?;;!V?k>k>j;?9ie*r(rPJ=a7P;?RxZ&5Pj5iRaa&@3EfMdPDEyzUbJe
z=hmg{-MRG3zUrWd?4*wR<=^byy5-QJ<j$Y#*|yxifZM!&+Pi(@%bwc1e%iWy%Ued<
zo^8}&Lerv`vZEDM00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~0*y&T
zK~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4Bxw;5
z6%&^jj9N(dizFWiNDe@VLDL<s)Q3$A6SI`Gj4YF!yn><<6Fx1<Dk`dK5G@?)8k$;6
z+B&*=`q;F<{i0%EU<lDez!pS^DXSO(^?<~T!M-rTmHLoFOxegl1w@;gnOj&|nc@md
z6z{88L$sLN*xK2f;z}2&S{xu+9G#q9T#3};2GQc~;pyd#TMM#_eSH0tL0bG>0|J9^
z`UNG81^b1B0`-KMf<QQNsZTjH0_Zf;$f)R;SW}`>UvOwd9MBfi_=LoyWSo9MNqjyj
zabUlsrln_O;?#mtD5+#YLM%HcH!mNz7UZH&r2wL(uqeN{1gBq6Z7Bt7ArNAaBxno?
zud;F#LSf1PPJ-s<W~LwwDrMyrs>En9udJ*zHwC*2s6&ldEfx@4REW|6(o$tpZHgpP
zQ(ISGU)Rvs)ZE<E(u&nDmTm6sNLo5NySjUNdi(k(Oq@7r@|3BVS|9-$hR`u>Is?Ou
znX`ak_8bO=x$`jfn9g6YaM5D8Ej67$9SlomEnT*J)~ppk=G-Y5ZZloEYW146a4of6
zAadP$28KBsX3YYzC%0f|*|=%*maT9tb=@GcX%djwHfuYGJ*f#pOW=;3yLQ91)c1hM
z=7|gpduGkr3t~@f#?Z2F|AFj-7+MZ(n>FijJ$5Z<AqI2Uky*2jcGux>*s<i}Cr+Yy
zzhyE|3qw~e4(}g7b^6R%v_Nj1GM9m2dS?xeKt6Hq{CR{HNK`URy)bLm#SR7>QF-<}
ziWX>sxio9mWn2lywB+pBvn3=YOY}5GT>t<le%ij3CAg3P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f^-`z-2eap

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7764b74f5e5e5af1a205b2ecee9ed184727e1fe0
GIT binary patch
literal 498
zcmV<O0S*3%P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70001NP)t-s|NsAV
zbaXa|!Dg1x!H+h2o~cd1>4~}9VaDrk&F*&1?7h9crNQLey@{>X>89TD^V}-D-R=M0
zDZt<E!r<=3;qS-e@W|uv%H#0M<nj6DU(Mz6|K?xO=kn9&^VI3|*6H>3>xcgAhT-q`
z!N@d;00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0Ut?3K~zY`?bO>2
zf-npQ;7O;V0?Nb_&J(=<J35L^UDqjzCdSySWyu#<`)@-0>*s42VGTQwXf%l9@iclL
zNdOXeuiySypCM4!;O@y&X{wSOIUXq~XQs-}6QvYS4hKrA@L4=0l~^f9_IpA^Zz9B`
z$BhkkJMf8s@sL*VhLWpb4Q#i-2+rIQ+9-eP?noQ0lYBNH*ld8!i7ZVDly=E_-I=Ub
z0iwNRxdislWu;A#vn&>!J9Z<@Ox#J(okv2`=*=e-1+Z!K=krEr8ku~;d=B=YX>j2X
z3`^5Z>~y=h^w%!z!<XmH<A-BxricT_g}zMDF0MB}&$8Uyetc9c(E*Y`k!1ybMZHLj
oF&RsOM{v$!pYbjo``5SC8S!5pi4Z+;&j0`b07*qoM6N<$g5#I=_5c6?

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..d44eff429fa9a3409776ea88a11421582f6f4598
GIT binary patch
literal 1298
zcmV+t1?~EYP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001q
zx!X2~!Eep(M7`;C&hL59?^VL-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUOPl=ya2odY-AJ!Q^I^(ZP>4@#=@|<6!gf$nfm8
z@9DMj?Zxiqv+?W1?BlTT>B8af_vhLx>g1j3<DBW>n(yed=-!#<+?Va;vF6#7<<^tz
z;H}o__3YxW=GvC*;jZM>lI!2C?dG!U-mLKEz|`sV%c5A@y@~4Gs_*2!)9CZ$)RE)T
zkK)jc>DsB$=kny!kmAma@8e(T+p6Kris{*?_Uni3;k?b|^7G_h=+~#{*QV{@yYAa6
z^V}-n$%)^{h~LMD=G3Ii<nj6DU*5)s%H#0o)urasqsZg%{_KYD<-qLSy2s=2#o_P&
z=3ngIy5`cN=Fy_&(V^_zxc}ZM-o%96!-L$xg5}Vm+`fRq;O^wkpX=JUz~AoV&7SJn
zw!Gc#+Pi+^%$}{*>Ep|u5BdaA00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0?A24K~zY`?UdPD5>Xh3ZKAN;Q_C#*KwubLStTT}1_fLIkws7h8A(MzWmN>>
zU-!(C!g1n>o1QNGE)F-m&yV-_9Zp~0%lzwkMX>Y7Qw$+qOC*vv#9OIU+E4Vqdp|Hp
z3=Rzsj}RlHV`Co(QA9F6K0ZNAOioUIBtA_|O$`u3pQX}aVsv_XV2tRNGV%8*U%21Q
zpI@J!9p9ai$!5?8$Q25O9A=eD<t)sqR4O&7Ns`onhN37fbRe?1IT?#6=I7@Xpj=p3
zRDo(~X=xcqonCJM%4js1F=S<>16kw|9SNjfucyFbG<8Q*i!8FN(;>v5|7N7XYFb^j
zi6er`Z;S7og+&Wmv(4_nkhL`y`2p<k=g;S-7$T&omq}6$8iRq-fYxd@JHSEHv=d8N
zUvG1Hvaqlg8+M>wZnp<RHaFW4Qm5mKHCn;Ey0JwA?RIZ>L3V^<DXv)CmdizhcYE9C
z7f0lZot<3~3#Z5L4`9gNUeAs32*`uMAWESuRxLPecC-tp*W+<QAQTEmL{lKRzaI@^
z7CvMViX9xrG34k-K%&tED%OOq2kmmXz2Ncrd?5%Q942w(_?SncEW#FR7mD@!VzDr!
zlF1~4AtxssvYSXG(yRsji?awIi*PCekvPMgV#wK9&yAg*b1CUWCX;4&;YGXf`$GYU
zq~h@lIL+qrmmrE{uCA^!xWzRCxk9m6>W1*wBNU4rq#&NWE@UBJES7PkQfWgTi$%L&
zt_y`cl#1nYRUAoYSeFiqTrPhDH>GOz219Oddv1(Jgp@!yg?4elFxgYMER{+qrCO`q
zwWpwmuU=;#Kf#5+9!!C=D6<x|Mx$}xj>y*P&1N0_`voL*$X<^uSL_qg>P)FLo0X@O
zINC)Pazz%YmdlMA+~Wvh&>fq3bh%)%n8jVI)w=6!OnAn7Y3ybGuXzBr6b33x=w6}#
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f@j>KSpWb4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_insert.png b/web/pgadmin/misc/static/explain/img/ex_insert.png
new file mode 100644
index 0000000000000000000000000000000000000000..862d837277c99e17d2b66232de79fce010752e7c
GIT binary patch
literal 1065
zcmV+^1lIeBP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003yP)t-sBmgK|
zTU$1W!3nA2a+aZzl9IuXHmaF8ou4@}wdHlD$3eX4%c58k;}QztMoPfx$EtN*#Oh<l
z>TAd7XvplT#d&Va>__Y%vc!A9!GLwm?AyJ6+r5e0zJT1nfP2yJ+`)o`(eB;DgM`!Y
z-o%97#f9F+hK$ti-^YjF$cT~E?%>IZ;mnGc+417ejN;IZ<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp;FQ>)N;I-kIv%s_fjj>EN2{-mL81y6fMq?B2TU;H~Q8obBJc?BTBM;JfPNo$TVT
z?cu!a<FM`JvG3%*?dG!X=CkkR!0_h4@94Ad>9z3a!tm+B@a(qn>%{Wy#q;gP^YF+M
z!Lnrl0004WQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkQhe<?1R7l6|m0NSt
zP!vXK&`lBB2CJaOTA)a|q(Gri2tkO1TjWv@3IQV^xBmZM;7QKm5)32GIHM2U59@)+
zO7{1iGlRkKH2+{;P|ED2o<i9y7RwoRabmSzQkSo7wrlG8!sWW5Zmt4>Pn51=F&d2)
z>IBhBy)v84HtG!NF6!#^)E1!r(pR3TJMW~P0*}+_^l{1Ycsz%>EXy;PNs=@H{0soS
zsZ1XpT4Ta-wOSp4TqzX#AZ4@ZCSYUngQhh~OI2D}smLG~3Iz$e+4NHRx>yYJuvW$`
z^<kBxN(CwX!so06?Q|;DAWA+@Hy5zZqVB&+@l8$9oxr5os0~RL2mO(muAG;k%==v=
z?{&03tR0w-WO+_F>-VJ@Oxn%n5Qd^C4wf(0IAs5IimsC4t_ET>94@Z+-*8Z+6;vrE
zDmL4OX6o)%1Th>A6*p>99W@-O6jM@*ZQMqqB9uZQMgW7>p*i#m+5guRgQ6hxVDr$y
zp&Tmg*VxiQ7K(#Tq1&Di4jVOe7K2i;IDj6{=Q^OhkHF`j9~j8~+7t{*!3X_54z}(v
zzyVU)=`^|+m|`><E<urD7#?zdpLbg!qEcy%5Em;j!C($DUayzcSJ3|AcS0pa52eUu
zI?&-*wyvxEL1XK$h+=6#I1mKB2b~PZ-9tT@h=0*v4DY{{!oxzs?R3WU6?`!;`C!nW
z0nY1jCn4^1p6^g_rK@7mS+E|rI}S1D`FTWN!TW1cBogs5lw*-d`L~U|Gby8??pJ!b
z3_1msrXytzkCh?RRq(}7X*#mo?MOj9Ce&3jKILMl+*jeyDI_uXRqokjvW~fDmd(cA
j94hm6lTUy1|Lgn&xg@`Em^A9100000NkvXXu0mjfS$;eB

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_join.png b/web/pgadmin/misc/static/explain/img/ex_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c391233c449bdc5a0d52d50f522bd3e35c649c36
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_7<@W^2*R&GEgz?Z3h8!ou#;)brTc^v=)n(b4kM)b!xs_i~n@
zTZg#3z3iu{=c=sduCM9E#_!9_@xH(9#m4Z~*7U)THuUky@a(t1!S2S#@6FEg-rn}>
z=AWyq=(DuyyuIzz)bo;+(T1sHQjHmZrCYPXrlG`Mg|bG+s&$Tqt8$|?qQ_wH?6#)N
zW$Wdh>g1j8>9p_bw(sb(^6bTgsA6`iJD#<NuG(>Uu0Heb#_r~_?d7rT;;-=M!L{Ui
z=GvC!*OTPakm}s3d$U61)sf@UkK)gb>DZ@@!Bgwrtm4p);mwQS%8BRHr0(Lq=+~y~
z-MP8zf4c61<<X(!&Y$Yow(R4uyzqqFzJS}je%iWy<IA4v<DBW?o0iO7=--*_;H~M}
zsqpE-@8-bX#f9C$g5=GfwL|Xa00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0r^QpK~zY`?UUJC(?Ar(V^BbpRmDM2l-2b%E>$$61+gq;DJ?Apx>_)8HEF30
z1qv<yI!QY65i<h=Jn+JOn3sINdveZqt{_~`MdpTR4te<yvEk;e#wM}(cFUc+BHuEx
zwXMCQQ|!8T|3SCNw~VdAT=visCNG}~>Uq@L_ZU6tfBI|y*^ohN&5&~$8Xg&a{sO&x
z)%W_19RZglNiNj$_TBprh!c_AZnuPnKYkkfd})N~g^7GY=8#tqWQ@OlJIjk*1$#W>
zgqZ+yzW-as^djR)gE?nn@}vi!H!=L<1mfjN3{M>+(=)SYCw3BX`FwNp6fe$M7+;i6
zP0Ts|#ifABKyWz}Ug1&^<UqvyH3+S)tw$+doagEfL}HdoIG#wRD2Vfr7JgcU0O^g*
zUkVLzoGXry#4@R@lB0NWs^H)F^p4ffmArN24^hdfs+Omy80GW&f-oN{gjc}IE4D=t
zwfMKBQ@nt8X1hFR1{zO;+2q*P4r#JhD$x<&c6oOh3TyF16wJRei2A*N&O&C(yQ>A*
zTTdh_V86<|uIp4UMlA~aa1gDeGGHqKGqL&y^4bu#%6@n+eE^xPqU7u<Y%fNWHY$*+
zR%<FRl1?Nyv!GOKHH{aECXMoeS}PV0|3jEwtnxul(+=~OPV9QFW{v><WVgBso0Wk8
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f?exDGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_limit.png b/web/pgadmin/misc/static/explain/img/ex_limit.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc3efd59d70436349e5413f8c5d2673200cd74f0
GIT binary patch
literal 1237
zcmV;`1S<Q9P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(M7`;C&hL59?^MF-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUNiI=UKt$YscuhV`Hfo7w~d&?QwDGaB$~uaOH1r
z;cjl-ZEe|XY}0FNhN)yyjTym@HuUkx@a(tm>$dan$M5O1^X<m+?8WZpv+d@x?d7rY
z>%{Ehuk7Kj?BK2N=)$7LVC&_c>f@a6>b2?Mn&;e?=i8R-<FMt|ljYTt>))-W%w_1_
zndaG*<kXSm(~#=js_*Et=-rs+*p%bakE+va@8-bo<iD=jaL1~3vEFs<<gn}Btm)gT
z>Ds97;k~rudA8?#xaxo6&Wz#Ai|N>>x$A)A(2n5BiRjm+=hmj@)urv<yUl57?A^NN
z(xc|lqU_wb-^hpG#)jt9q}{@U-NAz8(4pSMh1|e_-NS_2zJTP;pYP?s>)N;G)1&6m
zq2$e;<IJAw*|yrdedEiX37wJX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*pySK~zY`?UU_O5>XV!X``@E)R3}Nf|O*B$m)u`ByS4@xQIz9FPadbCWy#_
z5QxIR-d(t0;9^epp=NwO+}WKuzd83g&$%MegZ^zjB6tlvg%IMgSS)@*Je5c!L&VUt
z=h9(fctj=}B}T`_$6pYFh*&O{i-`$^Lh+J#H90vcB}QJqnUWD>)6>#%;<hdmlBqj%
znNi+NGuMdnZJ$&sm0G1%znh(%otvA}Xf*GU=I537>Y^pJA`vX;bh-upT-1Y}qNpWU
zrdJHBuxd1!Er6>!B0rD_(XFkm>CkDl+JLg#?KIF1r;`Dr%kB06uIeHmuaQNo)k-16
z>!rcq^!bcncKg>^z*Rzst%U@<-VFmVK3~uTX8-!;P2>~V_R@9{;9VHHF1A>f4FRs|
zBA?OE7vMGU#8sVnk=rDx1^uFp(!)}Krk7!LhhZ#WF?+&W5Q;>jF~C)w9^1QnUrFAD
zodO!W2)fMJMPxr7PXMm!blKhehPqHzyFCE(hQql7Owi>vLwGZpj6gIVPo)7@b#KOh
z^I4$5t~i`N223uuKMZVge?JO|R4R1<xT-gO?1Jfc5lbH&9`&dHC!2h8X_3C)(ET>K
zCEkTm$i;CclRW{Uhm5oEtZyb+ez5U09OO<<^N=|!6gXkT=yIVYXK@xOl*Qp`v6z8u
zp;Q`xnA|;C<PITUES_h9E0xL>VZ`DIvp*md{nbSlxu|lLN>vc~?IsI!LGL0Gf><J*
zK7ylszIF^J7aUiGYNdX8C48}77w26U^+u!76h_)hp3ddaU~A`Rg$v-yWrSP~M%qh`
zvMAR0EYM&p<#MBb8`9%~Yq_ZNF0Pu*=GEVt*n|Fa{RGa817HRj*Pj3Y03~!qSaf7z
zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6W
zZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjf-+GHC

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_lock_rows.png b/web/pgadmin/misc/static/explain/img/ex_lock_rows.png
new file mode 100644
index 0000000000000000000000000000000000000000..41c1148bb185c87898b3fddaa76be36a15703336
GIT binary patch
literal 1520
zcmV<M1rPd(P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700078P)t-s1prKL
zYin<9ZE<dHb#ijIWKo23T)1UXxMxwgX;FK4c)Ds)x@%E3h{3vUQGR@Ux^Yo}etx`j
zQG<Yhyme89f`YwxQHX|yzI;)Nh={*{QH+X;38~`0f>Di(jE;<qz=Tnbj*fDcp^%S{
zz=%=8h*6S~l9Q8@!H+hYm3+dEQI(aIm6n#4mzS5An8cD%#F$YswdI<go|~SYb*9Ih
zp`pT_QO2H8o};6lq@>5AQ9-=u%c5Axr%}nMQA)t+%BxY=s7}hQQOB=S%&$?Wv9Zju
zQmC`DUBv4BtpTdFwW_wZW5(*uwpDA#=xE67&bnBwy1LG~S+BgjZp-YjzP@$M?Ap72
zw8FyMy@}(!bKJgw|GhAK(eJjz#N5GxgwgKZ!-Is=@ZQ9P-o}QE)b8KMhu_GEk=5?t
z$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+m
zlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#r{>z0>Dj2~+?VOvsp{Fb>D#L7+PCQ5nd;rD
z?A*BN;F{~+tn%Ha?A^NS->vfAr|jOk^xmoL;H~Q8ob=zR?BTBM;JfPNo$TVT?cu!i
z;i~N8u=L}t?d7rb<gf4KzU}6+?&h=a<-qpkvhe1>`Q@?i=(G0cv-an-@9DMq=d<wX
z!uaU6@a(qs>bm&qxbf@6{pz;(?7H&p#rf^N`R>2@?#1))$p7!Z`SHN{@xlA?!~gNV
z|MAlO^v(bC)c^F+|MtiK_R0VD+W+?3|M<<SyU{8D0004WQchC<K<3zH00009a7bBm
z000XU000XU0RWnu7ytkRE=fc|R7l6|lj&CyQ5eRxeH+_N%WSus*<!m=*`Bu8B8iHI
zC~mlcpojwm1gMN_hN04AG^HkyMp;_fzWiC;xpx#ij-Jk`bLxZdhi7KancsQe=eh3<
z3Yw{ZPzMOMrc^$J5DEl>R3a-OF)@e8xgr!66UABC*%d@ZUSZ)af)^1)L_`RP1gMF`
zrP$b5A(0C4Y$7i?Sy)K?L6_Y-qqanb<M+*I<|&mUcAZbVnRe~SO7sCn1_lO3#?<5S
z41pnrVSK<x)3g_0A|~ZR%)0Q%xVS|K8R+b6_i*ZIY;5cUjNNMO2DH(rb8yJniyPR-
z(tX=Gq#Ysc7_l>ev0AM(=rZcYkf@Xxj4t0ln?*eB=x3yTb~{A6t+$OdaO!GmS~0Te
z24{TE!C>SC%|Gtq5pvPx1ztyMQx8I}?^v_?^qCXK;t$OVo);YybP}+Yfi!)xvdGo<
zqo2Nf{PF$s@L+%cR@fzzEilx_FhjuC-A(&|*XeNdfF6pX+^Eatcca5>4G#A9K3UHq
zNl8hW0WP{&7u8LGQYjQRgq(YWkWm(S7&eYDb~_H%=md_M>PiYw3PmYKPQCUc4?_J2
z3YJE}wpOZC6sRjLm0J*U{FNWMzhd0WWEXCm#o|Ip{0l#FH{`bxJN%qQMiFvnG3?@_
zyPRH5ds>^|S-9(LHaBpY&8802W$)8xFF$<!`R#4*z2@cx5W(|LbRmZ>%tZzHdW7tW
z-nMaVSm>%{Axjp{p9>MEP6rnQE2UB?>VQoymz#m9AYY~jd{+!b+Qcai)*>$|G6923
zCL{R~I6w86Q`UtGx-c0!K!=_rTX>P90f}WyPLIC}Mb){v)4|$d(Cgbki&j(Bz&{wC
zAMXHG=pvT{8mUC0=11^i1R{D_u2c)EBogry2#z!mA#<hDDxj8##Y%p}ZLyeL6TGx&
zG*$JWUaeHt|Fy(iCIdVLtZgKzZ2=8xwHkI&mY3J^4~7>5D=9@T#H@>Qkw{d=j~MdV
z#lWgX!TOOBenbyXLCdK|!XiqsSX2(mrbgOGEu0@~po`j)l9JkK5=)=%R?gJ_Sib;}
WuicyymX%Qe0000<MNUMnLSTYGrAb2o

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_materialize.png b/web/pgadmin/misc/static/explain/img/ex_materialize.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd0bb90dd2ef1cc1baf7f932817d1b6bdf463b
GIT binary patch
literal 1221
zcmV;$1UmbPP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001^
zyUc^E$aSX2Hi*HV&EAa1-IK}Ps?y-H)ZnGh-mTK#zS!b$%<V(H=}o`rQ^M+n)bNzp
z@OsYdhtuw@-}7s{*>k?y5Ub*3#pqna>5|p&pRKHao1$`-p~$a;;^52R-owF<HuUhw
z@a(tV+qd1?w&2~u>*k;8<eu;8wd&)Y-PyJ2;hXR1v*6sp^6kaUy`#sfb>G{;;oZva
z=CbkX#N5}k+|{$*+Q8@Bm*&}&?BlTA*tOf#vFYHN=--*_;H}=+zU0-B;M>TCsbo@(
z8Sv)7tJG}W*1h1`$FJLRv*CB}=)&sVtLfaT?&7`L(XZLhuG!A5qQzgO%Ve<JbGPSw
zxa)u8(U0QJjOp2^+S0Jx)VsRuf#J-G=+~#%%&gqgy5P!*=hmj**v0JKy4%va+tIk@
z)THLqqu0r)-^hpF#f9C$g67eo*vzZg%d6JMsM^oB<j$bnzJT7<!|U0$+RnGy&9~&u
zp5x4(<IA4a#irW2ecQc%&=gc%00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0(?nCK~zY`?b6p*8bK5XU_ww#jGAI1rVXt0A|?yUN>Pg78bgs7LkqpPC|yAQ
z^~}hc;|y@}z&Sa2@IKs^*>CRt?k<n_Q2(eN@lkOdJ9#WX#r69K{Ds9u;S&GJ)1~ES
ze2fTIR@a`t5WIY~zW!Q(k;OL~8$t+$3(Evq6^TTff|a$mo9_g3PI><U71uHH@iqn5
z?;ilML?RJ$9V671R4U!#Iz}WinM{JZTtN`2QmK@pu2N4Wa``NxQfX!ot#$^{Xmr~+
z;j$<c`Wac&>g^qjd;+Mrj*-t4Dy|b#6bd*+Pt%5pDQfi=wPQ3IcZn=8g&xp*CKE$u
z(db|b<Nm(cf)j<-YNY{fGTBI?)*T!gAu*fn1hHE8Xuz0kHU~)#4k59a%}#=dX_Ugi
zI2^7!a^yZjWSnBxZnt~>x-s`LGP5^kU>L>)TrA6;0C@jxeL01O+O=3LUn!5*>+|C;
z#@J8_HgFbX0nh#I{QUU%`{~6GH;+r<4TZxIl0>5{V9$cV*q<pEKhYGs)9Lh5zHmIA
zz=<Oo2tX1{CR2Cu-AEu33i+r=Je|(qWSk-!OQo{J#ynmhOyN&tGP$|`u@jb!WkEKd
zFJy^N>4`)VekxZimGCKCihQ|TE)WE!sAQ;OwN|U+B$oVjnFob(`Km#XM7mNfQuSJ`
z*2GEb@)F)(xVoAknM$=<f~3_V2ujfg?M9=~CJ0PX&rzLbv)P$@W7$Fh-rw$adlOUY
zb$Ea0x<A0Da4EXO;jl-LdJCoK-`v~`aMFOJ3wl#Xvo#uBQ-i4lK0K9-M)2V&WSpYi
z>rG_o^ihh=U@(|_V-NMe)o+jt(ePu5AK?H103~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfVJ3t*

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge.png b/web/pgadmin/misc/static/explain/img/ex_merge.png
new file mode 100644
index 0000000000000000000000000000000000000000..3fd8299fdcb72604d0c97816d38ee25ec51699ff
GIT binary patch
literal 1127
zcmV-t1ep7YP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001z
zmC<Hv(ap~CyuIwd!0pk|^3&Aw$H?!;$neU_@!Q+=*4Opn;rFJf=Bcaa!NTsv#qYJY
z>$$q@!^H2!#_)!zWKxY8p~PNwrpGpj!Eep(LA>Zn!0CI@?}XFvW5()e$n0**?3dZ`
zpxg0;(e8}Y?vd5*uHf?tsp4J4>N2(Eb<ON+$LOQSVRDwC!H+ic@W}A&w(se*^X<m&
z=CbYOvGMD~>g1j2;F{;$m*?A-?BlSe%w_81oao+}=Gm3(;;-o4nC97(<=2z!;H{?6
zXYAmv@aV$Fs&%W?Z0p~x>fEaD<-p_8kK@pe>DsAbl`meHCBewL<kgaJokg0;U3jiO
zp0$U%?SbUekm1dYi>qviyiJ|ZV|uVayzqqY<iF|Ir|#mtzsI+$-f_6=fAHqO=+~#`
z)}`&=yWz}=;K+#I$A{+Bq~67a=F_9#$%)>?h26u0=Fy_;-MZ$|qU_wb=Fy?$&!Owu
zx7@#g+r59}&Y<1Fg5%7dI?;o_00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0v<_3K~zY`?bGX5(r_3DaGDn~EzQc+f-=ht?ef4F_ZT1uwF%R(Lb^=WWfKN0
zFq@$K>;1-ob697`-_$p~@OkmP`<&<Z{qD@+xE#k7!Wh%W5n^n7Vq%hTUY)u&O-wtT
zGuH_$8NV?*J4f7{pT9LmOy0gTHS@oavu8O+V;7hOa^b9myh~n)+@p*!Z5*NQyWH*v
z)WgL`OOL51%THIHQCQ;gcsy=ub#2Y{oLXL8e6fNj_O84fA+P%HH*FjNug~Z6f|sUg
zKlm9Y5Ckmog+d`3=x{j9027HuW0vIgKO}AtmSrFiO(c>wNIZ@R!?LLW#1iZ2j3wDH
zWf`OhpcKJmHj~4WH(-ouTamY;SNt@?_#qgJC3BGDIDQkbgke(=lwv)NQgHcvL9ilQ
z8{6+3!+!vnR0I{9Os9(+@P&dk*?IrrBQj*kFsRrlDmGgz?(&F~%a-KR=PzHkNs=sy
z$c&`egFQj0NLJ(<GT(ocN}?!|HXB1`XA4~s`&V+=4EGZ@c|njsl4WJz>dLS<v7aT=
zPa%}c)e6WpRjr>w290sM`GP^JRT&hu(P*AR2H%Cx8^sD$O;e!W_|<CT$uNb2QXDjY
zw-2$zW@Dx+l2DN)*ss@{2XOdD*N^ZIt7uwnm_qM#y2p4TYc*7Cv(-A$5$Se&c%rB(
zDz@D^>F99WwM7OgbR)%4uh+w8IY(ocV>y2TWnrZACmaDB0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1n&OPyYY_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9aa51fce9d775f169f70d115761e9817541c48
GIT binary patch
literal 1599
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_append.png b/web/pgadmin/misc/static/explain/img/ex_merge_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..12fc55d76f504140935d5fa422f9a5075431bbc3
GIT binary patch
literal 980
zcmV;_11tQAP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005JP)t-s00000
z003rd(Nc{WHi*FosNxW-;$)T3a-%gGuH(UvHg>8zdY-9xu0FMyetNM%gs???v_por
zNkF{mMZW2fwrozm=1joprKsemspnI|>RQ3)jlolwyLFkoc&e=DiMiWd#p`6n=&rBn
zp~POI$6%JsU2)6oq{?G&&F-hoW_r%-+rEF>y@|ZN?A*VB+`xgozwN%i?cKqGgVF8X
z!h^rT?cT(M!ou!_)bQTLg~G${i`4Gl$A*&C@5RRNy3wP>#_+D%ag^Bb;mnHS&yCB>
z@#M{(<j$YW&GF^VpySbw<<Fqw(vQy1^0ec5<kOJl(V^tjk>=8)<kgbV(emchr03M6
zw&#51*OTYfrLEud)710S)b!}rrs&tF=Gv9%*r(Rk^ttPQ>Dj2~+?VOvsp{Fb*xB^D
z?t<yus_NXT=--*^+qmiAn!NCY>)x#C;hXH;y6fMq?B2TS;+*W@t?c2h>g1i+<;(2i
zukGQy?BlQO<FM`Hu;AeL?&7`e<gxDKzU}3)?&h-Z<-hOd!0+g@@9DJg=)v&l!td&~
z@9Vbl>cjBtw(#t?^6bU)?Z)%($Mo^Z1Vq=200001bW%=J06^y0W&i*H32;bRa{vGf
z6951U69E94oEQKA0kKI$K~zY`?UPkg!$1^;v)KUULUEU1#ogWAouV!7?ykih{xC_%
z08P8uzzo9+=i$EWd~@#Ev&%65`jNspQS#J=aPdC=LHS%|Vph=rrxEdXaCDB~V*P?6
zOSoTiagtKVQ(H*3K0T*!kezPn9y(&R*|RBuD5{jL$;c}oWwnJQBj0x+hE!(ZG4|b2
z56J|uqU6ai*kJH@XNdN;E;@msASoyeeLdlI2K-Hg!O>Vfiyb6RLfk-<JSpwt1POuH
z$A{Y&cse`Wy#}=#6BO#@!i&hmHd?uav*Et3WuQFNGM5Uyz`)%Gnzz7yUq|yS4PkhF
ztFRheEux77*lcU89;dqKTir#MXUoaK4P4Akj_d*D?}vtngvyQCK3LpK>BaV2;4y5!
zFcp^>3gRlIkf(HCo=X%&Q8JG`mtuU?vnWc&GXk+=|5<z>)+K-bO1R(MTt0_mbAQjO
zbuYw{VGA{*NiY=`WVb62QpvD}s*xFtx!GwA)=08cuRN`gIaG!Ep))fzsaD}aS{Y79
zOvZIsXsWB7QXr(2iS{Zh6GdK`j1X5QioEi_{zPxhzn!H%4w>%&0000<MNUMnLSTYP
CgEgxF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ce4839e02fd3f17b5fd8e358ff204046c38d26f
GIT binary patch
literal 1344
zcmY+CSx{347==TqEn;1Yb)kzRwvO5s8xTS1=pdpEBQS_s6{;)(R?#BJSj4Jq3RJBX
zQDkYPh!_@AR0IrxkdTEC5=cT4vWF~}EH}AX?oB|$Ort*a;rwUL%=w=FQ<a$T<6{5M
z{81>>;#h1<lGj7u%Fow(H%%9op-|{MiE#(_c;jQdE<1-e#<7zI!9lWxOtDPx9D+$F
za|9Y;+1PBmM2g7e2w&iwoP>oUczW7R8US1I0KR>uy>o^_wbJQO_e)bxulW@b<ZvB4
zo@1B>(HS=8h)pa;>PUr2w*vp69j|&N3$LV{>k!lrt7&peF2@=_WK3t6FL1zCrK1;c
zH7nuAD!5GzQ;lwx*-Zivior!Ux<t@i6v-ISq51BG4od>61lETNxJ+QH5ZZ2v>;w(c
zr$;)~&Sx6uh{@f|NG-y(<=?}pne`Kl{^u_ZM7rq_Go5lf9JaG;VDH?l&W*_Qz^qE`
z(rDcPFl)8GZeeAZfrp4=T%kmSLZ?ImE0ivyaqclYvz3#LAOEF|e_A$BCFnXQ9V7Dv
z_73408`uMxdz=u{jY#L*+Na`NlkRVmw$Z4rw*oI<I~TTdU}((ckeqDT`iPf(toCb@
zT??6djMEJURo#rD7SwjRVJU3mIH578m5tcNm+*=8qsIyfJBhqUufSr+@WP;cFeAGU
zOx1wur*5ajX&H5xSqLO*8Y-zE#x-)&i@KtYKUhz+mi9wsb~6oB6U@p^NJpMUrV*>q
zZCA}es>*=`951s&nb&8&N(4(twsIRVWR$lXrFet*iAB}#cBo*>Bn%3iW`PS*7^Q7$
z_8+Q|3MKs-z-xxVNk~tFw8K^n)nS-0h+9>R>&oGB1*KF!-sCj%Aq~|!O|hzn95ds<
zWQ&4YCLb!14P4c6a1JBSs-{>}Ll!034sgw~K0UWdJBBk&^&l3R0~ojKxv*KPW#gp8
z%b<J^8ZM=HN9j|LlyCqQ1x#go!@_geqvySjeW$&woSohR5F4}WAeMO|ZS{hWZ^o@H
zjt<~cX=Bt;2A4)*(K*93HuL+nBQc3L{_3j4ufE$?-cl2G_hx4!iJHcXCZ>+~h4>dY
zSFdYJuBo}Vk>A|xk3MZ$+vF3p@YH_cBwxfkBosto!jdAwL$+=?ya7+A@<*k6g(9w!
zvR@G^meB8Yt<;>!+s)T3aowoc;8%FDtaxd7@E|tX<MBnKJ@e?Y(o*z}vfa_c)PZNG
zs-i>z%K`%f)vG>Tv0{ZT@XO_&ueyC`AKI845G$^ZA*NQ}>28@ViTY+6jXxB0|Ihe+
zAGQbA9hF3-<mczxr>ZM7wVqPyI&zbynjb2^vgu!ZXGXE!his~s5{t;!8Lhxdd2+H$
z`a^M1P2{d350QAO=#A%f!-lxV@)Wji(}5@Phm(@(Lzd&2xj)T=+t1GfFXQUTsNU3-
z3CFt&S^TUm*_%@dF`1P<NcVl;vpy!2^m&H9U6|t$5mLzMU;Ubr=F|HHSR3Eadqx5D
zUS@KS_2<1=;NKL~a}pYy|H!jsP2}&ojk|&;3b$YI&{zkqJQ;7?@f+?)@T$!?Vu1u2
z6*|R)<?x~pmIY>P?D=^xPs_+6o5h4r&fZYnYKX{H+WIe;MNe~RudnS)#<~{;28H`~
zp;7y0eV6*_*%98?uP@khq#!fBAS?1jewNozp_mX%a7bt{Cgfl!CNgYGWLVg@n5~f*
t%(lAqYyJnE&CNWOeepej+5QfA+m6J9zXwKZ4|RG0RP3IF7(#Sf>3<opDS-e0

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested.png b/web/pgadmin/misc/static/explain/img/ex_nested.png
new file mode 100644
index 0000000000000000000000000000000000000000..15c47316d5d4163d97b95cda99b2e3a2049341c5
GIT binary patch
literal 1108
zcmV-a1grarP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002N
z!qAqP(SL){b9d2ZYtf>m<h8fzyT0th#P7ht?ZCnA!NTss!|uez@5RRN#>el+$nVI>
z@W{&W%FFS~%<;_4^3Bfj&d%}9&+^dF^3v1u)YS9U)%4fb^{cMtuCeH+s^-7I?YX<_
zv9sxtmEUxE)|i{&!NTpPsL;d2(7e9v+1d5IzwF1wx2vUWos>nDi!Y6V8N<SK)!C8X
z=A7p9r{wXW<M5#4@SfuCp5pDD;q08@?3>`~o8Rb}-shO$=$_f!mC@3Tzr1kL*o)26
zebU;8+TxSc+KR}^cFoU%&Cc<&uw0mqEU~Ov&d-F$$aBxniISDkwXlcC#=6<xjNRgy
z+TW7e-jUhekLvTc$jR@iqGZ|JkFBog*xQf2zwOuBjJLS!xVh`s*o@`wtmEsW*yMoL
z*Nf}&z3K0|)z^yM=aJRciPhDL-sP6m)QFdjE7;qU%*}nTs8!L=iO9rx!oF?G$b{0>
zhS%DTytZrH;g{Fej@H)nL!K(600001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0t-n*K~zY`?bYj3+CUHha0E^Sm0C-!f`UTD2bF{cA89RMMd}k&#45_8X`@6D
zMDcCwzpmmq!zFho^0nPBncU28_IB=WSEKnK57DI6=@B#ZI&Bj!nhh;RQ)^pWyU}cZ
z)A6?RoyGFL>qB>s)oQibdVBlq-DU@7w9Wkk{RS54bh=CqZ4J>G20jkDS^IJi4ZF;`
z8lrbO21iC&#g2{)nR@g!3&`nlGsS~(TRx#$;_)EO3xVtkGWPWA<#-<`!pC#oDzTG@
zm-7e4Cnl!^LHPV-`fD&4425UD%|;##g>e44==XU{gcqWVOA$^@mY1u)d>}fv62tcu
zi?6P&^D2@+DDn7(31MX;iI<(+h;RM?6?qPr6k<u`VluY5#Uwyj-DZ&~GD!iwmYp3E
zNvTN!?e11h8X!#~86`=e_H5Spl!p|M$tel4d9vA`ebie9IZ>Sj{rZg<Sws=27lu-c
zC{alo_Vx${Bq~Y6em=jiHXz9~0tdl-K6I!$8ITL~EO2y0s0Io}dif9vlTN=_Vvz7L
ziImF>GJZ_B{3m6ZPJE}8xcZ5m`Oke+!p|<Q#uX3w<m}?|il?`W$kJNndTTqC$=!&O
zB+G?jsp7x9xaAm4DY72;gNf4L^Q&7%Q+ieFtE0i*;iR56euwqsx`u@l8x{?Wqy`3G
z1LL)UA^xw%J#C$lG-cRqEC2uiC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$c
zGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLe
aHY+eSIxsNGmsP9)0000<MNUMnLSTY)!c1@g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1c0763337617f9ffe8cf13ba0f47f4cbc228c5e
GIT binary patch
literal 1741
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0e8a17d409343ea937e43ebb76e8ebce3cbee49
GIT binary patch
literal 1679
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
ZD=;-WFfhuORjdF2002ovPDHLkV1g+E(G&mx

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_recursive_union.png b/web/pgadmin/misc/static/explain/img/ex_recursive_union.png
new file mode 100644
index 0000000000000000000000000000000000000000..66952ea454e3703dcb08251bd2273193aacaeb28
GIT binary patch
literal 1224
zcmV;(1ULJMP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005?P)t-s0001q
zx!X2~!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_`zG-s;qsyLGzJqh@Q-%+2w=zwN)l?ZU(F)710W+4RoO^3l=q
z)YSCg;P-*9LUN@xnY?(sz3iu{=c=sduCM9E#_!9_@xH(9!ou#w#_-nG^mC;*r_X10
zsycS7JFwk!QjHmRt30ycc6zZujKETQu|cJ%<dC*(*yYQi#9oB3MRKDxqQ_vQ%44U^
zW`?#&cB(tB+HrWUKD6U`wV8gwk2a0LQ}pr4@a(tn?6&Xfw)5}D^6bU!<+1JKu<YWm
z@aVy|=X~nqo$BM9>EWC2=(Fb9mF3ry<kgbv-mJOnfA8zI>f)T}-<j^_vh3rp<kXSl
z(vRufs(PNOy6%GS>9pzKn&{q{=iHa<<FM@Dt>n{?<I#@d&yDHXsC=|Tyzqqc?Z)lo
zvGC}^@8-bj+^Xr?sp;6K?cuzZ%w5~PiSg>g@8!Sl;=SnCr{~qB=G3I@-MZ}IuH@B`
z;?R!h*QVdchTg@6-NJ+A(V^?#t>e*;;mwQZ)TG|TgxtV^<<6ku&Wz#Air~qK=F+3x
z!Ghesf!n@+<jtP$<G$?Py6fAx<<Fqx&Y$Ypwpcc+1poj50d!JMQvg8b*k%9#010qN
zS#tmY07w7;07w8v$!k6U00Ih0L_t(Y$75g^1>^xnCJ=y<#A#t-W?^MxXX4=G;^tu@
zQVTC1zknc<kg$lT7_nMNw?!Q63n)p*ixQGjKvG&pR!&|)T2V<^MOB)R<&tXZ8bB?Y
zTG~3g(t7#^hDOL*uzFF-*u+!=rxxs9)G#x*z@-Jd7o{w%tTk}?1-r1MjV;hGcG4gK
zB^fj<EbJY?TCiG<EyOGw9i6~>aJZKN(>M(aXP_REv;f1z+0g=bbYa!PVBrGNg6c&y
zE$GHsxMJD@@iq)#7-!*Tfnhl$q2L6P?j9KF2$E260x1tqFN|~qPAIs6hL^VmnqMFZ
z1!u;P^700z3Ljq(fRfOJf-}iV`hlc<{R0Anf_+0m!@?tc(LxL(V^~CjT<see6&(}n
z8yXj%km!r%atvE6lE7M$Q({uneADAIGPBUNWP^PHCBa&9z*=(i@(T)mi;7E1%h0u;
zIvN;W<rQEpm1$Ll)xI^gb@dGhEf7DUI$Fb`yrL15lAFL`1tk&D3{C~8j<(1F>Hy^z
z-{$b5#1`Myw)T!rge{O%fa+*?uJrBd&g`l4?d|KIFcHZw;8cL!(G0$mCYMfW^PM_v
z!t@zPwt!Osc1JV#*3O(YyWMxr+<EgCAhbYI0d_|-_%5vPow~?(@sgzrmSKb#c1Po|
z9FL>%0_={)uLY~4@dA8~9tHFV07F>Y9b_N@c>n+aC3HntbYx+4WjbSWWnpw>05UK!
zFfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GK
mIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTYtrHx7e

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png b/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_result.png b/web/pgadmin/misc/static/explain/img/ex_result.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfd7b5904f9b304709ac6aab37e24dcc06d233c7
GIT binary patch
literal 1320
zcmV+@1=sqCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002L
z$<UU#(67SKeXh~4!_bAX(Osq0Zn@x(wb8-K&}gpOjmqed%IIXO*vQS$YP8;l)bMe>
z<bu!aVX4@+#?YR;&}^sCf579B&FEmJ)}GYufxh8%tI?>y&|Rq4YRc_yz2s%c>~px_
zSfSIA(d>o8<yxZCm(uK6qSJ1{=25}wjkVEryyK13?q#joj?L+~#?YVI@LHwSTcFW`
z#^!Uh-Idtykjm$8yW%;y=Uv3<cDmqo&Fy`{<dN0xWTeqv!RRu#=5@;IZp-X#u-cN-
z?P09iK)mRBz~p+$>VwejipJ$*z2zga<UG3PRl(?g&g_)c?q;&xVZi4cujDnj=ViCx
zTEpph#prd#=v=(zEVboDyXJq$=zz%RWX9`=(d|CD<!s68Q=QRq$?2xQ&}6XOaJJrj
zx!-lf=SjZld&TESz2|<->y*ywR-w~O!0LI->S@2`Yq#KKzve-^=xegvb++DV$m?jj
z<BZ7Wg3arCyWyqU@K3knEo+Lw00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0^dnQK~zY`?N#Yh+CUT!A%sLMsaGh-LM_OZl!8#apdwKLmBRyy7FwkURz=05
z^{DN?-f#qVLv*HJI{o0AVTQN6zxUqnUBR$t`S+s2aTTPh2@R$Ze@_Uhr6?^eX#=!w
zkidu+dYpM2@p6b79vK~LYpmgw(Ik4&kDGzz5roD1dV(B&W5e4B`=q1wly#1~+^jML
zcsL#Z*85J~R5W&<&+Tu6-BT{7A|X|P;{qx^*y6$v?}zk~X*x7LLn!g#XXoaFl0t_g
zAIP46<ARx1fJ7H#9?<#k*9uEIb&ut;cg5uICm&DDQMi=5<os$BHaEGpPRal*X-K6r
zalBmum3AqcgrAV>8=EE-ir5<7j^*+nDazi&@X2h!2ImzwkBpdc1Oj$;KNWK&&*v{K
zHc_Fp2T>6_XL(<6MG=Sv0DC5#0-#mAvC+Nq*9v^kIK2l8vdFM%Et8knvxnJo1c6|c
z&4c}KvKHW}Vy0H@L~i3sM*z8wP6~1|m|;789O!rkC-wRX3IT({=^1*&oHufd5<hTM
z>Vx<$E{y1rNF{wq{1pVhD#p6C2Rn5oh%7L0oleB1N|4Fp`jMQ=#pw)z990@QuA5do
z%zYPv$i2ZQ&zL%%$z6BtR(}-AOZ|1ZsiIV3#aX_aKKpI?Rtwj<S$$bw@^{6#TF;1K
zr{3RVX9kpAD%Y_0MwLA*6vA-iVkfW3ieG7@w;>~pUC1Wk{1EpWn@(i<$E=y+6>NkK
z&qpAySa4-uHvH^XzT03OH><a{o{ehAyMj8w$h-WttQ&NZpV96kV4My?L0ABbMGMDq
z*$yScYY)=&)1fH`v<WoU-7VJ2U7}9J&`8I$z8yYm`gKnZK)LWFQpDw+wQo`T$%m=d
zzQj%A4ia8s<V50kDpFI}g$%88-LN9{Um<<~F?K%Tp#n<Vv$^~qaIuPR*#L_H0A7VD
z3nOkq=7|r|v-~&m2P92<JOAsn6#xJLC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@u
zGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<
eG&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTXtOrt>n

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_scan.png b/web/pgadmin/misc/static/explain/img/ex_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..396dfb4feabdd85e081bf8336304129058633c5c
GIT binary patch
literal 1320
zcmY+CX;4!K6or#E43U(&OiX7G0Tmtd5DXYmgR&F@acWZxiv&wwL>3hgrBoq~lr@it
zLJ^duAZWq5fw&ZkC=h`X77>I3W&snjFJvdI(x|^WbI&<*@7(X#z04qTpskIa4Gade
zJw!T4v82aGA-}XZR*;$vgTXU`4u=peEwhE|na4_cJ>&OMPHdC^GN?%#(geLygmo&i
zN3_g;wJ~hf>0VV*pQ>a+SI*I098_lwt84kXyOa8Y(V1&wnnwbI)t>@VyL>~#^s#Q`
z-ez%7n>4gTN@YnI!|M1RSrS`*eOyDod9RKoioG$+D)iJTAoVnDW}2o~Kn4Y*nFci?
z(4d5jN=PdLbt2HDg!CeCRtX6-W+}%-_OSp37f^A587`pV0tPWSt}w|tfRYQSEfXG~
z<pD-9I3_n5EVvjnE1@|h#C^#el^bWp;E2qi;{kdeVBi5}F*q#M8+pK-7&KX20?sR;
z1r;<X(GQCC^Ad1T#TXFlOnku12j=+L1qs+M(#`ULMG4q9t)1s%*%ntpODaaswDupN
zW=R4rs~Ft^&Eh0>X%f3E0nHYQkNqG4U%j7Mp2U7oF*>GZTDYnYlh_prI4U=^O{(k1
z<RH8(z;Xk43WXdD^Oa1*TAEd7Nz|V$N&2LK-gs{q?2D;G2mL}wJ?au}Fvk}SCEl-Z
zn!Q~=7IE=$y}e(yGh*kDXdI~~U{jVK(F*Yxuqnx~>1&_l=Y&sWTE`Nqyi*-+eA9Md
zK3zI6YurED-VlgxD2VgQq4T)o<{Uc52dBM4_ns4qT@<Dvt#*S#&6df$?z_JA^7Nd&
zsc>|3T=BIfA3pq|mf@Ygu#{_8Ub(bzb>X|nwg*!@UBbMkgoVpZr*Tf=$$yMQS7cHv
zJl|yI9uG%_?<;ufhl(nz-b3s9yRngm7!eBB**@c+iOV>**WUgtxir4qH!Q58sBY^X
z+t^lqCrqiOuN6j!FCm^Z-Pw}A4cEdyv#acY!)jFJxKh;d#_(duq1<!#u|_inUhRb7
zZuWs4?RmPM3Lb48C_72VNCv(-0Y~+p3u~M&y#z-hvq<OPO#~HPL|HeT-JBnDa|-Q?
z%<Wv4nUHu7scz!F?nr$`t8(+1oBKW|Pe5|!z>mbS2==EN8$unCzu)=|NpXJS+8k(y
zsf-o0Hs=vz(jedS7h1^)Nk6Egj#I}j#|fGRi^DszZaa6=0t<S~OgC$5;PxXZf91sE
zOWU(6NzTED<doV?9Qs3{X(;P@)9MkAuHxQkhHpV(D8foPn!Wy&AP<pDsLJyP%3oh&
z_cAYs?`!v|IYy!w<2r@-;@--2?03|0W>xir$OP}|H5oNdm)-MvQ!!rm(9c)!#r=_M
zBHea|)}|e>!%!27`lYDuS>)H4O$vGq?bkY|V%O_yDBIEKFR-P%ozXV=^u|^833oyO
zaZUL45Q3B6k&=}B<!$*W;*kcVXyLiC3y+ARgakaz>j~!U7+UU*(%Cey-DHB+7FHqH
zPj|}S-*ndrRToh|R<X8I9r-f-LcEN{x??i2OZ4-b(JqNaSpSxvc22t`6N*Rz>U)~O
z3XiIz)B{0V)^^ow^e^-{bxVdh`d7qN(nH;q3&j)u%b;lk?cGMHsP*lX<>$Ai5W`cV
zkEg^CP9(=z0_K5p$GN(DxZ>PHJa7d3E&?9!g4<2N;r5nqb@(5Um=t{~HtjQj+xH3h
axJ|%$eg^tVLmyfI*dZeMV8gextp5Q0yRj$$

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_seek.png b/web/pgadmin/misc/static/explain/img/ex_seek.png
new file mode 100644
index 0000000000000000000000000000000000000000..130fbd8f53ff5c2c757c8173eb62a1b604555f34
GIT binary patch
literal 1326
zcmV+}1=0G6P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=yH~!hN)yyjTym@HuUhw@a(tm>$dan$M5O1^6ka$=CkqY#O&j+
zp~PP6<(}%}obT$j>EN30=(FhFndjV>?d7rN*p%$xt?c5j<kgbw;jZi7t?lNr@aV$t
z=D^3Qb@1lE>fEaD<G$n4kK)jc>Dj2{(~#oMjN#0S=-8+2;k;g#B*DnK#F|j0%w%+@
zIG(kKuGw+%>WA&)VD95x^XP-`;92wMfbif}@ZVJQ=6#RAQ?cH30002z+A8STDCyWG
z>eeFb)Es@XL#xzmv*LH^)gSEB81mgwaGgbAl`r+=dg#}t=hUR^+_~n_qTtDi-^YgD
z#D(V5q}{`W+`xk0$cWs(faK1gsoHLy(PX^vg!0`^^V>}K<977gNQ<j%pw(yY&=l^^
z5bw?o^V&-A%?rQBx1!f+zBxuV00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0_90WK~zY`?b6v(+E5e+U>k6&Tea1i1BihN$R<*($RcX1MKFRGK$JxVf+$KW
zhy}F%>%F-wiB95-Go4N!ybt#&-#z)Ab9#DS&%Zx!2<}MUVuX;%<?>#luYX`*kQjU`
zlMN9=eZ#{e#K`E_*gHa$$j8UW<wXC)#Ke2z!{p?ojOaytm>8X!l8q66sme$E&ysih
zt7&qYC!kO&l?v`wDPTseR?osLMNt~iXti1mw7()hkpiNeo10UDQm51DL2WP?=7BPr
zOlHtpEEel?M1_c&BSs1+lgXro1&hV@3{im|lX;^N5wpo;(ZZt5X0wYEg;J%O;ZuCZ
zDHa^yu-ffQqT~y3NAec0qSvcYm3fLnRm^78CC#GM>VRdZ)43x4V<?4zPhsh%aJt-X
zk2q25T8%Zf8@mWro6YM4r`x?MMbIodjcv`s?sd5kS@roO$Sh8w!6_Vq6e}Lj+B#1*
zzIONK8}5wc-65M>-;p8xoPq5?Ah6xNgTWoxp=o*tc0-}?Ubo6-WNU*YN&h}F5MZKA
z00PlyGyuU^EFJ{<;NXCUP$H2C^W-qHdF1!+CzB*1Q6`E>EC|7PJWeB$N`)YtNT)MA
zA&*Z`6(sm7u~>_s_vus$y&p~s$r0`tnG|LTK25Qg$z)ESkQa5#FPw|RYPB!JQhOF_
z*>zaY<#Kt*vn-njQ6gLqkFQY3Lhh_sJO}ooRAReiUr6j;FJ6yTv>w?);W7ugV)23n
zwp6+*OAwdK?L_bUd~1-sJUh$5dGP|1D*;IgiByVwxm@mMIr;J89XtMch<EH7&xM=C
zF6@TGtyxq~;j~(<-oQ<*R=ep`xsCif<aX?uGe9#bCe)sFre3dCpxS6Q@1S;ne_!j6
z-?x#6<TXBM$LJK(U5`q=-l)P|v)QaYCdl9&YgGv`2__7gbiIy9qk#$c@nh0%Tq{c_
zMWvPEu9M<XmiBo`$6n9>cm4nf0rwvzb1L2d001R)MObuXVRU6WV{&C-bY%cCFflMK
zF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGP
kFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f_kQ{fB*mh

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_setop.png b/web/pgadmin/misc/static/explain/img/ex_setop.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3a9b1983b5b47c96ae910e73ab4260ae9c28b73
GIT binary patch
literal 1143
zcmV--1c>{IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002E
zxzL`V(1?rCc6!ljZ_&-ct;w>B#-wM%nNGrzJd>5te}mbYo#C;w>b<`0$jI=)!tS)S
z>Yk$FfrZ(}s&>qySJ}LM<k6by-Kg#6v*XQ`qNLELspq@A?YOz?x47%Bu<78)j_l>D
z>*J&8;hW6M%aoVjwzuoBvgxw3>bkq^zQOKonbT;Cw%F6E=-!y;+?VRzrRLP5*SC1B
zujqQE*JQ2NWs|?=+Lq|snys$rs;uaQsoGkq)?dWwX2$7un78KHmFC!#<=2zt){@q>
zb)U22NtVh}z2-Hy=1RThb;#&boz14Or{vX=<kXSo)|jNH<fy9WTeRLOv*bFs<#o5*
zUyi-x(~#rRkKMwEAh6_t&FbUOj<dAt7_H;t&Wz#Ai>j^WVVTb!u;hfp;!=vh;meBO
z#*ekO>Wt0lK!(EL%8BLApgM`e53A#l)9m2Mh~LPG-^YjE#f9R_n%=~P-olRE!h_qr
zgxtV_;K!5Guy4}DpWelaCqTqQ00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0xn5JK~zY`?bK^i+CUTrVD7hwTp9u~3K&4efQ155Dpjm4f~c)k&`L{%-n1>)
z8Vm%m{&hD%k|mfKr!(y*&NpUw=DfQ(dlZUEP3k|EQl-{twHmcbIry#98;mBC(V*AK
z0c6TNZLwNy_D3iJkj{ZQUHHrlPB<~gy=WGlv$#E8ug7AuTIbPOJx2O`et#ek@cVEQ
z(~p4#WO2zm9bC3Ac_=qPq43IMANT)2Boc{6p2Xsg1qngSR4_5mlTc_inS9EFXf&02
zhC3aSREyMFdQBwH*EcfBO%4E&da?EL)fS!|$)-f@i8MsEbNQXZ?%w{O1t(s=IdEa{
z9UyO)4`C8M+9{SY$0rd1ygNNTC_~YdQ=T+TwsVE|#Zvym-aY_BQK?j7P#cx`;~Y#@
z*NbPxQepRaGbMm(wdRV8r%``OjF8VqUpSKa`fZqr1GRJF!XOaM_w)$K<<I)$$4{|E
z?Yv%V7zLtrAHsyXEd1ig#_vn9Mg5AVV<tJ`7D*pe`=HruLPQdb$`$xve5NY^u4%^j
zfaCL-0Jv_EZNmdDS!~f_Sppgv`A=CNwrBy-V&I993`~e0m^z<{1x0q?S(Y2i<{%R%
zy5-);Sjr4PEwt!%S>{IaFeOjS2A<dne{2A{WhN_mVq*{45?=X3(8Ek!_M&$)_K+p(
zBEhYTPJ<wC!A;%vn9e=xqQ8_-b8vJi#oJ7y!L+k7OG)t;t8Y@1`X_Y<dEniZp!V7a
z0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFU
zC3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ov
JPDHLkV1j^qN`L?W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_sort.png b/web/pgadmin/misc/static/explain/img/ex_sort.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d46fd34beee098f997529bf0b7714c52232a2df
GIT binary patch
literal 1157
zcmY+Ce>l?#9LKkGD0JGb!<|bOQ+Me`PIBs@X131ubm*x$mvXaab;*yACb5J^<yUCU
z=?G6fnfz!u<i}*hkYy7xW3|nXjiOR_@wspHSATpy@8|t_KCjR7c|Ol4eIL!oZ1p#*
zQ7Dudg-rBA)cHdg8z6b}L>w1|LZ92`8{m!DQ{|nmX`=An**#5DzO7UKZPUMtXP6Il
z5fWW|pN=clXUJv+$_1mFs&#^Aj(62w4Qg7mCQzhhh$nO8bCLPQz+!<`3u9yCLG%bb
z?15U4$&kq)mBQ#~6BITAuO9LqLIsSB)IkpP_6i{rRI10&2s?LDwdh7Z8fJ@NCJm;!
zFd4t3ITfD?Q+k-tfKm+#6{x2Ho*jVkUqDF&&9OlR$ex0982Sc5B7<QifWi&bjP3!r
zaEsx0$?8cB`nK#iITTstVily{c^X+e5er>i<q%g4k%ho6fRJ1W5&*YE99$-0RE<W}
zX*u<}b08K&QxnwJgHU)0{BOZo5Q%sYYAS}-7f?P0MUzmdg?qD*qJqS6;3y$x4DvM)
zseqCx$W=qd3>dXTM?bXpLR$}rBv8e3)XBr99y8x4qpF)h&;TP34lIsGOB-m{TFz%8
zmqJJT(fm=Knjt1)R-Gi%qYx#33{uyzT`1HF4+_yMfZVAcm^py)vTU8m)18%`sKnhj
z*<uksYUi1eVOfJKPs>_|AHuUzvW>}&>XtFnD#12=5jSD-l}O2Xy6AEAsv_w~<-a~=
zR<a9*e9KC+;FgGmLH*CMbBgT*1FGqRFG(blkIk<5_s=(bzrrV0ONLT{JvvNXj&wR)
z>7XZDI#BoKl9GL|_MWroq;CqTDQRU3U&c7L*{b^OT#I7a=4*Z|__`WB7ft(h<#lmL
zwVU<juuHW=)EJqm7n3feC-+#%HCxw=uD|m{yYOyl`E(|KL();vVdHgcQY}`jeA$Kz
zd^GU|aTDfZM~Hc9Sy{U?g_H0ClY?C)4vFx^B<8H|$3?8%P+fzuPdEAthoRteGj^qv
zp2y-%Cb5#6EEi(d{<;x1@Ul9Xj(^`H-CwfeY>d+eFuzNvy3((g8#?Rm4)+uXRZ{0S
zek&Lj2&|vqTX>h8z4p+Yu5=*Wb*%X-%fX;6ZCeB97F{VJdvHeYIL!geO(qzY#fd~4
zgP|L%u1gYzNTmVY-#^*ix*}V#)w7h0IfJh~v<cg-uAtgBZSQdJ-+WSx^D$TOOB4TV
z&ougHZ<*gtEH4SK60NlsIlMIuW@DxNaj!)u>tp7<>2HN#d3gq@q^0Jug@L!_)33u3
zSD40j=<&?;yenlDmRu9KMFjKSyr<uxY0vOCm{D>(4e9y6VRvwCmgKfwx9qwK@bjUR
zis9q0OCHVF-XIovFSc|R*2(*Lx0j!tMXN^#U$e&f2a$)ekM=$o%{USr>i$P$C?Y6l
zf)l~f$=Q+M6yQv7ciHCd;_?IGCwBtDjc339GvLo~Mi}$-CxEcyBk-Zyo#6Tj=q}h_
PfdD9qH;q`i`*8CAb@5|F

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_subplan.png b/web/pgadmin/misc/static/explain/img/ex_subplan.png
new file mode 100644
index 0000000000000000000000000000000000000000..e13d7794e91fc0842702b0b34c1a9b61607dcf92
GIT binary patch
literal 1283
zcmV+e1^oJnP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002O
z!_bzQ(SL){ba>NdYtf>m<hHo#y}#_l#qYwz?#9UP$jI=?%J9p~@y*Wi&(QMG(el&N
z^Viq)tgh&<vFNF*=D4}*zQ66j!R@lN>5`V;n496cyz8c@(AnDc$Hlj+rEHy)MVE~(
zje!}GmC?n;b=BCB+u@bv?y2JJpyBGA(b0>*y>Y_8Zq(O`&Cz|**oW5HjmgV)&CP_q
zyl}CsSnBe)wXlcC#=6+uj;W$#*yDlM;eY7uxXQ|X(bI*zwrqx}WKxY8b*9HQh{34O
zXl={vL%r!uzvx%P>V?$sl-Tfk&g_TN?ycYR5Ub*n)$gj(YI2sLtk-VAk2du2$?)v9
z@9DJj?Z)lovFzcl@ae+p;+*H(mgLov>)x!v$huvZB(B<V=--*+(T?ies-Crnc&$CS
z>3-?jshY`Moz7yq?t<^;!0Fhh?&7_NyG?JLL&vIhi>qwE$G7O#rsmV5>)g5C#f9C%
zgW=4J+`xh5&Y;`Af8@=c4PV1400001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0=h{=K~zY`&6Md=+CUh_IT8h`QXz`eqXw2kLy;n)g4XJmdbG8avTB3Kp(v<$
z)p}e1_3p-?5Zu7nnNFV%o0)f?-@M1}Mx*)X((3dKs}!TxX;p}pR)f)GG8$jBwRd!w
z%`ZDUEmpIMqcggeuI?_I0%_~9**P5z(YJQL>Qz?8^x17DJq=;{x!wU~4cdV|%WIZ~
zu$^Xx5QDtK35c8yeo&)jELJMwVp*3&xINyX;bB4W`rcrMwbBt;yniI{)+-1?FfbY#
z<5dwki^CHb36sj4;gCDzP(xg7TRUOE_;`3?5(g$H!Vxb}L0qhf<BXVz0ua3qO*$hH
zHH5VoIL=<X3)B(ZiOt5=WWh)fsJ71<dhlKp3=LHn5NrmCg5jxYL1vmmz_>6q?R^LC
z+aGBz3y6${r7U>JZlAM>UNjEhXh=TF8Nd2bRuFzH;GTIO2?j%Mzk8N%fEdW$AV22w
zL@?s<g=QOEOV-}=<mI>0-wW-D+06_Mp*`e&BlRIa<9G3lpVBjaeSfJrI7i?75F?V;
zhba6A>5ka^!s61W*yk_H%U@$pB6W_~LOdQ{ip?)B&3~&x5><%OLCKO($?{#QQC=UB
z<Ren&Kr5@OYb)r7D5loYlSrKdt)|oIH6(6iGFem^Z!W)?&s9dPbVz|M5s8^hVH*(_
zo>gwAm@Mw(G_XV%g#!oHwzf7zl+6?hyND182=m3g{rzGd!crMCwGM*d8pU;Vu)TYD
zgz$MJ63OJ|-to!teyLO{m&;J$!1{@Z<P~R0Bq4++r@u~5@V88|)H+C(^;9Z*fR4`2
z&M%~1s%8btQZKa*97stu9$sEvoj**h=5i_>8k8**uFuf<<<-sY<GEC;&VjbCuSwwQ
z=Jy>1Q997kA*u1=_V(_Af+!tz^Qco}$N!-}nbr1E9_U2o0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1oA>oooOA

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_tid_scan.png b/web/pgadmin/misc/static/explain/img/ex_tid_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a6063a1af031c626802911fdca598983aff7d52
GIT binary patch
literal 1074
zcmV-21kL-2P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002d
z;PI2T-@Mf8{{H{L*6o?Q;ibal>hbyT^!nN5^vBrl+3WSI#^#K%-OAhV<Lvi^t=n~>
z)~3be%jEIR<?+4P?Qx#e)#LN7$>)Ek+0f?l*6Q@d;qbxV@5$rvYnjrc!{ox+?t7-#
zwb1FX%;=%P<Hg|a#oq4t`u(HD<awmm>Ezqh$F9h;kiMjKx|(3PkxRstJB*5mfq{XC
zhlh@ij+T~|prD|vtgO7eyv@zc+}zyo@bKZ`;laPVtDlO=oI}p1PuR6w-oa?$%4zD;
zYVFo~;L^J5>E!0-=GocV($dn%$jHFJz_hfqs;a7)nVF-bqn?|W>)wU&>3;X;Y4PG!
z_2EYK-Z;soXy4!8*4Eb0(9p`t%EiUS?d|R1)xgxbiOHZ?)2mYZ=33Can9k15#>U3O
z!^66|x{;BQ>gwv<&a=<QxxT)>xVX5tx3}Kg*UYkn%e$bxy}jh*<eZ$G=;-L1o13t(
zu%DlwudlDArKN?1g_M+(;^{T(00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0qIFZK~zY`?UY$l+E5sVm1q!Z0s&d05EXH$OJzw&f>9JvV-~<xtzpr&mJ)3#
zt^4}dJ;CwH5a3Q{crP>OnfJ;0@};Mzn|G656V%rsV(N#@1DyaC>j%>yg4)_VZtpi^
z4$R~na=AT7>*IZ1AL@sFZhUwc9|;5piB`Y>L|~&3j^krablejP42`#Fv4k9O$mI$;
zF%N;*>=WT2Y&Irr9@rh6L@)yJTD0r+VpD$OYqp`G5qH=Twobo1f&7qh2|N*)`RMCt
z5>^5=+dBb3rrVuclg!S|FML{Dj6`FxSe&F1G{YvB49;`QE2-7B^m-<<5!u}0xots|
z<ZO=2OPoHy%R7<P^ye?Td;4GaGY3ktP$(AJk|>G{P073tp-|<+t)p+>BR|ra<Kx}6
zp9P^(`c;(}RaK?3e4@NYvh3lhmf7D8Y$NrD70Xgh{acr1xh^r;=Ey0}bN}Z4ADn5#
z`7_Z35rLQn@fV!3T@cu3sYXRZOw?(q(U+=l1rYG!LIqTS4wb1%)v`)c*EjlN#riFI
zORLpVMG%CdCdSn7?sHM{I%^=7WY6#Nxs&Jv80AAeN<QWi_cvX!_a&yZ5Yzei!HOA(
z>8=<EC*^g;nl5r9OG%cY6BHS5^LNqB+sG4UmhG=%BJ%?P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$g8msoqyPW_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unique.png b/web/pgadmin/misc/static/explain/img/ex_unique.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb70480be8a4f49369d9a974ce9461284d1a4654
GIT binary patch
literal 1238
zcmV;{1S$K8P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002Z
z#KgUKcgI3Ps~Q@~u&}o&Ddc5l(9X`ss&&DSHnvqK%tl7dN=mayB!;PEQjHmZrCYPX
zru6a2@a(tm>b0~uF5<tRp~YT>vPSFWp6cS9>EWB(wt%)SEA8a5a-%iq-I&LARP5lb
z-DPF(<-exPW%BF9vLz+pz?JIUs&=Y7>DZ_2+_~o1l-8qo)LdNR&yAk7hpyUjc&<L=
z)RExHh~LMC=Fy_RTtmV?KjF-Z=+~yT<a*t~g5%7duOT6OvqIz1kK4U}xG^!fML3PY
zQ|HvA<j$bIJ3G^6X1VKs(35S+RaLw1gT_uymdstaH8rt4A-G~H%wuG_M@zb8Ws;TA
zW^2)iL34~eXwA;?y}#|h!R^Ar?#0IN$jR`_%<--<Hq^DqyuItEspqP!=dQ2mv9s#7
zx9hx}zpN}W)z$R0W_!S=%C9#);NbVg#O%qp*3r@OwTrE~yX?Nd?W<8{vvZEQtk|zV
zN|H5LgGPX=ey69T;D1YtlFETR00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*y&TK~zY`&6N9F(oh)3hs+U!xs^aDm&vUZae{%Mz>t6d6NpNgVwgoYFIh(3
zy7;emwsT-WPoD8Z&*S?eXP>?A`{nFI5QC)~(-9-qn4TPw8K(^79c_;qX;>yRGhrkM
zmgDZ;!yxpe#VVK0K;5Sag0tJFa13pkb~v0)7iDnswA^I|Fc`!t6CSVG?Df&|5A2Mc
z!yy3hc-(%<1rabC4w>YwJny3XHeUcC4=@N!Y=Y67XxiA1bfc8ZIN0SO&+|UgKRXvh
zUD&C47Dj2YfB}5L&;mV(@PZ&Ly2G|eBm_^E<{w2_CCX`sM~Fq1<B1`}C&Xea%&Iq;
zOraAtn&uS5sSu)=8Af8U2<cyQC6^Jh6isJYB}$L*I2jkk+%OUd1{d?;LMoL)BePGk
zEL;+-QB0IFnW0^bN?v7?8Vr}lB^C%K$n~|kVljbU#y!RTm7(NDzETiDp<uWw4oSsV
zYGtpCl;9o9;t`QtCtYGLmn$hZIa_^(yU7b1^-3X8DwULAxGEqp-;0rcUaP<81*7Gw
zBy{A<kv%cHxiuOAn5}W8+b#20LxQz!W_B9d5|f&{uUdPr_um+(w~{uGN$q#<KYaXj
zKp75?ByAo;$g$acEF)SxhLA%)^%){~kHK9IDF4aP7lhzE1{2$1^xKlu=tsIy%GLP0
z%U9sB({2~!v2K*jZ{34|T{`aYNox=7f;@(n{C?Iu7(mXM)eH3mdJKaAk6o%=pvP`N
zn!Bw|K76H)j6C-5UF2YX)XG2AV35adO6;r+Ja($S4C^s4@~?d5s&8bh#{hC(D_^Jx
z-eXexH}KeH7t!J|X}^=NZ1#fD;;{zEtA5=J<`#PF|I{BM#JXLtE~^v(001R)MObuX
zVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=
zI&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f(A@n
A@Bjb+

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unknown.png b/web/pgadmin/misc/static/explain/img/ex_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5d54f5c7b253978b96993ea260828a144395095
GIT binary patch
literal 1088
zcmV-G1i$-<P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-s00025
z!qAqu(2ljybgR*YvC*c#(8$fuxW>>^ozY{g+H$$zaJ}Pq#prd!=Xk~Eea7c~!sL0n
z;BT|sS)tRx%FtS*)o#A!TgB^2zUV@{=s>&YKf2~ayXQu|=uW=pS;FaL#_4Uz>V?ql
zgTmx#uG*ix&}^sCZn)r0!09iw<{+@-AFtyivE(VU<S(@3G`8hBxaB~(<wd*ZOTFe$
zzUOPl=~<%FSE18r!{{2W;}xvqRlw(3!RL9*>ypmuTB6fvyyGgg<`JvnU&H8?(CcKb
z+6}7WW5noy&+LiA<Yc7LR>0^9sp2xV<VL*aXU6Do$?BHZ?`E*vFt_Gl#_Mpx=3d3>
zQNZYfzTthY(P6#i1*qX$pwV}|;(pHSdCBU($<S58=^e1-60GB4s@Sl@&}p>ZUZ&P@
z%j|o{>5S9ub++Dj%<5;W*ml6=ipuD1!|15M&|t;sdd}^2y5K>(=xE66YQW}z$LM~@
z=wip~XUObp%Itj4?KV3{{{R300d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U
z00H_*L_t(Y$L-VoSJF@z2k>#Zm$&&66HySyHefJJgw$z(fRdI186mP%OejOc)GU0-
z>Thp&cTUGnWA_LBjqi^;_jC5#=Xowg_45ER0W(yCfW=C5-iGWBdSGy9_=%GlaUqi-
z7)9=}agTSxH_7@rUI+w3XtcKw!x1Su^>jKm6Hh#wotx+6KyX3qS=8e5Xfl=jOVUex
zCY$SJ(DF(?f1kYIGpjlM+Q3>O|MFk*N?zYUx};D{l{E6&w>hhkH|4iEy=IG*tr8}&
ziR$)Hxu#$u2i^f4va_4qyCc)=fD6L<KCfW%UbnKkRKg@awqsLAR5&PNlF7pVi-x=T
zA@#A_G0)+r?gu!`8IsoWZc$>r-g5o!YDdRuOx8EdI)ya=f(84cqtfc00Its*zP`{t
zvGm*-tI5)xw)yaY^JDU5HB1@DiXyUq6VF9xpNhu9hUnU)HhsCxm`TK0GodYv9AJ>K
zpwO8V$2+zpw@(TFp)QUR#cP=s&KF1o75*B>n>;TB;RT6Mw;+beHz%@@2+C4n@q3xe
zxX1(rB0fx-PLf_>Qk9e@SL44kfRHrYjx{x*Q0S;ZNGK6#A=nd5v?4T3cRC&Bgw()6
z&n4?oZ*z4HNy+sL?wE4ZCuHQu?RfR}=da`6SyFNV?OMnlOFxg0KS=w20#>Z+^Z)<=
zC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!q
zSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMn
GLSTY1nJ@bQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_update.png b/web/pgadmin/misc/static/explain/img/ex_update.png
new file mode 100644
index 0000000000000000000000000000000000000000..a45c53f83a0593704855bb8488871e8e8c9be701
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003#P)t-s00016
zTU$1W!3nA2a+aZzl9IuXHZry4b*9Hbyy(lKSW3X@*Qid%s&!q&>iw+&W5()h$LMIt
z>~72Kb<OPByMEiff7`u@<Gpj-zJT1nfd9QPd(rRQ!GeU*?%l(Kgwyce#Dw0(h2F-7
zjMVPm$A{m@h>_Lq;K_;M%!-%U@#D*$;?9iY%%0@Up5oAr<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp{Fb>D#L7+PCQ5nd;rD?A*BN;F{~+tnA&o>));H-n#7It?J{P?cclX;jZoAyXxef
z?BcKO;k@kQu<hls@8rJi=CbbQv+w1=@aDkp=(F$XweaY|@ae+v?6&di#PaRM^X<m-
z@W|M;qjdlP00DGTPE!Ct=GbNc000SaNLh0L01FcU01FcV0GgZ_0007=Nkl<ZNXPAy
z-B#LA5QQ<eL#ah;5tS;UMbwA_5<v)|fIxsm0YgCuG)2mv@BaclIp+XDLzfq>)m}Ik
z`zl|u_nwKx@;3iqJ}~+$R5OHe*le~9W_M+Eb)VV);&7ZYr@MQ57tF=s@$q-Y6tOKY
zFWZ<Eq^rzltJUgYHW0qY9ImfBj+s~b$~)|Np_(D^I2a5bC)(@vMljMeZ3shERr^4m
z0k9j9!To)$N40l*d;0*Il+U+8O{EeF5Mun>Zl6PH7^x9N(m>1S^C~n`i3##!Jnm*;
zhV*LqVXP05gphXrI;BEA5sz0XlFizcG0d`H|I-vdaf)Ui`bxFjrCB!Z-`K&F`2_lG
zW8HeL^u3$4uQLXA^nuZXrj20OZmUD+*A=A;U0IeplNZl1u(P(dwqr=q)KgrQK@JCl
z;>^E+p@=3)q}Ws)l=#)94014dCK_tO3=TIzaIq>bwt5*3%TPQ!V{>4cXY2w@vpi4H
z5p%f~7?%#4`cmO#jZGLtp*Uy@&3d0|w^~NK=oCe<1FcLZ-GKRfgiQARl7;%8Pr;%T
znUf=*08efhU<o1hL;@WKQ8^qA7C~fLHUsG+pZ6(3LA}y4RZ?t@Jvm83((Co|<_PjH
z|Dvj=XrUD8WCI!k&sR*5kA}v!pSNfJ6<IEJ=yr%Ul7estl~^=#V{VN2CpSP8r8wXr
z>T$a{bA%j*5fQuxo)|>jZr1{YRBA-{1CJ*HoZICJnIoNCF4LOJQi(<*B`9&BQ0dhg
zYo(KD3q<dDK30Jnj^of*LV-ZR+!#3wqPz#EaOo)mPN(zS97+20!yp>N#m*4FIU=Af
z#HdEyIudfZoB=prjNI`t<e#W9MZxd)7yi=N)=U1%xA~vu4`}JQAOw$Lx&QzG07*qo
IM6N<$g4z^C1ONa4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_values_scan.png b/web/pgadmin/misc/static/explain/img/ex_values_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..15b5ab4079cb8e2c015b4f5f10a3dbf8aa6410d3
GIT binary patch
literal 913
zcmV;C18)3@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-siMiW0
zh{12o?m@ljMZW2E&hL59?@+?(R>SIp)9{Ja@MXyCYRT+x%IuWb@tNB3d(Q2G(e0$&
z@u=SNj@0gw*6*+3^985jNWbYDuH#$9>o~XNP`>9?z~@=P=Z4YkYscts$mx~W?|Po8
zrNQK8meIkFHu36*?c-qX<6!dYhx6)(@8e(Z<X`jahxF@*@Z?|d<X`pchw|iK_Unh?
z@Av20E9lxQ=-Mjk+bZnaD(>4V@7pT#<zLt8_37Fw>)R^r+bZweD)8JY^yOdH>GbpD
zU-ji*_v?rF>xbLDiPY)z_T^vG==1U1D)QVa^V}-;<zLa~^7Py)_~l>q+$#9yU-|2Y
z`s|0!=JL+v^7-ap`|O6y<?;31D)-$g_}wY`=3mR?@%G&+`{rND<M8?2DahmS`Rs@M
z?1snV@W$fr{N`W%?1shR@B7{<{N5@3=3n>SDf-<h{^no8;O_n2DgNw+z~An<+wA_{
zDgWkQ)amoS-tDc{=`>ap0ssI20d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_
z00FH@L_t(Y$L-b0a)Lk*24Ek?nFomRW_gGUK~X^z7hFIQqoPPyTuOQNU}g{;#Yj~u
zhm?Jq%hywM|1~5M&-$-bf~P9QA@C)YD!#&4B$dhJ^6-HRlK><UDZb@$)Hi|e6h+aI
z7lL#eAd=5jtC&#LT8)Xk5M;BM1g#-eV_6OzX@=ukmluLU0gw}e6wC6MaC~zhM3C?#
zXpU=qVA33nA0Xiih4FYO5~N%P_eS3qM6pMZN(IEs1gO_R%)p!pq$!4J!B~W0dA{*T
zU_hti6P|CPal)1$2<_;=bi0@Y8wgGI=I8hOm;|k%FdTl$=-8HJWkw8nG`bFqIFuv>
z5m{ALg&>p3bzpku)=*JRlO)sW-M}cOX=((S&+i6irfAxdAd5xpz^qoW1LG6e7Dc)D
zP+)8u6H)Rf`%_@fM3(#wc;Boj%jm#tw?0u-yn)kXbuBtDXA-pbh^`yxxHJr711}%G
z-3~o|;r(g)dX0&(b{s2Az~Sh+#{;pMQ)_F3i9?ViuwB>2PwdUWSk8WbK|JGSEO{p|
n8_Tjb@b)wQYk!?*{c(K(wSJimSdJ6(00000NkvXXu0mjfih%ky

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f858be1cee3f5d8f2b67f55d730e6d3f43e9b42
GIT binary patch
literal 2021
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png b/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..b51e0b3f4c61ba166b69bd7f32cb23dc8428429e
GIT binary patch
literal 1965
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exclude.png b/web/pgadmin/misc/static/explain/img/exclude.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd62eef6410d9315857d2e6d246770e8b65b8f47
GIT binary patch
literal 725
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47y|-)LR^8|wi}<ymTvVw_QrY1
zLC3?d^*UDhFWLA1|9`)wM`KREbvXQ5A-=(6%9fR9ZhU_6^4^pwS28lLWMw^=HS5>=
z_isLZP1<@%wQ`Qjk=K%*nNgG09JqAnY+~Zm($d$dsZT2^E~KQ~e)?Lsd6{J1L`ko#
z8QYH^XzhAZSorVPuP+-md|tBT-`~HFYif>6nkJPuNuqp?YIOD56Ib^8`rV6+d^K<0
zzkmPUtXQ!pGFqZ!hGg3swe*g4XK!wIaz5qe_HfFSrwbQu^AC`0S|K@Qk7Uk-*~hPM
zEhsq<82I=1?;BI6UhV1m_vg>S@bIm*jgk|0NG?2Of9R!T^@7Cpr%znEwc5~Vm%sly
zUEQ5NKC5l*c3rqFx8Q{2nrn7@9?A4<w%>5&&c`3`Z{FOOoV;9Hdxfs<!JM4Ox9?6m
zenWfBW$A4XY?hzYUUH^<`Bv54PXhWDq@H_cwegx-ao6F7A05CjWh@Eu3ubV5b|VeM
zN%D4g;b^-zwF=1LEbxdd2GSm2>~=ES4#-&V>Eak7aXC5R07H*Y2Gbdx45l?XZ+LiQ
zWMmY~)Ws(}c=qt=V{riyAu&Nw;pq&V9$ucOPn<e=Qd>hyv$&ZhB;@K9Q<JS*N=v?e
zImpD;=5|bN*|M}_&%~xBFluK@M_UI6S4XqEt8Zx7+`W4C?)K%=xA(7?_c&m$V4z{4
zVq&6Wqh!RTA|NX)Ek1w3j45*_&6>t1bmBw`i*--4qPDWM%7n?><t2V{=CjN{U1V)w
zV3-jtUHUai{xs0(swJ)wB`Jv|saDBFsX&Us$iUE0*8qr2Lkumf3@ojTjkFEStPBi}
hcbK)IXvob^$xN%ntzp~MJ}aOG22WQ%mvv4FO#q89Jv#sZ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension-sm.png b/web/pgadmin/misc/static/explain/img/extension-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..432d2f44231c1f88e787091060efc5125d80ef64
GIT binary patch
literal 423
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}QGic~E0BJDv+cp5$Qv6qzrVk7
zW3&9@vzBik`G5Ho|K-)uTicc2-1GkNGyU0x%oo?3fBedPak1gnHlr`^;(z|izO~Kb
z%lm|1zjI$+bN~H2|Mm{mcMpR9{3*J*S>f3^`;Si}e|^pQ`={jETDdE$#Qyy)dv><$
z|KAGswyocQwlbCk`2{mLJiCzw<Zu>vL>2>S4={E+nQaGT<aoL`hDcoQ?f2wsP!M2o
z7l{eDQ+~(c|9@BE84^1Kx361bY|Bu`qQb-J62DBxZ_<p5BE`pwna}6wxpa1%>e!Vz
zahdU=*X&ESFOpJE*&;e)`qd3*DpaT5u9JDLS%3P((k)@<ZbaLzV+h!Of4=?`KK92q
z=3fE@l4^--L`h0wNvc(HQ7VvPFfuSS&^0vDH82b@GO#i+wlXo*HZZj^FqrpFZxxD$
o-29Zxv`UBu152<5plTB<12c$*Q`1A&05vdpy85}Sb4q9e01}k2!2kdN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension.png b/web/pgadmin/misc/static/explain/img/extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c533347883fc1dec73bcb21b32fc54e3c31732
GIT binary patch
literal 996
zcmV<A0~`E_P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%`cO<%MQ2zLf_6I6&9|zdd99>-@a*2m!Junh7N)|bRf|-bx|zVboSKha_w(lG
z;mc-L4XVVcQjb)R!IoN>UTm6cdTlP+)48aeY_F(+!qvh)fIq$I$ak@QM1Demb2p-y
zZNIpg%*U?Z+|0eUmR?Q)@#@yLtA3u9XFhv8mdv0tb~C!Ok)4xckc3Wsa4?*aUD?pH
z@afX-<;s|gP-<HecxWLITMn+{ye)Dq{QLL&`11Mo?Dg>A^X%E{;lk+My5`xk<JGC)
z(V?J^Pa0qr9%URUZYXJ}aH`|EXsd7=V;MkvKHbcisFznYY9*e@qsi^kq0p+c;k~!#
z!?5MPqv5li->#6?p?1NAale3n)0NuDkgb|nMQ$vEwur;)&X(P%klLe(*qwsbn0(Ze
z&%lqBg+*3S1ZY?dnutTImr{GNfpyZ2etkM9XCNqTCtr9wihVOAV;iN_u6D(TK#xcf
zTn`v!7(bOtw4`b=XCQ>Lht=`it<|(#mtSF?W<QQcm$;T>m1BZ;G0*SUx8%WGt7&4B
zV3D+tb8RIwgF3Luu#bN^7G4v==*yVRp-_@kjmeoSdoaG!zSzWv!K`UHUKC!MVuZSj
zaIAJVdpAyF9vxyClh&g+jY8GGe$u;klYuouT@*iVD?w)>Y;7EsekVC?DRR1gZ_tQZ
zyKv99ZOyY~z@}84hBK{_JHn(!l6fLVbTnhFZfMSfV$6Lul0qhGC2*Z?NP<TvhBL~q
zUB|0c#ivccpg)Ro98G#WT*`P>$a6}(W;2>aI;l}Wz+t|fIeljlScFSfreaXWZ%f2#
zMZ#t~zg?k^OOAUphI1*4cr3V}Qns8&igY1@Y#D@Y7H?b$T8UCtsbn_2S%i2!rk!)h
z!>EaMCcvamyR?(W!KK8$qF<3+9BCaAUJ)8<9X6LmEss1DV->ZTL3e2=8EP9)hfaum
zLy>wXERsEdsDEo(3zmE$ex`jsyImG&7=LOOfol~TcO*%INEmJ%7i}B|YRn1%0004W
zQchC<K<3zH00001VoOIv0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW
z0~-xG4!&HBF8}}lR!KxbR2b7^U?3j2xS;VvLXbtwTz~>0W=QhVA)yH_FkL{}1;huk
zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<<OZkC{A-h<6}A<CjbDaP8qDe
SR_4e60000<MNUMnLSTaX-^L37

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extensions.png b/web/pgadmin/misc/static/explain/img/extensions.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed7ca97a33ef595f448b8621168d531f86d51d5
GIT binary patch
literal 1017
zcmV<V0|xwwP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%_fSk!MS^xZ(#^N3p?R&Od+_Yu$ibj%T^6RorB#bmn!1_5x}2JiUH9|m=i$p{
zRt>7esZx(rkHMB&m|kp}YkF-i+S9qHoNTYCfx^|oK7c>H>Bx7neMEjjfO9vZnQgzg
znaszo-`vc-ww7K_0rBeAwyS=gm1jPCJeJI$Gj=n&vyq*XV~~VSeQ+?GkzLu)v+(KC
z?&Zpui%@D?5_o7K5L*te<Gd|$E&TiU`}p$t_U!fW;PdR+>*2!a-MZ%4vg6gM;L)L=
zk53w479M3BDQ+lfsBo&|xoE3!8)F$jd_LXGn5dUmHEJcE$)m~b(xK3*v*EqB=fkk&
zzN6u@o!_pI*P(X7g>k=tf76xP$dIj?SVe9ugSLpn>&}+lsF2#DiP)Wj)|h<Mlh449
zm4!uCPy}dL4Vs8UtCv!Hv4M5cjedPPC}$ujZ6{xNJBocXBx4(;)vk8Mhd_@=5nK-#
zWf(t|O0=YEF=rrzvxn93+^yBLT$f*Ao@PIeN0+#kWR+urcQMcJ*SF-sT&rnflVFjw
zkaKM%G=n;@$*_-qITl_M!syGG&7n||RE^1*D|;}$)4tfmhrz6AIbIZAnqq{yi*T%V
zHhVWtV;&u18I#tdIE_NpzkbrYc9VfMLtPX<ZYx1&B5Z9Om3}8VZ7FiPes9o-TDx%1
zw{6X{Wx%FXorW{5k~_krMv{3VMsze|t!`+}gJR5mHj+XnY9(-;Zb*VhCx$c1uU*Hh
zRmG=Gz@R^havV*1JY33nR>*Toyk;|+MLMZbK)_+XojHAH5m<yvR;FT5#&1i+YDL0k
zJHK6_k4uhwGlp|1i+C)!pi;J+M~ZYIf@~RtZ5D4_30jF#R;gq*y;+2KJ*J&=$it|K
zbtb^1PrI~}#=)h;zM@}|T^wm05?&D+Y8^J0MJ<m!6k`>&nL&4HC>d%SP=`*4d_$3X
zCoGaZfT(|KS__tZB7UZQKD%8OXc&KL7J+LO8+Rm0f=C!{9T#mJ7UDFE00001bW%=J
z06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru+XEXB
z6Cp(Rbcp}}0B%V{K~xyiU68R2z#t3+nS(tt!3~hY^vGg42FL=`EENNm0{M!6-y1;)
zQxFazvL_cK*b<_okFg0d8Hc#V=Eh`)h+yZmplehnlNZEdtgQ@hX0CNeIWEpxw!oyN
ndc~tsp9TlidOzOC&-)*|KH)3pD2ngr00000NkvXXu0mjf<KNUz

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable-sm.png b/web/pgadmin/misc/static/explain/img/exttable-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..acd43cb8abab6ef38daf34ff6d7544153a11dacf
GIT binary patch
literal 612
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47`X#{LR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)^XY$8$A0G@_?f)(TiB-0o~u9DE`4h_
z@3s2Om(8dDdI7mh-fGNz8JxJ$(RuCl>$mUSyLbQo{YQ@;J%0Rn>9T9)mKA9e*Bv-;
z!q9k*y7s}c^63vAJOIkOd8|>@*q@f(clYkyWy_XDM0Trbo^<nAscUdhS#3sp`^5F@
z*S~u8DlvJNy7pxi^{qR09)J1r<?Gk4&!4|-WV%sNW#!y?Ya^o8sA?W7EL!yO<Hxse
z-}X<~s-}6=(71d4{Dn`SK5cATs;+%VU9<bdiL;%Z6I)tVJ$drv&6_t59=vw<+ErTC
zQw{Rh4~>~Gc%JJ_00swRNswPKgTu2MX+REVfk$L9koEv$x0Bg+K*j`57sn8Z%gG4}
zOa@Af9Sp+8+}hIC?Ck2|<}MBG)BEEc0z4vIL{>Gq^Qb83DJpS_PM9=p;?&9E0U<$c
zTq{<rTA7`ZmHE2Bfwko87hay;9$%lc3z#+)6+IK17IrPoEP&B6+Pa$ET|Heqd_}|T
z-R;Y#Z{6O%UOwc2y~2g=!)h89Dk9JL?rkX8V67z4`B96(W2)e}i60vRfNoPQag8WR
zNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTd
cHGouG8JIydoSGiG2B?9-)78&qol`;+0LVQH?EnA(

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable.png b/web/pgadmin/misc/static/explain/img/exttable.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d84035feea62d53d457481178b1bcbe9622f856
GIT binary patch
literal 793
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47;6K3LR^7d#i`GGFZ`c${`aEG
zUw7Sjd-49u`;VVLefIp>vuC#+K3#L}dGf{=db3}2pa0)_=3m{3Kc$C%<?Q>BxZ_*c
zrqAAMJ~%9YXTIQ#?wnU@GhViy`J27(hvTw$W((iw&U_Y~)1HyF{qEhn_wL=hfB*i2
z2M-=TeE8_mqsNaQ8yXiV>iW%}zd>1TV^GL$BjYL^{S(WU9oVt+prXnmSNFZkm#=*C
z<jL~o%k>TF^o@>PyZ-vst5*{zPLflcU|_QU>a|B#uU=JF?bJ2cziipY_3PKae*OB&
zm8%-sEqaEB)ikG?m>;=w=lPpAZ{EIr`|{<>zP<^HDjPJkuS}Y>_5J(zA3l8e^y$;P
zckfoNT)BGn>N#`fDXPqIbUAEeo3EjBs<&_JrcIlkKYzY=?-6CSwcft_U%!6i?2@ga
zbAH+K$Dcoc{`&RnzI`WD)Hj5LAKkcd)7Gt9lT#YiwN7;R-um|K+wv9LmDM(Tcy0Rl
z@#D5_+u{=nRW-JDc3)AL{8UY|HzVudq)BroPg$ri@$mwIO;dmoz*rLG7tG-B>_!@p
z!&%@FSq!8-z}W3%wjGdh+0(@_MB;LCLV^n;4-Zd|&l#OHId5d<%!#>Uvqyo^u8z@B
zF;Otku#nSp1DBm9qhscRhMqMtE)H>yfu51Bp}w)6&cZqimabjQ@@VyoS1+4cMR<97
zd#?KUoIRtfbCAhvS=cqRZDrs1`uxr%FfOPQ4>vF8_xIM%-_X$2-@kPI{CbCkfC7#P
z2U8Or8zmzxD>XAS4xIy%;`1lWcrs_sq*>GW#5zu&ICF}R^VG?+r}Y`QMZ`qYkF!mE
zGG*G-@CFvG)vNY8H+eN(zQmoCbx`Y7z#e{vFBY0Te&TE7f!<Rsag8WRNi0dVN-jzT
zQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTdHGouG8JIyd
UoSGiG2B?9-)78&qol`;+0Q{<O$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttables.png b/web/pgadmin/misc/static/explain/img/exttables.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5dfbd454ed33727bd1deeb87c62f8671f6a5a03
GIT binary patch
literal 662
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47^MPyLR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)(1uT&ufDx|_ipp)e^tkR=O6f)yz^Vw
zrq7<MKiDpPYdG(<`plR2?%iuX{nra9vgEDdjF%cSU*5le-z{*Hjoqv(SFS&L^yu;9
z$BCh54YV)SY`ps5!GohmFPT|vP}AIAUOws6@q1>vXAHE?^>wU&`SRt%hYy!6TNV*D
zT}|t>v;9UL?W?Amt7goYwSN8jSFc`8IQ7}h=Aef5HBHT(=Pz7){rdI(-PetE&Kqf+
zTC?J?x9>_dt@Dk|J3fB=`1bAF+~nQb+SeRSH>_B(;_1_;Rn;?8HFg`BEWUQ_zJ=bg
zri$H9o;-Q;=FOu=PwX9+YwPW<2KnxXa>IqjgMEvDp~F}b<QL4~@a#q!ki%Kv5m^kR
zJ;2!QWVRiUvDwqbF+}2W?0HwQLk<G27xhdW8XmB4zY~jTVks}b`QPk?8Z+O@ht}mE
zxDUR+@ZDiy$V$_wu1ha@gBJAuXUmw$IQdLNWLm+2wv7?FW$l-rva#qG$_eeUt7l;6
zYfsQPQ#3I{XWjwDPG7CLC)J97I!!#j(8XtG(AulgXTzi?C<q+hI5%(o`R%TodS@Ig
zFq!9*{+X5YhEDkFv(xUs<#&JSY%y0q+H23fcl??hCZB62%C0l7Vc_x-dugW}eiG<>
z)e_f;l9a@fRIB8oR3OD*WMF8ZYiOivU>IU#U}a)#Wn!XjU}|MxU@=ow4n;$5eoAIq
iB}9XPC0GMUwUvPxM8m1+p=*E|7(8A5T-G@yGywoZHZsuw

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/js/snap.svg-min.js b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
new file mode 100644
index 0000000..6567d19
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
@@ -0,0 +1,21 @@
+// Snap.svg 0.4.1
+//
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// build: 2015-04-13
+
+!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g=/\s*,\s*/,h="*",i=function(a,b){return a-b},j={n:{}},k=function(){for(var a=0,b=this.length;b>a;a++)if("undefined"!=typeof this[a])return this[a]},l=function(){for(var a=this.length;--a;)if("undefined"!=typeof this[a])return this[a]},m=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=m.listeners(a),j=0,n=[],o={},p=[],q=b;p.firstDefined=k,p.lastDefined=l,b=a,c=0;for(var r=0,s=h.length;s>r;r++)"zIndex"in h[r]&&(n.push(h[r].zIndex),h[r].zIndex<0&&(o[h[r].zIndex]=h[r]));for(n.sort(i);n[j]<0;)if(e=o[n[j++]],p.push(e.apply(d,g)),c)return c=f,p;for(r=0;s>r;r++)if(e=h[r],"zIndex"in e)if(e.zIndex==n[j]){if(p.push(e.apply(d,g)),c)break;do if(j++,e=o[n[j]],e&&p.push(e.apply(d,g)),c)break;while(e)}else o[e.zIndex]=e;else if(p.push(e.apply(d,g)),c)break;return c=f,b=q,p};m._events=j,m.listeners=function(a){var b,c,d,e,g,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,g=m.length;g>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[h]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},m.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(g),d=0,e=c.length;e>d;d++)!function(a){for(var c,d=a.split(f),e=j,g=0,h=d.length;h>g;g++)e=e.n,e=e.hasOwnProperty(d[g])&&e[d[g]]||(e[d[g]]={n:{}});for(e.f=e.f||[],g=0,h=e.f.length;h>g;g++)if(e.f[g]==b){c=!0;break}!c&&e.f.push(b)}(c[d]);return function(a){+a==+a&&(b.zIndex=+a)}},m.f=function(a){var b=[].slice.call(arguments,1);return function(){m.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},m.stop=function(){c=1},m.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},m.nts=function(){return b.split(f)},m.off=m.unbind=function(a,b){if(!a)return void(m._events=j={n:{}});var c=a.split(g);if(c.length>1)for(var d=0,i=c.length;i>d;d++)m.off(c[d],b);else{c=a.split(f);var k,l,n,d,i,o,p,q=[j];for(d=0,i=c.length;i>d;d++)for(o=0;o<q.length;o+=n.length-2){if(n=[o,1],k=q[o].n,c[d]!=h)k[c[d]]&&n.push(k[c[d]]);else for(l in k)k[e](l)&&n.push(k[l]);q.splice.apply(q,n)}for(d=0,i=q.length;i>d;d++)for(k=q[d];k.n;){if(b){if(k.f){for(o=0,p=k.f.length;p>o;o++)if(k.f[o]==b){k.f.splice(o,1);break}!k.f.length&&delete k.f}for(l in k.n)if(k.n[e](l)&&k.n[l].f){var r=k.n[l].f;for(o=0,p=r.length;p>o;o++)if(r[o]==b){r.splice(o,1);break}!r.length&&delete k.n[l].f}}else{delete k.f;for(l in k.n)k.n[e](l)&&k.n[l].f&&delete k.n[l].f}k=k.n}}},m.once=function(a,b){var c=function(){return m.unbind(a,c),b.apply(this,arguments)};return m.on(a,c)},m.version=d,m.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=m:"function"==typeof define&&define.amd?define("eve",[],function(){return m}):a.eve=m}(this),function(a,b){if("function"==typeof define&&define.amd)define(["eve"],function(c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("eve");module.exports=b(a,c)}else b(a,a.eve)}(window||this,function(a,b){var c=function(b){var c={},d=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},e=Array.isArray||function(a){return a instanceof Array||"[object Array]"==Object.prototype.toString.call(a)},f=0,g="M"+(+new Date).toString(36),h=function(){return g+(f++).toString(36)},i=Date.now||function(){return+new Date},j=function(a){var b=this;if(null==a)return b.s;var c=b.s-a;b.b+=b.dur*c,b.B+=b.dur*c,b.s=a},k=function(a){var b=this;return null==a?b.spd:void(b.spd=a)},l=function(a){var b=this;return null==a?b.dur:(b.s=b.s*a/b.dur,void(b.dur=a))},m=function(){var a=this;delete c[a.id],a.update(),b("mina.stop."+a.id,a)},n=function(){var a=this;a.pdif||(delete c[a.id],a.update(),a.pdif=a.get()-a.b)},o=function(){var a=this;a.pdif&&(a.b=a.get()-a.pdif,delete a.pdif,c[a.id]=a)},p=function(){var a,b=this;if(e(b.start)){a=[];for(var c=0,d=b.start.length;d>c;c++)a[c]=+b.start[c]+(b.end[c]-b.start[c])*b.easing(b.s)}else a=+b.start+(b.end-b.start)*b.easing(b.s);b.set(a)},q=function(){var a=0;for(var e in c)if(c.hasOwnProperty(e)){var f=c[e],g=f.get();a++,f.s=(g-f.b)/(f.dur/f.spd),f.s>=1&&(delete c[e],f.s=1,a--,function(a){setTimeout(function(){b("mina.finish."+a.id,a)})}(f)),f.update()}a&&d(q)},r=function(a,b,e,f,g,i,s){var t={id:h(),start:a,end:b,b:e,s:0,dur:f-e,spd:1,get:g,set:i,easing:s||r.linear,status:j,speed:k,duration:l,stop:m,pause:n,resume:o,update:p};c[t.id]=t;var u,v=0;for(u in c)if(c.hasOwnProperty(u)&&(v++,2==v))break;return 1==v&&d(q),t};return r.time=i,r.getById=function(a){return c[a]||null},r.linear=function(a){return a},r.easeout=function(a){return Math.pow(a,1.7)},r.easein=function(a){return Math.pow(a,.48)},r.easeinout=function(a){if(1==a)return 1;if(0==a)return 0;var b=.48-a/1.04,c=Math.sqrt(.1734+b*b),d=c-b,e=Math.pow(Math.abs(d),1/3)*(0>d?-1:1),f=-c-b,g=Math.pow(Math.abs(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},r.backin=function(a){if(1==a)return 1;var b=1.70158;return a*a*((b+1)*a-b)},r.backout=function(a){if(0==a)return 0;a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},r.elastic=function(a){return a==!!a?a:Math.pow(2,-10*a)*Math.sin(2*(a-.075)*Math.PI/.3)+1},r.bounce=function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b},a.mina=r,r}("undefined"==typeof b?function(){}:b),d=function(a){function c(a,b){if(a){if(a.nodeType)return w(a);if(e(a,"array")&&c.set)return c.set.apply(c,a);if(a instanceof s)return a;if(null==b)return a=y.doc.querySelector(String(a)),w(a)}return a=null==a?"100%":a,b=null==b?"100%":b,new v(a,b)}function d(a,b){if(b){if("#text"==a&&(a=y.doc.createTextNode(b.text||b["#text"]||"")),"#comment"==a&&(a=y.doc.createComment(b.text||b["#text"]||"")),"string"==typeof a&&(a=d(a)),"string"==typeof b)return 1==a.nodeType?"xlink:"==b.substring(0,6)?a.getAttributeNS(T,b.substring(6)):"xml:"==b.substring(0,4)?a.getAttributeNS(U,b.substring(4)):a.getAttribute(b):"text"==b?a.nodeValue:null;if(1==a.nodeType){for(var c in b)if(b[z](c)){var e=A(b[c]);e?"xlink:"==c.substring(0,6)?a.setAttributeNS(T,c.substring(6),e):"xml:"==c.substring(0,4)?a.setAttributeNS(U,c.substring(4),e):a.setAttribute(c,e):a.removeAttribute(c)}}else"text"in b&&(a.nodeValue=b.text)}else a=y.doc.createElementNS(U,a);return a}function e(a,b){return b=A.prototype.toLowerCase.call(b),"finite"==b?isFinite(a):"array"==b&&(a instanceof Array||Array.isArray&&Array.isArray(a))?!0:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||J.call(a).slice(8,-1).toLowerCase()==b}function f(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=f(a[c]));return b}function h(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function i(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),g=d.cache=d.cache||{},i=d.count=d.count||[];return g[z](f)?(h(i,f),c?c(g[f]):g[f]):(i.length>=1e3&&delete g[i.shift()],i.push(f),g[f]=a.apply(b,e),c?c(g[f]):g[f])}return d}function j(a,b,c,d,e,f){if(null==e){var g=a-c,h=b-d;return g||h?(180+180*D.atan2(-h,-g)/H+360)%360:0}return j(a,b,e,f)-j(c,d,e,f)}function k(a){return a%360*H/180}function l(a){return 180*a/H%360}function m(a){var b=[];return a=a.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g,function(a,c,d){return d=d.split(/\s*,\s*|\s+/),"rotate"==c&&1==d.length&&d.push(0,0),"scale"==c&&(d.length>2?d=d.slice(0,2):2==d.length&&d.push(0,0),1==d.length&&d.push(d[0],0,0)),b.push("skewX"==c?["m",1,0,D.tan(k(d[0])),1,0,0]:"skewY"==c?["m",1,D.tan(k(d[0])),0,1,0,0]:[c.charAt(0)].concat(d)),a}),b}function n(a,b){var d=ab(a),e=new c.Matrix;if(d)for(var f=0,g=d.length;g>f;f++){var h,i,j,k,l,m=d[f],n=m.length,o=A(m[0]).toLowerCase(),p=m[0]!=o,q=p?e.invert():0;"t"==o&&2==n?e.translate(m[1],0):"t"==o&&3==n?p?(h=q.x(0,0),i=q.y(0,0),j=q.x(m[1],m[2]),k=q.y(m[1],m[2]),e.translate(j-h,k-i)):e.translate(m[1],m[2]):"r"==o?2==n?(l=l||b,e.rotate(m[1],l.x+l.width/2,l.y+l.height/2)):4==n&&(p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.rotate(m[1],j,k)):e.rotate(m[1],m[2],m[3])):"s"==o?2==n||3==n?(l=l||b,e.scale(m[1],m[n-1],l.x+l.width/2,l.y+l.height/2)):4==n?p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.scale(m[1],m[1],j,k)):e.scale(m[1],m[1],m[2],m[3]):5==n&&(p?(j=q.x(m[3],m[4]),k=q.y(m[3],m[4]),e.scale(m[1],m[2],j,k)):e.scale(m[1],m[2],m[3],m[4])):"m"==o&&7==n&&e.add(m[1],m[2],m[3],m[4],m[5],m[6])}return e}function o(a){var b=a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||a.node.parentNode&&w(a.node.parentNode)||c.select("svg")||c(0,0),d=b.select("defs"),e=null==d?!1:d.node;return e||(e=u("defs",b.node).node),e}function p(a){return a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||c.select("svg")}function q(a,b,c){function e(a){if(null==a)return I;if(a==+a)return a;d(j,{width:a});try{return j.getBBox().width}catch(b){return 0}}function f(a){if(null==a)return I;if(a==+a)return a;d(j,{height:a});try{return j.getBBox().height}catch(b){return 0}}function g(d,e){null==b?i[d]=e(a.attr(d)||0):d==b&&(i=e(null==c?a.attr(d)||0:c))}var h=p(a).node,i={},j=h.querySelector(".svg---mgr");switch(j||(j=d("rect"),d(j,{x:-9e9,y:-9e9,width:10,height:10,"class":"svg---mgr",fill:"none"}),h.appendChild(j)),a.type){case"rect":g("rx",e),g("ry",f);case"image":g("width",e),g("height",f);case"text":g("x",e),g("y",f);break;case"circle":g("cx",e),g("cy",f),g("r",e);break;case"ellipse":g("cx",e),g("cy",f),g("rx",e),g("ry",f);break;case"line":g("x1",e),g("x2",e),g("y1",f),g("y2",f);break;case"marker":g("refX",e),g("markerWidth",e),g("refY",f),g("markerHeight",f);break;case"radialGradient":g("fx",e),g("fy",f);break;case"tspan":g("dx",e),g("dy",f);break;default:g(b,e)}return h.removeChild(j),i}function r(a){e(a,"array")||(a=Array.prototype.slice.call(arguments,0));for(var b=0,c=0,d=this.node;this[b];)delete this[b++];for(b=0;b<a.length;b++)"set"==a[b].type?a[b].forEach(function(a){d.appendChild(a.node)}):d.appendChild(a[b].node);var f=d.childNodes;for(b=0;b<f.length;b++)this[c++]=w(f[b]);return this}function s(a){if(a.snap in V)return V[a.snap];var b;try{b=a.ownerSVGElement}catch(c){}this.node=a,b&&(this.paper=new v(b)),this.type=a.tagName||a.nodeName;var d=this.id=S(this);if(this.anims={},this._={transform:[]},a.snap=d,V[d]=this,"g"==this.type&&(this.add=r),this.type in{g:1,mask:1,pattern:1,symbol:1})for(var e in v.prototype)v.prototype[z](e)&&(this[e]=v.prototype[e])}function t(a){this.node=a}function u(a,b){var c=d(a);b.appendChild(c);var e=w(c);return e}function v(a,b){var c,e,f,g=v.prototype;if(a&&"svg"==a.tagName){if(a.snap in V)return V[a.snap];var h=a.ownerDocument;c=new s(a),e=a.getElementsByTagName("desc")[0],f=a.getElementsByTagName("defs")[0],e||(e=d("desc"),e.appendChild(h.createTextNode("Created with Snap")),c.node.appendChild(e)),f||(f=d("defs"),c.node.appendChild(f)),c.defs=f;for(var i in g)g[z](i)&&(c[i]=g[i]);c.paper=c.root=c}else c=u("svg",y.doc.body),d(c.node,{height:b,version:1.1,width:a,xmlns:U});return c}function w(a){return a?a instanceof s||a instanceof t?a:a.tagName&&"svg"==a.tagName.toLowerCase()?new v(a):a.tagName&&"object"==a.tagName.toLowerCase()&&"image/svg+xml"==a.type?new v(a.contentDocument.getElementsByTagName("svg")[0]):new s(a):a}function x(a,b){for(var c=0,d=a.length;d>c;c++){var e={type:a[c].type,attr:a[c].attr()},f=a[c].children();b.push(e),f.length&&x(f,e.childNodes=[])}}c.version="0.4.0",c.toString=function(){return"Snap v"+this.version},c._={};var y={win:a.window,doc:a.window.document};c._.glob=y;{var z="hasOwnProperty",A=String,B=parseFloat,C=parseInt,D=Math,E=D.max,F=D.min,G=D.abs,H=(D.pow,D.PI),I=(D.round,""),J=Object.prototype.toString,K=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,L=(c._.separator=/[,\s]+/,/[\s]*,[\s]*/),M={hs:1,rg:1},N=/([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,O=/([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,P=/(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/gi,Q=0,R="S"+(+new Date).toString(36),S=function(a){return(a&&a.type?a.type:I)+R+(Q++).toString(36)},T="http://www.w3.org/1999/xlink",U="http://www.w3.org/2000/svg",V={};c.url=function(a){return"url('#"+a+"')"}}c._.$=d,c._.id=S,c.format=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return A(b).replace(a,function(a,b){return c(a,b,d)})}}(),c._.clone=f,c._.cacher=i,c.rad=k,c.deg=l,c.sin=function(a){return D.sin(c.rad(a))},c.tan=function(a){return D.tan(c.rad(a))},c.cos=function(a){return D.cos(c.rad(a))},c.asin=function(a){return c.deg(D.asin(a))},c.acos=function(a){return c.deg(D.acos(a))},c.atan=function(a){return c.deg(D.atan(a))},c.atan2=function(a){return c.deg(D.atan2(a))},c.angle=j,c.len=function(a,b,d,e){return Math.sqrt(c.len2(a,b,d,e))},c.len2=function(a,b,c,d){return(a-c)*(a-c)+(b-d)*(b-d)},c.closestPoint=function(a,b,c){function d(a){var d=a.x-b,e=a.y-c;return d*d+e*e}for(var e,f,g,h,i=a.node,j=i.getTotalLength(),k=j/i.pathSegList.numberOfItems*.125,l=1/0,m=0;j>=m;m+=k)(h=d(g=i.getPointAtLength(m)))<l&&(e=g,f=m,l=h);for(k*=.5;k>.5;){var n,o,p,q,r,s;(p=f-k)>=0&&(r=d(n=i.getPointAtLength(p)))<l?(e=n,f=p,l=r):(q=f+k)<=j&&(s=d(o=i.getPointAtLength(q)))<l?(e=o,f=q,l=s):k*=.5}return e={x:e.x,y:e.y,length:f,distance:Math.sqrt(l)}},c.is=e,c.snapTo=function(a,b,c){if(c=e(c,"finite")?c:10,e(a,"array")){for(var d=a.length;d--;)if(G(a[d]-b)<=c)return a[d]}else{a=+a;var f=b%a;if(c>f)return b-f;if(f>a-c)return b-f+a}return b},c.getRGB=i(function(a){if(!a||(a=A(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:Z};if(!(M[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=W(a)),!a)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};var b,d,f,g,h,i,j=a.match(K);return j?(j[2]&&(f=C(j[2].substring(5),16),d=C(j[2].substring(3,5),16),b=C(j[2].substring(1,3),16)),j[3]&&(f=C((h=j[3].charAt(3))+h,16),d=C((h=j[3].charAt(2))+h,16),b=C((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=B(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),f=B(i[2]),"%"==i[2].slice(-1)&&(f*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsb2rgb(b,d,f,g)):j[6]?(i=j[6].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsl2rgb(b,d,f,g)):(b=F(D.round(b),255),d=F(D.round(d),255),f=F(D.round(f),255),g=F(E(g,0),1),j={r:b,g:d,b:f,toString:Z},j.hex="#"+(16777216|f|d<<8|b<<16).toString(16).slice(1),j.opacity=e(g,"finite")?g:1,j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z}},c),c.hsb=i(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=i(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=i(function(a,b,c,d){if(e(d,"finite")){var f=D.round;return"rgba("+[f(a),f(b),f(c),+d.toFixed(2)]+")"}return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)});var W=function(a){var b=y.doc.getElementsByTagName("head")[0]||y.doc.getElementsByTagName("svg")[0],c="rgb(255, 0, 0)";return(W=i(function(a){if("red"==a.toLowerCase())return c;b.style.color=c,b.style.color=a;var d=y.doc.defaultView.getComputedStyle(b,I).getPropertyValue("color");return d==c?null:d}))(a)},X=function(){return"hsb("+[this.h,this.s,this.b]+")"},Y=function(){return"hsl("+[this.h,this.s,this.l]+")"},Z=function(){return 1==this.opacity||null==this.opacity?this.hex:"rgba("+[this.r,this.g,this.b,this.opacity]+")"},$=function(a,b,d){if(null==b&&e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&e(a,string)){var f=c.getRGB(a);a=f.r,b=f.g,d=f.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},_=function(a,b,d,f){a=D.round(255*a),b=D.round(255*b),d=D.round(255*d);var g={r:a,g:b,b:d,opacity:e(f,"finite")?f:1,hex:c.rgb(a,b,d),toString:Z};return e(f,"finite")&&(g.opacity=f),g};c.color=function(a){var b;return e(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):e(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):(e(a,"string")&&(a=c.getRGB(a)),e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&!("error"in a)?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1,a.error=1)),a.toString=Z,a},c.hsb2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var f,g,h,i,j;return a=a%360/60,j=c*b,i=j*(1-G(a%2-1)),f=g=h=c-j,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.hsl2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var f,g,h,i,j;return a=a%360/60,j=2*b*(.5>c?c:1-c),i=j*(1-G(a%2-1)),f=g=h=c-j/2,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.rgb2hsb=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=E(a,b,c),g=f-F(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:X}},c.rgb2hsl=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=E(a,b,c),h=F(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:Y}},c.parsePathString=function(a){if(!a)return null;var b=c.path(a);if(b.arr)return c.path.clone(b.arr);var d={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},f=[];return e(a,"array")&&e(a[0],"array")&&(f=c.path.clone(a)),f.length||A(a).replace(N,function(a,b,c){var e=[],g=b.toLowerCase();if(c.replace(P,function(a,b){b&&e.push(+b)}),"m"==g&&e.length>2&&(f.push([b].concat(e.splice(0,2))),g="l",b="m"==b?"l":"L"),"o"==g&&1==e.length&&f.push([b,e[0]]),"r"==g)f.push([b].concat(e));else for(;e.length>=d[g]&&(f.push([b].concat(e.splice(0,d[g]))),d[g]););}),f.toString=c.path.toString,b.arr=c.path.clone(f),f};var ab=c.parseTransformString=function(a){if(!a)return null;var b=[];return e(a,"array")&&e(a[0],"array")&&(b=c.path.clone(a)),b.length||A(a).replace(O,function(a,c,d){{var e=[];c.toLowerCase()}d.replace(P,function(a,b){b&&e.push(+b)}),b.push([c].concat(e))}),b.toString=c.path.toString,b};c._.svgTransform2string=m,c._.rgTransform=/^[a-z][\s]*-?\.?\d/i,c._.transform2matrix=n,c._unit2px=q;y.doc.contains||y.doc.compareDocumentPosition?function(a,b){var c=9==a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a==d||!(!d||1!=d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)for(;b;)if(b=b.parentNode,b==a)return!0;return!1};c._.getSomeDefs=o,c._.getSomeSVG=p,c.select=function(a){return a=A(a).replace(/([^\\]):/g,"$1\\:"),w(y.doc.querySelector(a))},c.selectAll=function(a){for(var b=y.doc.querySelectorAll(a),d=(c.set||Array)(),e=0;e<b.length;e++)d.push(w(b[e]));return d},setInterval(function(){for(var a in V)if(V[z](a)){var b=V[a],c=b.node;("svg"!=b.type&&!c.ownerSVGElement||"svg"==b.type&&(!c.parentNode||"ownerSVGElement"in c.parentNode&&!c.ownerSVGElement))&&delete V[a]}},1e4),s.prototype.attr=function(a,c){var d=this,f=d.node;if(!a){if(1!=f.nodeType)return{text:f.nodeValue};for(var g=f.attributes,h={},i=0,j=g.length;j>i;i++)h[g[i].nodeName]=g[i].nodeValue;return h}if(e(a,"string")){if(!(arguments.length>1))return b("snap.util.getattr."+a,d).firstDefined();var k={};k[a]=c,a=k}for(var l in a)a[z](l)&&b("snap.util.attr."+l,d,a[l]);return d},c.parse=function(a){var b=y.doc.createDocumentFragment(),c=!0,d=y.doc.createElement("div");if(a=A(a),a.match(/^\s*<\s*svg(?:\s|>)/)||(a="<svg>"+a+"</svg>",c=!1),d.innerHTML=a,a=d.getElementsByTagName("svg")[0])if(c)b=a;else for(;a.firstChild;)b.appendChild(a.firstChild);return new t(b)},c.fragment=function(){for(var a=Array.prototype.slice.call(arguments,0),b=y.doc.createDocumentFragment(),d=0,e=a.length;e>d;d++){var f=a[d];f.node&&f.node.nodeType&&b.appendChild(f.node),f.nodeType&&b.appendChild(f),"string"==typeof f&&b.appendChild(c.parse(f).node)}return new t(b)},c._.make=u,c._.wrap=w,v.prototype.el=function(a,b){var c=u(a,this.node);return b&&c.attr(b),c},s.prototype.children=function(){for(var a=[],b=this.node.childNodes,d=0,e=b.length;e>d;d++)a[d]=c(b[d]);return a},s.prototype.toJSON=function(){var a=[];return x([this],a),a[0]},b.on("snap.util.getattr",function(){var a=b.nt();a=a.substring(a.lastIndexOf(".")+1);var c=a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});return bb[z](c)?this.node.ownerDocument.defaultView.getComputedStyle(this.node,null).getPropertyValue(c):d(this.node,a)});var bb={"alignment-baseline":0,"baseline-shift":0,clip:0,"clip-path":0,"clip-rule":0,color:0,"color-interpolation":0,"color-interpolation-filters":0,"color-profile":0,"color-rendering":0,cursor:0,direction:0,display:0,"dominant-baseline":0,"enable-background":0,fill:0,"fill-opacity":0,"fill-rule":0,filter:0,"flood-color":0,"flood-opacity":0,font:0,"font-family":0,"font-size":0,"font-size-adjust":0,"font-stretch":0,"font-style":0,"font-variant":0,"font-weight":0,"glyph-orientation-horizontal":0,"glyph-orientation-vertical":0,"image-rendering":0,kerning:0,"letter-spacing":0,"lighting-color":0,marker:0,"marker-end":0,"marker-mid":0,"marker-start":0,mask:0,opacity:0,overflow:0,"pointer-events":0,"shape-rendering":0,"stop-color":0,"stop-opacity":0,stroke:0,"stroke-dasharray":0,"stroke-dashoffset":0,"stroke-linecap":0,"stroke-linejoin":0,"stroke-miterlimit":0,"stroke-opacity":0,"stroke-width":0,"text-anchor":0,"text-decoration":0,"text-rendering":0,"unicode-bidi":0,visibility:0,"word-spacing":0,"writing-mode":0};b.on("snap.util.attr",function(a){var c=b.nt(),e={};c=c.substring(c.lastIndexOf(".")+1),e[c]=a;var f=c.replace(/-(\w)/gi,function(a,b){return b.toUpperCase()}),g=c.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});bb[z](g)?this.node.style[f]=null==a?I:a:d(this.node,e)}),function(){}(v.prototype),c.ajax=function(a,c,d,f){var g=new XMLHttpRequest,h=S();if(g){if(e(c,"function"))f=d,d=c,c=null;else if(e(c,"object")){var i=[];for(var j in c)c.hasOwnProperty(j)&&i.push(encodeURIComponent(j)+"="+encodeURIComponent(c[j]));c=i.join("&")}return g.open(c?"POST":"GET",a,!0),c&&(g.setRequestHeader("X-Requested-With","XMLHttpRequest"),g.setRequestHeader("Content-type","application/x-www-form-urlencoded")),d&&(b.once("snap.ajax."+h+".0",d),b.once("snap.ajax."+h+".200",d),b.once("snap.ajax."+h+".304",d)),g.onreadystatechange=function(){4==g.readyState&&b("snap.ajax."+h+"."+g.status,f,g)},4==g.readyState?g:(g.send(c),g)}},c.load=function(a,b,d){c.ajax(a,function(a){var e=c.parse(a.responseText);d?b.call(d,e):b(e)})};var cb=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,h=e.clientLeft||d.clientLeft||0,i=b.top+(g.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(g.win.pageXOffset||e.scrollLeft||d.scrollLeft)-h;return{y:i,x:j}};return c.getElementByPoint=function(a,b){var c=this,d=(c.canvas,y.doc.elementFromPoint(a,b));if(y.win.opera&&"svg"==d.tagName){var e=cb(d),f=d.createSVGRect();f.x=a-e.x,f.y=b-e.y,f.width=f.height=1;var g=d.getIntersectionList(f,null);g.length&&(d=g[g.length-1])}return d?w(d):null},c.plugin=function(a){a(c,s,v,y,t)},y.win.Snap=c,c}(a||this);return d.plugin(function(d,e,f,g,h){function i(a,b){if(null==b){var c=!0;if(b=a.node.getAttribute("linearGradient"==a.type||"radialGradient"==a.type?"gradientTransform":"pattern"==a.type?"patternTransform":"transform"),!b)return new d.Matrix;b=d._.svgTransform2string(b)}else b=d._.rgTransform.test(b)?o(b).replace(/\.{3}|\u2026/g,a._.transform||""):d._.svgTransform2string(b),n(b,"array")&&(b=d.path?d.path.toString.call(b):o(b)),a._.transform=b;var e=d._.transform2matrix(b,a.getBBox(1));return c?e:void(a.matrix=e)}function j(a){function b(a,b){var c=q(a.node,b);c=c&&c.match(f),c=c&&c[2],c&&"#"==c.charAt()&&(c=c.substring(1),c&&(h[c]=(h[c]||[]).concat(function(c){var d={};d[b]=URL(c),q(a.node,d)})))}function c(a){var b=q(a.node,"xlink:href");b&&"#"==b.charAt()&&(b=b.substring(1),b&&(h[b]=(h[b]||[]).concat(function(b){a.attr("xlink:href","#"+b)})))}for(var d,e=a.selectAll("*"),f=/^\s*url\(("|'|)(.*)\1\)\s*$/,g=[],h={},i=0,j=e.length;j>i;i++){d=e[i],b(d,"fill"),b(d,"stroke"),b(d,"filter"),b(d,"mask"),b(d,"clip-path"),c(d);var k=q(d.node,"id");k&&(q(d.node,{id:d.id}),g.push({old:k,id:d.id}))}for(i=0,j=g.length;j>i;i++){var l=h[g[i].old];if(l)for(var m=0,n=l.length;n>m;m++)l[m](g[i].id)}}function k(a,b,c){return function(d){var e=d.slice(a,b);return 1==e.length&&(e=e[0]),c?c(e):e}}function l(a){return function(){var b=a?"<"+this.type:"",c=this.node.attributes,d=this.node.childNodes;if(a)for(var e=0,f=c.length;f>e;e++)b+=" "+c[e].name+'="'+c[e].value.replace(/"/g,'\\"')+'"';if(d.length){for(a&&(b+=">"),e=0,f=d.length;f>e;e++)3==d[e].nodeType?b+=d[e].nodeValue:1==d[e].nodeType&&(b+=u(d[e]).toString());a&&(b+="</"+this.type+">")}else a&&(b+="/>");return b}}var m=e.prototype,n=d.is,o=String,p=d._unit2px,q=d._.$,r=d._.make,s=d._.getSomeDefs,t="hasOwnProperty",u=d._.wrap;m.getBBox=function(a){if(!d.Matrix||!d.path)return this.node.getBBox();var b=this,c=new d.Matrix;if(b.removed)return d._.box();for(;"use"==b.type;)if(a||(c=c.add(b.transform().localMatrix.translate(b.attr("x")||0,b.attr("y")||0))),b.original)b=b.original;else{var e=b.attr("xlink:href");b=b.original=b.node.ownerDocument.getElementById(e.substring(e.indexOf("#")+1))}var f=b._,g=d.path.get[b.type]||d.path.get.deflt;try{return a?(f.bboxwt=g?d.path.getBBox(b.realPath=g(b)):d._.box(b.node.getBBox()),d._.box(f.bboxwt)):(b.realPath=g(b),b.matrix=b.transform().localMatrix,f.bbox=d.path.getBBox(d.path.map(b.realPath,c.add(b.matrix))),d._.box(f.bbox))}catch(h){return d._.box()}};var v=function(){return this.string};m.transform=function(a){var b=this._;if(null==a){for(var c,e=this,f=new d.Matrix(this.node.getCTM()),g=i(this),h=[g],j=new d.Matrix,k=g.toTransformString(),l=o(g)==o(this.matrix)?o(b.transform):k;"svg"!=e.type&&(e=e.parent());)h.push(i(e));for(c=h.length;c--;)j.add(h[c]);return{string:l,globalMatrix:f,totalMatrix:j,localMatrix:g,diffMatrix:f.clone().add(g.invert()),global:f.toTransformString(),total:j.toTransformString(),local:k,toString:v}}return a instanceof d.Matrix?(this.matrix=a,this._.transform=a.toTransformString()):i(this,a),this.node&&("linearGradient"==this.type||"radialGradient"==this.type?q(this.node,{gradientTransform:this.matrix}):"pattern"==this.type?q(this.node,{patternTransform:this.matrix}):q(this.node,{transform:this.matrix})),this},m.parent=function(){return u(this.node.parentNode)},m.append=m.add=function(a){if(a){if("set"==a.type){var b=this;return a.forEach(function(a){b.add(a)}),this}a=u(a),this.node.appendChild(a.node),a.paper=this.paper}return this},m.appendTo=function(a){return a&&(a=u(a),a.append(this)),this},m.prepend=function(a){if(a){if("set"==a.type){var b,c=this;return a.forEach(function(a){b?b.after(a):c.prepend(a),b=a}),this}a=u(a);var d=a.parent();this.node.insertBefore(a.node,this.node.firstChild),this.add&&this.add(),a.paper=this.paper,this.parent()&&this.parent().add(),d&&d.add()}return this},m.prependTo=function(a){return a=u(a),a.prepend(this),this},m.before=function(a){if("set"==a.type){var b=this;return a.forEach(function(a){var c=a.parent();b.node.parentNode.insertBefore(a.node,b.node),c&&c.add()}),this.parent().add(),this}a=u(a);var c=a.parent();return this.node.parentNode.insertBefore(a.node,this.node),this.parent()&&this.parent().add(),c&&c.add(),a.paper=this.paper,this},m.after=function(a){a=u(a);var b=a.parent();return this.node.nextSibling?this.node.parentNode.insertBefore(a.node,this.node.nextSibling):this.node.parentNode.appendChild(a.node),this.parent()&&this.parent().add(),b&&b.add(),a.paper=this.paper,this},m.insertBefore=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.insertAfter=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node.nextSibling),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.remove=function(){var a=this.parent();return this.node.parentNode&&this.node.parentNode.removeChild(this.node),delete this.paper,this.removed=!0,a&&a.add(),this},m.select=function(a){return u(this.node.querySelector(a))},m.selectAll=function(a){for(var b=this.node.querySelectorAll(a),c=(d.set||Array)(),e=0;e<b.length;e++)c.push(u(b[e]));return c},m.asPX=function(a,b){return null==b&&(b=this.attr(a)),+p(this,a,b)},m.use=function(){var a,b=this.node.id;return b||(b=this.id,q(this.node,{id:b})),a="linearGradient"==this.type||"radialGradient"==this.type||"pattern"==this.type?r(this.type,this.node.parentNode):r("use",this.node.parentNode),q(a.node,{"xlink:href":"#"+b}),a.original=this,a},m.clone=function(){var a=u(this.node.cloneNode(!0));return q(a.node,"id")&&q(a.node,{id:a.id}),j(a),a.insertAfter(this),a},m.toDefs=function(){var a=s(this);return a.appendChild(this.node),this},m.pattern=m.toPattern=function(a,b,c,d){var e=r("pattern",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,a=a.x),q(e.node,{x:a,y:b,width:c,height:d,patternUnits:"userSpaceOnUse",id:e.id,viewBox:[a,b,c,d].join(" ")}),e.node.appendChild(this.node),e},m.marker=function(a,b,c,d,e,f){var g=r("marker",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,e=a.refX||a.cx,f=a.refY||a.cy,a=a.x),q(g.node,{viewBox:[a,b,c,d].join(" "),markerWidth:c,markerHeight:d,orient:"auto",refX:e||0,refY:f||0,id:g.id}),g.node.appendChild(this.node),g};var w=function(a,b,d,e){"function"!=typeof d||d.length||(e=d,d=c.linear),this.attr=a,this.dur=b,d&&(this.easing=d),e&&(this.callback=e)};d._.Animation=w,d.animation=function(a,b,c,d){return new w(a,b,c,d)},m.inAnim=function(){var a=this,b=[];for(var c in a.anims)a.anims[t](c)&&!function(a){b.push({anim:new w(a._attrs,a.dur,a.easing,a._callback),mina:a,curStatus:a.status(),status:function(b){return a.status(b)},stop:function(){a.stop()}})}(a.anims[c]);return b},d.animate=function(a,d,e,f,g,h){"function"!=typeof g||g.length||(h=g,g=c.linear);var i=c.time(),j=c(a,d,i,i+f,c.time,e,g);return h&&b.once("mina.finish."+j.id,h),j},m.stop=function(){for(var a=this.inAnim(),b=0,c=a.length;c>b;b++)a[b].stop();return this},m.animate=function(a,d,e,f){"function"!=typeof e||e.length||(f=e,e=c.linear),a instanceof w&&(f=a.callback,e=a.easing,d=a.dur,a=a.attr);var g,h,i,j,l=[],m=[],p={},q=this;for(var r in a)if(a[t](r)){q.equal?(j=q.equal(r,o(a[r])),g=j.from,h=j.to,i=j.f):(g=+q.attr(r),h=+a[r]);var s=n(g,"array")?g.length:1;p[r]=k(l.length,l.length+s,i),l=l.concat(g),m=m.concat(h)}var u=c.time(),v=c(l,m,u,u+d,c.time,function(a){var b={};for(var c in p)p[t](c)&&(b[c]=p[c](a));q.attr(b)},e);return q.anims[v.id]=v,v._attrs=a,v._callback=f,b("snap.animcreated."+q.id,v),b.once("mina.finish."+v.id,function(){delete q.anims[v.id],f&&f.call(q)}),b.once("mina.stop."+v.id,function(){delete q.anims[v.id]}),q};var x={};m.data=function(a,c){var e=x[this.id]=x[this.id]||{};if(0==arguments.length)return b("snap.data.get."+this.id,this,e,null),e;
+if(1==arguments.length){if(d.is(a,"object")){for(var f in a)a[t](f)&&this.data(f,a[f]);return this}return b("snap.data.get."+this.id,this,e[a],a),e[a]}return e[a]=c,b("snap.data.set."+this.id,this,c,a),this},m.removeData=function(a){return null==a?x[this.id]={}:x[this.id]&&delete x[this.id][a],this},m.outerSVG=m.toString=l(1),m.innerSVG=l(),m.toDataURL=function(){if(a&&a.btoa){var b=this.getBBox(),c=d.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>',{x:+b.x.toFixed(3),y:+b.y.toFixed(3),width:+b.width.toFixed(3),height:+b.height.toFixed(3),contents:this.outerSVG()});return"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(c)))}},h.prototype.select=m.select,h.prototype.selectAll=m.selectAll}),d.plugin(function(a){function b(a,b,d,e,f,g){return null==b&&"[object SVGMatrix]"==c.call(a)?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,void(this.f=a.f)):void(null!=a?(this.a=+a,this.b=+b,this.c=+d,this.d=+e,this.e=+f,this.f=+g):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0))}var c=Object.prototype.toString,d=String,e=Math,f="";!function(c){function g(a){return a[0]*a[0]+a[1]*a[1]}function h(a){var b=e.sqrt(g(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}c.add=function(a,c,d,e,f,g){var h,i,j,k,l=[[],[],[]],m=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],n=[[a,d,f],[c,e,g],[0,0,1]];for(a&&a instanceof b&&(n=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),h=0;3>h;h++)for(i=0;3>i;i++){for(k=0,j=0;3>j;j++)k+=m[h][j]*n[j][i];l[h][i]=k}return this.a=l[0][0],this.b=l[1][0],this.c=l[0][1],this.d=l[1][1],this.e=l[0][2],this.f=l[1][2],this},c.invert=function(){var a=this,c=a.a*a.d-a.b*a.c;return new b(a.d/c,-a.b/c,-a.c/c,a.a/c,(a.c*a.f-a.d*a.e)/c,(a.b*a.e-a.a*a.f)/c)},c.clone=function(){return new b(this.a,this.b,this.c,this.d,this.e,this.f)},c.translate=function(a,b){return this.add(1,0,0,1,a,b)},c.scale=function(a,b,c,d){return null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d),this},c.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var f=+e.cos(b).toFixed(9),g=+e.sin(b).toFixed(9);return this.add(f,g,-g,f,c,d),this.add(1,0,0,1,-c,-d)},c.x=function(a,b){return a*this.a+b*this.c+this.e},c.y=function(a,b){return a*this.b+b*this.d+this.f},c.get=function(a){return+this[d.fromCharCode(97+a)].toFixed(4)},c.toString=function(){return"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")"},c.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},c.determinant=function(){return this.a*this.d-this.b*this.c},c.split=function(){var b={};b.dx=this.e,b.dy=this.f;var c=[[this.a,this.c],[this.b,this.d]];b.scalex=e.sqrt(g(c[0])),h(c[0]),b.shear=c[0][0]*c[1][0]+c[0][1]*c[1][1],c[1]=[c[1][0]-c[0][0]*b.shear,c[1][1]-c[0][1]*b.shear],b.scaley=e.sqrt(g(c[1])),h(c[1]),b.shear/=b.scaley,this.determinant()<0&&(b.scalex=-b.scalex);var d=-c[0][1],f=c[1][1];return 0>f?(b.rotate=a.deg(e.acos(f)),0>d&&(b.rotate=360-b.rotate)):b.rotate=a.deg(e.asin(d)),b.isSimple=!(+b.shear.toFixed(9)||b.scalex.toFixed(9)!=b.scaley.toFixed(9)&&b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate,b},c.toTransformString=function(a){var b=a||this.split();return+b.shear.toFixed(9)?"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]:(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[+b.dx.toFixed(4),+b.dy.toFixed(4)]:f)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:f)+(b.rotate?"r"+[+b.rotate.toFixed(4),0,0]:f))}}(b.prototype),a.Matrix=b,a.matrix=function(a,c,d,e,f,g){return new b(a,c,d,e,f,g)}}),d.plugin(function(a,c,d,e,f){function g(d){return function(e){if(b.stop(),e instanceof f&&1==e.node.childNodes.length&&("radialGradient"==e.node.firstChild.tagName||"linearGradient"==e.node.firstChild.tagName||"pattern"==e.node.firstChild.tagName)&&(e=e.node.firstChild,n(this).appendChild(e),e=l(e)),e instanceof c)if("radialGradient"==e.type||"linearGradient"==e.type||"pattern"==e.type){e.node.id||p(e.node,{id:e.id});var g=q(e.node.id)}else g=e.attr(d);else if(g=a.color(e),g.error){var h=a(n(this).ownerSVGElement).gradient(e);h?(h.node.id||p(h.node,{id:h.id}),g=q(h.node.id)):g=e}else g=r(g);var i={};i[d]=g,p(this.node,i),this.node.style[d]=t}}function h(a){b.stop(),a==+a&&(a+="px"),this.node.style.fontSize=a}function i(a){for(var b=[],c=a.childNodes,d=0,e=c.length;e>d;d++){var f=c[d];3==f.nodeType&&b.push(f.nodeValue),"tspan"==f.tagName&&b.push(1==f.childNodes.length&&3==f.firstChild.nodeType?f.firstChild.nodeValue:i(f))}return b}function j(){return b.stop(),this.node.style.fontSize}var k=a._.make,l=a._.wrap,m=a.is,n=a._.getSomeDefs,o=/^url\(#?([^)]+)\)$/,p=a._.$,q=a.url,r=String,s=a._.separator,t="";b.on("snap.util.attr.mask",function(a){if(a instanceof c||a instanceof f){if(b.stop(),a instanceof f&&1==a.node.childNodes.length&&(a=a.node.firstChild,n(this).appendChild(a),a=l(a)),"mask"==a.type)var d=a;else d=k("mask",n(this)),d.node.appendChild(a.node);!d.node.id&&p(d.node,{id:d.id}),p(this.node,{mask:q(d.id)})}}),function(a){b.on("snap.util.attr.clip",a),b.on("snap.util.attr.clip-path",a),b.on("snap.util.attr.clipPath",a)}(function(a){if(a instanceof c||a instanceof f){if(b.stop(),"clipPath"==a.type)var d=a;else d=k("clipPath",n(this)),d.node.appendChild(a.node),!d.node.id&&p(d.node,{id:d.id});p(this.node,{"clip-path":q(d.node.id||d.id)})}}),b.on("snap.util.attr.fill",g("fill")),b.on("snap.util.attr.stroke",g("stroke"));var u=/^([lr])(?:\(([^)]*)\))?(.*)$/i;b.on("snap.util.grad.parse",function(a){a=r(a);var b=a.match(u);if(!b)return null;var c=b[1],d=b[2],e=b[3];return d=d.split(/\s*,\s*/).map(function(a){return+a==a?+a:a}),1==d.length&&0==d[0]&&(d=[]),e=e.split("-"),e=e.map(function(a){a=a.split(":");var b={color:a[0]};return a[1]&&(b.offset=parseFloat(a[1])),b}),{type:c,params:d,stops:e}}),b.on("snap.util.attr.d",function(c){b.stop(),m(c,"array")&&m(c[0],"array")&&(c=a.path.toString.call(c)),c=r(c),c.match(/[ruo]/i)&&(c=a.path.toAbsolute(c)),p(this.node,{d:c})})(-1),b.on("snap.util.attr.#text",function(a){b.stop(),a=r(a);for(var c=e.doc.createTextNode(a);this.node.firstChild;)this.node.removeChild(this.node.firstChild);this.node.appendChild(c)})(-1),b.on("snap.util.attr.path",function(a){b.stop(),this.attr({d:a})})(-1),b.on("snap.util.attr.class",function(a){b.stop(),this.node.className.baseVal=a})(-1),b.on("snap.util.attr.viewBox",function(a){var c;c=m(a,"object")&&"x"in a?[a.x,a.y,a.width,a.height].join(" "):m(a,"array")?a.join(" "):a,p(this.node,{viewBox:c}),b.stop()})(-1),b.on("snap.util.attr.transform",function(a){this.transform(a),b.stop()})(-1),b.on("snap.util.attr.r",function(a){"rect"==this.type&&(b.stop(),p(this.node,{rx:a,ry:a}))})(-1),b.on("snap.util.attr.textpath",function(a){if(b.stop(),"text"==this.type){var d,e,f;if(!a&&this.textPath){for(e=this.textPath;e.node.firstChild;)this.node.appendChild(e.node.firstChild);return e.remove(),void delete this.textPath}if(m(a,"string")){var g=n(this),h=l(g.parentNode).path(a);g.appendChild(h.node),d=h.id,h.attr({id:d})}else a=l(a),a instanceof c&&(d=a.attr("id"),d||(d=a.id,a.attr({id:d})));if(d)if(e=this.textPath,f=this.node,e)e.attr({"xlink:href":"#"+d});else{for(e=p("textPath",{"xlink:href":"#"+d});f.firstChild;)e.appendChild(f.firstChild);f.appendChild(e),this.textPath=l(e)}}})(-1),b.on("snap.util.attr.text",function(a){if("text"==this.type){for(var c=this.node,d=function(a){var b=p("tspan");if(m(a,"array"))for(var c=0;c<a.length;c++)b.appendChild(d(a[c]));else b.appendChild(e.doc.createTextNode(a));return b.normalize&&b.normalize(),b};c.firstChild;)c.removeChild(c.firstChild);for(var f=d(a);f.firstChild;)c.appendChild(f.firstChild)}b.stop()})(-1),b.on("snap.util.attr.fontSize",h)(-1),b.on("snap.util.attr.font-size",h)(-1),b.on("snap.util.getattr.transform",function(){return b.stop(),this.transform()})(-1),b.on("snap.util.getattr.textpath",function(){return b.stop(),this.textPath})(-1),function(){function c(c){return function(){b.stop();var d=e.doc.defaultView.getComputedStyle(this.node,null).getPropertyValue("marker-"+c);return"none"==d?d:a(e.doc.getElementById(d.match(o)[1]))}}function d(a){return function(c){b.stop();var d="marker"+a.charAt(0).toUpperCase()+a.substring(1);if(""==c||!c)return void(this.node.style[d]="none");if("marker"==c.type){var e=c.node.id;return e||p(c.node,{id:c.id}),void(this.node.style[d]=q(e))}}}b.on("snap.util.getattr.marker-end",c("end"))(-1),b.on("snap.util.getattr.markerEnd",c("end"))(-1),b.on("snap.util.getattr.marker-start",c("start"))(-1),b.on("snap.util.getattr.markerStart",c("start"))(-1),b.on("snap.util.getattr.marker-mid",c("mid"))(-1),b.on("snap.util.getattr.markerMid",c("mid"))(-1),b.on("snap.util.attr.marker-end",d("end"))(-1),b.on("snap.util.attr.markerEnd",d("end"))(-1),b.on("snap.util.attr.marker-start",d("start"))(-1),b.on("snap.util.attr.markerStart",d("start"))(-1),b.on("snap.util.attr.marker-mid",d("mid"))(-1),b.on("snap.util.attr.markerMid",d("mid"))(-1)}(),b.on("snap.util.getattr.r",function(){return"rect"==this.type&&p(this.node,"rx")==p(this.node,"ry")?(b.stop(),p(this.node,"rx")):void 0})(-1),b.on("snap.util.getattr.text",function(){if("text"==this.type||"tspan"==this.type){b.stop();var a=i(this.node);return 1==a.length?a[0]:a}})(-1),b.on("snap.util.getattr.#text",function(){return this.node.textContent})(-1),b.on("snap.util.getattr.viewBox",function(){b.stop();var c=p(this.node,"viewBox");return c?(c=c.split(s),a._.box(+c[0],+c[1],+c[2],+c[3])):void 0})(-1),b.on("snap.util.getattr.points",function(){var a=p(this.node,"points");return b.stop(),a?a.split(s):void 0})(-1),b.on("snap.util.getattr.path",function(){var a=p(this.node,"d");return b.stop(),a})(-1),b.on("snap.util.getattr.class",function(){return this.node.className.baseVal})(-1),b.on("snap.util.getattr.fontSize",j)(-1),b.on("snap.util.getattr.font-size",j)(-1)}),d.plugin(function(a,b){var c=/\S+/g,d=String,e=b.prototype;e.addClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(h.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e||k.push(f);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.removeClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(k.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e&&k.splice(e,1);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.hasClass=function(a){var b=this.node,d=b.className.baseVal,e=d.match(c)||[];return!!~e.indexOf(a)},e.toggleClass=function(a,b){if(null!=b)return b?this.addClass(a):this.removeClass(a);var d,e,f,g,h=(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];for(d=0;f=h[d++];)e=k.indexOf(f),~e?k.splice(e,1):k.push(f);return g=k.join(" "),j!=g&&(i.className.baseVal=g),this}}),d.plugin(function(){function a(a){return a}function c(a){return function(b){return+b.toFixed(3)+a}}var d={"+":function(a,b){return a+b},"-":function(a,b){return a-b},"/":function(a,b){return a/b},"*":function(a,b){return a*b}},e=String,f=/[a-z]+$/i,g=/^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;b.on("snap.util.attr",function(a){var c=e(a).match(g);if(c){var h=b.nt(),i=h.substring(h.lastIndexOf(".")+1),j=this.attr(i),k={};b.stop();var l=c[3]||"",m=j.match(f),n=d[c[1]];if(m&&m==l?a=n(parseFloat(j),+c[2]):(j=this.asPX(i),a=n(this.asPX(i),this.asPX(i,c[2]+l))),isNaN(j)||isNaN(a))return;k[i]=a,this.attr(k)}})(-10),b.on("snap.util.equal",function(h,i){var j=e(this.attr(h)||""),k=e(i).match(g);if(k){b.stop();var l=k[3]||"",m=j.match(f),n=d[k[1]];return m&&m==l?{from:parseFloat(j),to:n(parseFloat(j),+k[2]),f:c(m)}:(j=this.asPX(h),{from:j,to:n(j,this.asPX(h,k[2]+l)),f:a})}})(-10)}),d.plugin(function(c,d,e,f){var g=e.prototype,h=c.is;g.rect=function(a,b,c,d,e,f){var g;return null==f&&(f=e),h(a,"object")&&"[object Object]"==a?g=a:null!=a&&(g={x:a,y:b,width:c,height:d},null!=e&&(g.rx=e,g.ry=f)),this.el("rect",g)},g.circle=function(a,b,c){var d;return h(a,"object")&&"[object Object]"==a?d=a:null!=a&&(d={cx:a,cy:b,r:c}),this.el("circle",d)};var i=function(){function a(){this.parentNode.removeChild(this)}return function(b,c){var d=f.doc.createElement("img"),e=f.doc.body;d.style.cssText="position:absolute;left:-9999em;top:-9999em",d.onload=function(){c.call(d),d.onload=d.onerror=null,e.removeChild(d)},d.onerror=a,e.appendChild(d),d.src=b}}();g.image=function(a,b,d,e,f){var g=this.el("image");if(h(a,"object")&&"src"in a)g.attr(a);else if(null!=a){var j={"xlink:href":a,preserveAspectRatio:"none"};null!=b&&null!=d&&(j.x=b,j.y=d),null!=e&&null!=f?(j.width=e,j.height=f):i(a,function(){c._.$(g.node,{width:this.offsetWidth,height:this.offsetHeight})}),c._.$(g.node,j)}return g},g.ellipse=function(a,b,c,d){var e;return h(a,"object")&&"[object Object]"==a?e=a:null!=a&&(e={cx:a,cy:b,rx:c,ry:d}),this.el("ellipse",e)},g.path=function(a){var b;return h(a,"object")&&!h(a,"array")?b=a:a&&(b={d:a}),this.el("path",b)},g.group=g.g=function(a){var b=this.el("g");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.svg=function(a,b,c,d,e,f,g,i){var j={};return h(a,"object")&&null==b?j=a:(null!=a&&(j.x=a),null!=b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),null!=e&&null!=f&&null!=g&&null!=i&&(j.viewBox=[e,f,g,i])),this.el("svg",j)},g.mask=function(a){var b=this.el("mask");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.ptrn=function(a,b,c,d,e,f,g,i){if(h(a,"object"))var j=a;else j={patternUnits:"userSpaceOnUse"},a&&(j.x=a),b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),j.viewBox=null!=e&&null!=f&&null!=g&&null!=i?[e,f,g,i]:[a||0,b||0,c||0,d||0];return this.el("pattern",j)},g.use=function(a){return null!=a?(a instanceof d&&(a.attr("id")||a.attr({id:c._.id(a)}),a=a.attr("id")),"#"==String(a).charAt()&&(a=a.substring(1)),this.el("use",{"xlink:href":"#"+a})):d.prototype.use.call(this)},g.symbol=function(a,b,c,d){var e={};return null!=a&&null!=b&&null!=c&&null!=d&&(e.viewBox=[a,b,c,d]),this.el("symbol",e)},g.text=function(a,b,c){var d={};return h(a,"object")?d=a:null!=a&&(d={x:a,y:b,text:c||""}),this.el("text",d)},g.line=function(a,b,c,d){var e={};return h(a,"object")?e=a:null!=a&&(e={x1:a,x2:c,y1:b,y2:d}),this.el("line",e)},g.polyline=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polyline",b)},g.polygon=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polygon",b)},function(){function d(){return this.selectAll("stop")}function e(a,b){var d=k("stop"),e={offset:+b+"%"};return a=c.color(a),e["stop-color"]=a.hex,a.opacity<1&&(e["stop-opacity"]=a.opacity),k(d,e),this.node.appendChild(d),this}function f(){if("linearGradient"==this.type){var a=k(this.node,"x1")||0,b=k(this.node,"x2")||1,d=k(this.node,"y1")||0,e=k(this.node,"y2")||0;return c._.box(a,d,math.abs(b-a),math.abs(e-d))}var f=this.node.cx||.5,g=this.node.cy||.5,h=this.node.r||0;return c._.box(f-h,g-h,2*h,2*h)}function h(a,c){function d(a,b){for(var c=(b-l)/(a-m),d=m;a>d;d++)g[d].offset=+(+l+c*(d-m)).toFixed(2);m=a,l=b}var e,f=b("snap.util.grad.parse",null,c).firstDefined();if(!f)return null;f.params.unshift(a),e="l"==f.type.toLowerCase()?i.apply(0,f.params):j.apply(0,f.params),f.type!=f.type.toLowerCase()&&k(e.node,{gradientUnits:"userSpaceOnUse"});var g=f.stops,h=g.length,l=0,m=0;h--;for(var n=0;h>n;n++)"offset"in g[n]&&d(n,g[n].offset);for(g[h].offset=g[h].offset||100,d(h,g[h].offset),n=0;h>=n;n++){var o=g[n];e.addStop(o.color,o.offset)}return e}function i(a,b,g,h,i){var j=c._.make("linearGradient",a);return j.stops=d,j.addStop=e,j.getBBox=f,null!=b&&k(j.node,{x1:b,y1:g,x2:h,y2:i}),j}function j(a,b,g,h,i,j){var l=c._.make("radialGradient",a);return l.stops=d,l.addStop=e,l.getBBox=f,null!=b&&k(l.node,{cx:b,cy:g,r:h}),null!=i&&null!=j&&k(l.node,{fx:i,fy:j}),l}var k=c._.$;g.gradient=function(a){return h(this.defs,a)},g.gradientLinear=function(a,b,c,d){return i(this.defs,a,b,c,d)},g.gradientRadial=function(a,b,c,d,e){return j(this.defs,a,b,c,d,e)},g.toString=function(){var a,b=this.node.ownerDocument,d=b.createDocumentFragment(),e=b.createElement("div"),f=this.node.cloneNode(!0);return d.appendChild(e),e.appendChild(f),c._.$(f,{xmlns:"http://www.w3.org/2000/svg"}),a=e.innerHTML,d.removeChild(d.firstChild),a},g.toDataURL=function(){return a&&a.btoa?"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(this))):void 0},g.clear=function(){for(var a,b=this.node.firstChild;b;)a=b.nextSibling,"defs"!=b.tagName?b.parentNode.removeChild(b):g.clear.call({node:b}),b=a}}()}),d.plugin(function(a,b){function c(a){var b=c.ps=c.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[K](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]}function d(a,b,c,d){return null==a&&(a=b=c=d=0),null==b&&(b=a.y,c=a.width,d=a.height,a=a.x),{x:a,y:b,width:c,w:c,height:d,h:d,x2:a+c,y2:b+d,cx:a+c/2,cy:b+d/2,r1:N.min(c,d)/2,r2:N.max(c,d)/2,r0:N.sqrt(c*c+d*d)/2,path:w(a,b,c,d),vb:[a,b,c,d].join(" ")}}function e(){return this.join(",").replace(L,"$1")}function f(a){var b=J(a);return b.toString=e,b}function g(a,b,c,d,e,f,g,h,j){return null==j?n(a,b,c,d,e,f,g,h):i(a,b,c,d,e,f,g,h,o(a,b,c,d,e,f,g,h,j))}function h(c,d){function e(a){return+(+a).toFixed(3)}return a._.cacher(function(a,f,h){a instanceof b&&(a=a.attr("d")),a=E(a);for(var j,k,l,m,n,o="",p={},q=0,r=0,s=a.length;s>r;r++){if(l=a[r],"M"==l[0])j=+l[1],k=+l[2];else{if(m=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6]),q+m>f){if(d&&!p.start){if(n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q),o+=["C"+e(n.start.x),e(n.start.y),e(n.m.x),e(n.m.y),e(n.x),e(n.y)],h)return o;p.start=o,o=["M"+e(n.x),e(n.y)+"C"+e(n.n.x),e(n.n.y),e(n.end.x),e(n.end.y),e(l[5]),e(l[6])].join(),q+=m,j=+l[5],k=+l[6];continue}if(!c&&!d)return n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q)}q+=m,j=+l[5],k=+l[6]}o+=l.shift()+l}return p.end=o,n=c?q:d?p:i(j,k,l[0],l[1],l[2],l[3],l[4],l[5],1)},null,a._.clone)}function i(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/O;return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}}function j(b,c,e,f,g,h,i,j){a.is(b,"array")||(b=[b,c,e,f,g,h,i,j]);var k=D.apply(null,b);return d(k.min.x,k.min.y,k.max.x-k.min.x,k.max.y-k.min.y)}function k(a,b,c){return b>=a.x&&b<=a.x+a.width&&c>=a.y&&c<=a.y+a.height}function l(a,b){return a=d(a),b=d(b),k(b,a.x,a.y)||k(b,a.x2,a.y)||k(b,a.x,a.y2)||k(b,a.x2,a.y2)||k(a,b.x,b.y)||k(a,b.x2,b.y)||k(a,b.x,b.y2)||k(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)}function m(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function n(a,b,c,d,e,f,g,h,i){null==i&&(i=1),i=i>1?1:0>i?0:i;for(var j=i/2,k=12,l=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;k>p;p++){var q=j*l[p]+j,r=m(q,a,c,e,g),s=m(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return j*o}function o(a,b,c,d,e,f,g,h,i){if(!(0>i||n(a,b,c,d,e,f,g,h)<i)){var j,k=1,l=k/2,m=k-l,o=.01;for(j=n(a,b,c,d,e,f,g,h,m);S(j-i)>o;)l/=2,m+=(i>j?1:-1)*l,j=n(a,b,c,d,e,f,g,h,m);return m}}function p(a,b,c,d,e,f,g,h){if(!(Q(a,c)<P(e,g)||P(a,c)>Q(e,g)||Q(b,d)<P(f,h)||P(b,d)>Q(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+Q(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+Q(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+Q(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+Q(f,h).toFixed(2)))return{x:l,y:m}}}}function q(a,b,c){var d=j(a),e=j(b);if(!l(d,e))return c?0:[];for(var f=n.apply(0,a),g=n.apply(0,b),h=~~(f/8),k=~~(g/8),m=[],o=[],q={},r=c?0:[],s=0;h+1>s;s++){var t=i.apply(0,a.concat(s/h));m.push({x:t.x,y:t.y,t:s/h})}for(s=0;k+1>s;s++)t=i.apply(0,b.concat(s/k)),o.push({x:t.x,y:t.y,t:s/k});for(s=0;h>s;s++)for(var u=0;k>u;u++){var v=m[s],w=m[s+1],x=o[u],y=o[u+1],z=S(w.x-v.x)<.001?"y":"x",A=S(y.x-x.x)<.001?"y":"x",B=p(v.x,v.y,w.x,w.y,x.x,x.y,y.x,y.y);if(B){if(q[B.x.toFixed(4)]==B.y.toFixed(4))continue;q[B.x.toFixed(4)]=B.y.toFixed(4);var C=v.t+S((B[z]-v[z])/(w[z]-v[z]))*(w.t-v.t),D=x.t+S((B[A]-x[A])/(y[A]-x[A]))*(y.t-x.t);C>=0&&1>=C&&D>=0&&1>=D&&(c?r++:r.push({x:B.x,y:B.y,t1:C,t2:D}))}}return r}function r(a,b){return t(a,b)}function s(a,b){return t(a,b,1)}function t(a,b,c){a=E(a),b=E(b);for(var d,e,f,g,h,i,j,k,l,m,n=c?0:[],o=0,p=a.length;p>o;o++){var r=a[o];if("M"==r[0])d=h=r[1],e=i=r[2];else{"C"==r[0]?(l=[d,e].concat(r.slice(1)),d=l[6],e=l[7]):(l=[d,e,d,e,h,i,h,i],d=h,e=i);for(var s=0,t=b.length;t>s;s++){var u=b[s];if("M"==u[0])f=j=u[1],g=k=u[2];else{"C"==u[0]?(m=[f,g].concat(u.slice(1)),f=m[6],g=m[7]):(m=[f,g,f,g,j,k,j,k],f=j,g=k);var v=q(l,m,c);if(c)n+=v;else{for(var w=0,x=v.length;x>w;w++)v[w].segment1=o,v[w].segment2=s,v[w].bez1=l,v[w].bez2=m;n=n.concat(v)}}}}}return n}function u(a,b,c){var d=v(a);return k(d,b,c)&&t(a,[["M",b,c],["H",d.x2+10]],1)%2==1}function v(a){var b=c(a);if(b.bbox)return J(b.bbox);if(!a)return d();a=E(a);for(var e,f=0,g=0,h=[],i=[],j=0,k=a.length;k>j;j++)if(e=a[j],"M"==e[0])f=e[1],g=e[2],h.push(f),i.push(g);else{var l=D(f,g,e[1],e[2],e[3],e[4],e[5],e[6]);h=h.concat(l.min.x,l.max.x),i=i.concat(l.min.y,l.max.y),f=e[5],g=e[6]}var m=P.apply(0,h),n=P.apply(0,i),o=Q.apply(0,h),p=Q.apply(0,i),q=d(m,n,o-m,p-n);return b.bbox=J(q),q}function w(a,b,c,d,f){if(f)return[["M",+a+ +f,b],["l",c-2*f,0],["a",f,f,0,0,1,f,f],["l",0,d-2*f],["a",f,f,0,0,1,-f,f],["l",2*f-c,0],["a",f,f,0,0,1,-f,-f],["l",0,2*f-d],["a",f,f,0,0,1,f,-f],["z"]];var g=[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]];return g.toString=e,g}function x(a,b,c,d,f){if(null==f&&null==d&&(d=c),a=+a,b=+b,c=+c,d=+d,null!=f)var g=Math.PI/180,h=a+c*Math.cos(-d*g),i=a+c*Math.cos(-f*g),j=b+c*Math.sin(-d*g),k=b+c*Math.sin(-f*g),l=[["M",h,j],["A",c,c,0,+(f-d>180),0,i,k]];else l=[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]];return l.toString=e,l}function y(b){var d=c(b),g=String.prototype.toLowerCase;if(d.rel)return f(d.rel);a.is(b,"array")&&a.is(b&&b[0],"array")||(b=a.parsePathString(b));var h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=b[0][1],j=b[0][2],k=i,l=j,m++,h.push(["M",i,j]));for(var n=m,o=b.length;o>n;n++){var p=h[n]=[],q=b[n];if(q[0]!=g.call(q[0]))switch(p[0]=g.call(q[0]),p[0]){case"a":p[1]=q[1],p[2]=q[2],p[3]=q[3],p[4]=q[4],p[5]=q[5],p[6]=+(q[6]-i).toFixed(3),p[7]=+(q[7]-j).toFixed(3);break;case"v":p[1]=+(q[1]-j).toFixed(3);break;case"m":k=q[1],l=q[2];default:for(var r=1,s=q.length;s>r;r++)p[r]=+(q[r]-(r%2?i:j)).toFixed(3)}else{p=h[n]=[],"m"==q[0]&&(k=q[1]+i,l=q[2]+j);for(var t=0,u=q.length;u>t;t++)h[n][t]=q[t]}var v=h[n].length;switch(h[n][0]){case"z":i=k,j=l;break;case"h":i+=+h[n][v-1];break;case"v":j+=+h[n][v-1];break;default:i+=+h[n][v-2],j+=+h[n][v-1]}}return h.toString=e,d.rel=f(h),h}function z(b){var d=c(b);if(d.abs)return f(d.abs);if(I(b,"array")&&I(b&&b[0],"array")||(b=a.parsePathString(b)),!b||!b.length)return[["M",0,0]];var g,h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=+b[0][1],j=+b[0][2],k=i,l=j,m++,h[0]=["M",i,j]);for(var n,o,p=3==b.length&&"M"==b[0][0]&&"R"==b[1][0].toUpperCase()&&"Z"==b[2][0].toUpperCase(),q=m,r=b.length;r>q;q++){if(h.push(n=[]),o=b[q],g=o[0],g!=g.toUpperCase())switch(n[0]=g.toUpperCase(),n[0]){case"A":n[1]=o[1],n[2]=o[2],n[3]=o[3],n[4]=o[4],n[5]=o[5],n[6]=+o[6]+i,n[7]=+o[7]+j;break;case"V":n[1]=+o[1]+j;break;case"H":n[1]=+o[1]+i;break;case"R":for(var s=[i,j].concat(o.slice(1)),t=2,u=s.length;u>t;t++)s[t]=+s[t]+i,s[++t]=+s[t]+j;h.pop(),h=h.concat(G(s,p));break;case"O":h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);break;case"U":h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));break;case"M":k=+o[1]+i,l=+o[2]+j;default:for(t=1,u=o.length;u>t;t++)n[t]=+o[t]+(t%2?i:j)}else if("R"==g)s=[i,j].concat(o.slice(1)),h.pop(),h=h.concat(G(s,p)),n=["R"].concat(o.slice(-2));else if("O"==g)h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);else if("U"==g)h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));else for(var v=0,w=o.length;w>v;v++)n[v]=o[v];if(g=g.toUpperCase(),"O"!=g)switch(n[0]){case"Z":i=+k,j=+l;break;case"H":i=n[1];break;case"V":j=n[1];break;case"M":k=n[n.length-2],l=n[n.length-1];default:i=n[n.length-2],j=n[n.length-1]}}return h.toString=e,d.abs=f(h),h}function A(a,b,c,d){return[a,b,c,d,c,d]}function B(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]}function C(b,c,d,e,f,g,h,i,j,k){var l,m=120*O/180,n=O/180*(+f||0),o=[],p=a._.cacher(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(b,c,-n),b=l.x,c=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(O/180*f),N.sin(O/180*f),(b-i)/2),r=(c-j)/2,s=q*q/(d*d)+r*r/(e*e);s>1&&(s=N.sqrt(s),d=s*d,e=s*e);var t=d*d,u=e*e,v=(g==h?-1:1)*N.sqrt(S((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*d*r/e+(b+i)/2,x=v*-e*q/d+(c+j)/2,y=N.asin(((c-x)/e).toFixed(9)),z=N.asin(((j-x)/e).toFixed(9));y=w>b?O-y:y,z=w>i?O-z:z,0>y&&(y=2*O+y),0>z&&(z=2*O+z),h&&y>z&&(y-=2*O),!h&&z>y&&(z-=2*O)}var A=z-y;if(S(A)>m){var B=z,D=i,E=j;z=y+m*(h&&z>y?1:-1),i=w+d*N.cos(z),j=x+e*N.sin(z),o=C(i,j,d,e,f,0,h,D,E,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),J=N.tan(A/4),K=4/3*d*J,L=4/3*e*J,M=[b,c],P=[b+K*G,c-L*F],Q=[i+K*I,j-L*H],R=[i,j];if(P[0]=2*M[0]-P[0],P[1]=2*M[1]-P[1],k)return[P,Q,R].concat(o);o=[P,Q,R].concat(o).join().split(",");for(var T=[],U=0,V=o.length;V>U;U++)T[U]=U%2?p(o[U-1],o[U],n).y:p(o[U],o[U+1],n).x;return T}function D(a,b,c,d,e,f,g,h){for(var i,j,k,l,m,n,o,p,q=[],r=[[],[]],s=0;2>s;++s)if(0==s?(j=6*a-12*c+6*e,i=-3*a+9*c-9*e+3*g,k=3*c-3*a):(j=6*b-12*d+6*f,i=-3*b+9*d-9*f+3*h,k=3*d-3*b),S(i)<1e-12){if(S(j)<1e-12)continue;l=-k/j,l>0&&1>l&&q.push(l)}else o=j*j-4*k*i,p=N.sqrt(o),0>o||(m=(-j+p)/(2*i),m>0&&1>m&&q.push(m),n=(-j-p)/(2*i),n>0&&1>n&&q.push(n));for(var t,u=q.length,v=u;u--;)l=q[u],t=1-l,r[0][u]=t*t*t*a+3*t*t*l*c+3*t*l*l*e+l*l*l*g,r[1][u]=t*t*t*b+3*t*t*l*d+3*t*l*l*f+l*l*l*h;return r[0][v]=a,r[1][v]=b,r[0][v+1]=g,r[1][v+1]=h,r[0].length=r[1].length=v+2,{min:{x:P.apply(0,r[0]),y:P.apply(0,r[1])},max:{x:Q.apply(0,r[0]),y:Q.apply(0,r[1])}}}function E(a,b){var d=!b&&c(a);if(!b&&d.curve)return f(d.curve);for(var e=z(a),g=b&&z(b),h={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},i={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},j=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"].concat(C.apply(0,[b.x,b.y].concat(a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e].concat(a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"].concat(B(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"].concat(B(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"].concat(A(b.x,b.y,a[1],a[2]));break;case"H":a=["C"].concat(A(b.x,b.y,a[1],b.y));break;case"V":a=["C"].concat(A(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"].concat(A(b.x,b.y,b.X,b.Y))}return a}),k=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)m[b]="A",g&&(n[b]="A"),a.splice(b++,0,["C"].concat(c.splice(0,6)));a.splice(b,1),r=Q(e.length,g&&g.length||0)}},l=function(a,b,c,d,f){a&&b&&"M"==a[f][0]&&"M"!=b[f][0]&&(b.splice(f,0,["M",d.x,d.y]),c.bx=0,c.by=0,c.x=a[f][1],c.y=a[f][2],r=Q(e.length,g&&g.length||0))},m=[],n=[],o="",p="",q=0,r=Q(e.length,g&&g.length||0);r>q;q++){e[q]&&(o=e[q][0]),"C"!=o&&(m[q]=o,q&&(p=m[q-1])),e[q]=j(e[q],h,p),"A"!=m[q]&&"C"==o&&(m[q]="C"),k(e,q),g&&(g[q]&&(o=g[q][0]),"C"!=o&&(n[q]=o,q&&(p=n[q-1])),g[q]=j(g[q],i,p),"A"!=n[q]&&"C"==o&&(n[q]="C"),k(g,q)),l(e,g,h,i,q),l(g,e,i,h,q);var s=e[q],t=g&&g[q],u=s.length,v=g&&t.length;h.x=s[u-2],h.y=s[u-1],h.bx=M(s[u-4])||h.x,h.by=M(s[u-3])||h.y,i.bx=g&&(M(t[v-4])||i.x),i.by=g&&(M(t[v-3])||i.y),i.x=g&&t[v-2],i.y=g&&t[v-1]}return g||(d.curve=f(e)),g?[e,g]:e}function F(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=E(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a}function G(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}var H=b.prototype,I=a.is,J=a._.clone,K="hasOwnProperty",L=/,?([a-z]),?/gi,M=parseFloat,N=Math,O=N.PI,P=N.min,Q=N.max,R=N.pow,S=N.abs,T=h(1),U=h(),V=h(0,1),W=a._unit2px,X={path:function(a){return a.attr("path")},circle:function(a){var b=W(a);return x(b.cx,b.cy,b.r)},ellipse:function(a){var b=W(a);return x(b.cx||0,b.cy||0,b.rx,b.ry)},rect:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height,b.rx,b.ry)},image:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height)},line:function(a){return"M"+[a.attr("x1")||0,a.attr("y1")||0,a.attr("x2"),a.attr("y2")]},polyline:function(a){return"M"+a.attr("points")},polygon:function(a){return"M"+a.attr("points")+"z"},deflt:function(a){var b=a.node.getBBox();return w(b.x,b.y,b.width,b.height)}};a.path=c,a.path.getTotalLength=T,a.path.getPointAtLength=U,a.path.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return V(a,b).end;var d=V(a,c,1);return b?V(d,b).end:d},H.getTotalLength=function(){return this.node.getTotalLength?this.node.getTotalLength():void 0},H.getPointAtLength=function(a){return U(this.attr("d"),a)},H.getSubpath=function(b,c){return a.path.getSubpath(this.attr("d"),b,c)},a._.box=d,a.path.findDotsAtSegment=i,a.path.bezierBBox=j,a.path.isPointInsideBBox=k,a.closest=function(b,c,e,f){for(var g=100,h=d(b-g/2,c-g/2,g,g),i=[],j=e[0].hasOwnProperty("x")?function(a){return{x:e[a].x,y:e[a].y}}:function(a){return{x:e[a],y:f[a]}},l=0;1e6>=g&&!l;){for(var m=0,n=e.length;n>m;m++){var o=j(m);if(k(h,o.x,o.y)){l++,i.push(o);break}}l||(g*=2,h=d(b-g/2,c-g/2,g,g))}if(1e6!=g){var p,q=1/0;for(m=0,n=i.length;n>m;m++){var r=a.len(b,c,i[m].x,i[m].y);q>r&&(q=r,i[m].len=r,p=i[m])}return p}},a.path.isBBoxIntersect=l,a.path.intersection=r,a.path.intersectionNumber=s,a.path.isPointInside=u,a.path.getBBox=v,a.path.get=X,a.path.toRelative=y,a.path.toAbsolute=z,a.path.toCubic=E,a.path.map=F,a.path.toString=e,a.path.clone=f}),d.plugin(function(a){var d=Math.max,e=Math.min,f=function(a){if(this.items=[],this.bindings={},this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)a[b]&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},g=f.prototype;g.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],a&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},g.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},g.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this},g.animate=function(d,e,f,g){"function"!=typeof f||f.length||(g=f,f=c.linear),d instanceof a._.Animation&&(g=d.callback,f=d.easing,e=f.dur,d=d.attr);var h=arguments;if(a.is(d,"array")&&a.is(h[h.length-1],"array"))var i=!0;var j,k=function(){j?this.b=j:j=this.b},l=0,m=this,n=g&&function(){++l==m.length&&g.call(this)
+};return this.forEach(function(a,c){b.once("snap.animcreated."+a.id,k),i?h[c]&&a.animate.apply(a,h[c]):a.animate(d,e,f,n)})},g.remove=function(){for(;this.length;)this.pop().remove();return this},g.bind=function(a,b,c){var d={};if("function"==typeof b)this.bindings[a]=b;else{var e=c||a;this.bindings[a]=function(a){d[e]=a,b.attr(d)}}return this},g.attr=function(a){var b={};for(var c in a)this.bindings[c]?this.bindings[c](a[c]):b[c]=a[c];for(var d=0,e=this.items.length;e>d;d++)this.items[d].attr(b);return this},g.clear=function(){for(;this.length;)this.pop()},g.splice=function(a,b){a=0>a?d(this.length+a,0):a,b=d(0,e(this.length-a,b));var c,g=[],h=[],i=[];for(c=2;c<arguments.length;c++)i.push(arguments[c]);for(c=0;b>c;c++)h.push(this[a+c]);for(;c<this.length-a;c++)g.push(this[a+c]);var j=i.length;for(c=0;c<j+g.length;c++)this.items[a+c]=this[a+c]=j>c?i[c]:g[c-j];for(c=this.items.length=this.length-=b-j;this[c];)delete this[c++];return new f(h)},g.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0;return!1},g.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},g.getBBox=function(){for(var a=[],b=[],c=[],f=[],g=this.items.length;g--;)if(!this.items[g].removed){var h=this.items[g].getBBox();a.push(h.x),b.push(h.y),c.push(h.x+h.width),f.push(h.y+h.height)}return a=e.apply(0,a),b=e.apply(0,b),c=d.apply(0,c),f=d.apply(0,f),{x:a,y:b,x2:c,y2:f,width:c-a,height:f-b,cx:a+(c-a)/2,cy:b+(f-b)/2}},g.clone=function(a){a=new f;for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},g.toString=function(){return"Snap‘s set"},g.type="set",a.Set=f,a.set=function(){var a=new f;return arguments.length&&a.push.apply(a,Array.prototype.slice.call(arguments,0)),a}}),d.plugin(function(a,c){function d(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}}function e(b,c,e){c=p(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];for(var f,g,h,i,l=Math.max(b.length,c.length),m=[],n=[],o=0;l>o;o++){if(h=b[o]||d(c[o]),i=c[o]||d(h),h[0]!=i[0]||"r"==h[0].toLowerCase()&&(h[2]!=i[2]||h[3]!=i[3])||"s"==h[0].toLowerCase()&&(h[3]!=i[3]||h[4]!=i[4])){b=a._.transform2matrix(b,e()),c=a._.transform2matrix(c,e()),m=[["m",b.a,b.b,b.c,b.d,b.e,b.f]],n=[["m",c.a,c.b,c.c,c.d,c.e,c.f]];break}for(m[o]=[],n[o]=[],f=0,g=Math.max(h.length,i.length);g>f;f++)f in h&&(m[o][f]=h[f]),f in i&&(n[o][f]=i[f])}return{from:k(m),to:k(n),f:j(m)}}function f(a){return a}function g(a){return function(b){return+b.toFixed(3)+a}}function h(a){return a.join(" ")}function i(b){return a.rgb(b[0],b[1],b[2])}function j(a){var b,c,d,e,f,g,h=0,i=[];for(b=0,c=a.length;c>b;b++){for(f="[",g=['"'+a[b][0]+'"'],d=1,e=a[b].length;e>d;d++)g[d]="val["+h++ +"]";f+=g+"]",i[b]=f}return Function("val","return Snap.path.toString.call(["+i+"])")}function k(a){for(var b=[],c=0,d=a.length;d>c;c++)for(var e=1,f=a[c].length;f>e;e++)b.push(a[c][e]);return b}function l(a){return isFinite(parseFloat(a))}function m(b,c){return a.is(b,"array")&&a.is(c,"array")?b.toString()==c.toString():!1}var n={},o=/[a-z]+$/i,p=String;n.stroke=n.fill="colour",c.prototype.equal=function(a,c){return b("snap.util.equal",this,a,c).firstDefined()},b.on("snap.util.equal",function(b,c){var d,q,r=p(this.attr(b)||""),s=this;if(l(r)&&l(c))return{from:parseFloat(r),to:parseFloat(c),f:f};if("colour"==n[b])return d=a.color(r),q=a.color(c),{from:[d.r,d.g,d.b,d.opacity],to:[q.r,q.g,q.b,q.opacity],f:i};if("viewBox"==b)return d=this.attr(b).vb.split(" ").map(Number),q=c.split(" ").map(Number),{from:d,to:q,f:h};if("transform"==b||"gradientTransform"==b||"patternTransform"==b)return c instanceof a.Matrix&&(c=c.toTransformString()),a._.rgTransform.test(c)||(c=a._.svgTransform2string(c)),e(r,c,function(){return s.getBBox(1)});if("d"==b||"path"==b)return d=a.path.toCubic(r,c),{from:k(d[0]),to:k(d[1]),f:j(d[0])};if("points"==b)return d=p(r).split(a._.separator),q=p(c).split(a._.separator),{from:d,to:q,f:function(a){return a}};var t=r.match(o),u=p(c).match(o);return t&&m(t,u)?{from:parseFloat(r),to:parseFloat(c),f:g(t)}:{from:this.asPX(b),to:this.asPX(b,c),f:f}})}),d.plugin(function(a,c,d,e){for(var f=c.prototype,g="hasOwnProperty",h=("createTouch"in e.doc),i=["click","dblclick","mousedown","mousemove","mouseout","mouseover","mouseup","touchstart","touchmove","touchend","touchcancel"],j={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},k=(function(a,b){var c="y"==a?"scrollTop":"scrollLeft",d=b&&b.node?b.node.ownerDocument:e.doc;return d[c in d.documentElement?"documentElement":"body"][c]}),l=function(){return this.originalEvent.preventDefault()},m=function(){return this.originalEvent.stopPropagation()},n=function(a,b,c,d){var e=h&&j[b]?j[b]:b,f=function(e){var f=k("y",d),i=k("x",d);if(h&&j[g](b))for(var n=0,o=e.targetTouches&&e.targetTouches.length;o>n;n++)if(e.targetTouches[n].target==a||a.contains(e.targetTouches[n].target)){var p=e;e=e.targetTouches[n],e.originalEvent=p,e.preventDefault=l,e.stopPropagation=m;break}var q=e.clientX+i,r=e.clientY+f;return c.call(d,e,q,r)};return b!==e&&a.addEventListener(b,f,!1),a.addEventListener(e,f,!1),function(){return b!==e&&a.removeEventListener(b,f,!1),a.removeEventListener(e,f,!1),!0}},o=[],p=function(a){for(var c,d=a.clientX,e=a.clientY,f=k("y"),g=k("x"),i=o.length;i--;){if(c=o[i],h){for(var j,l=a.touches&&a.touches.length;l--;)if(j=a.touches[l],j.identifier==c.el._drag.id||c.el.node.contains(j.target)){d=j.clientX,e=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();{var m=c.el.node;m.nextSibling,m.parentNode,m.style.display}d+=g,e+=f,b("snap.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},q=function(c){a.unmousemove(p).unmouseup(q);for(var d,e=o.length;e--;)d=o[e],d.el._drag={},b("snap.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c),b.off("snap.drag.*."+d.el.id);o=[]},r=i.length;r--;)!function(b){a[b]=f[b]=function(c,d){if(a.is(c,"function"))this.events=this.events||[],this.events.push({name:b,f:c,unbind:n(this.node||document,b,c,d||this)});else for(var e=0,f=this.events.length;f>e;e++)if(this.events[e].name==b)try{this.events[e].f.call(this)}catch(g){}return this},a["un"+b]=f["un"+b]=function(a){for(var c=this.events||[],d=c.length;d--;)if(c[d].name==b&&(c[d].f==a||!a))return c[d].unbind(),c.splice(d,1),!c.length&&delete this.events,this;return this}}(i[r]);f.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},f.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var s=[];f.drag=function(c,d,e,f,g,h){function i(i,j,l){(i.originalEvent||i).preventDefault(),k._drag.x=j,k._drag.y=l,k._drag.id=i.identifier,!o.length&&a.mousemove(p).mouseup(q),o.push({el:k,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("snap.drag.start."+k.id,d),c&&b.on("snap.drag.move."+k.id,c),e&&b.on("snap.drag.end."+k.id,e),b("snap.drag.start."+k.id,g||f||k,j,l,i)}function j(a,c,d){b("snap.draginit."+k.id,k,a,c,d)}var k=this;if(!arguments.length){var l;return k.drag(function(a,b){this.attr({transform:l+(l?"T":"t")+[a,b]})},function(){l=this.transform().local})}return b.on("snap.draginit."+k.id,i),k._drag={},s.push({el:k,start:i,init:j}),k.mousedown(j),k},f.undrag=function(){for(var c=s.length;c--;)s[c].el==this&&(this.unmousedown(s[c].init),s.splice(c,1),b.unbind("snap.drag.*."+this.id),b.unbind("snap.draginit."+this.id));return!s.length&&a.unmousemove(p).unmouseup(q),this}}),d.plugin(function(a,c,d){var e=(c.prototype,d.prototype),f=/^\s*url\((.+)\)/,g=String,h=a._.$;a.filter={},e.filter=function(b){var d=this;"svg"!=d.type&&(d=d.paper);var e=a.parse(g(b)),f=a._.id(),i=(d.node.offsetWidth,d.node.offsetHeight,h("filter"));return h(i,{id:f,filterUnits:"userSpaceOnUse"}),i.appendChild(e.node),d.defs.appendChild(i),new c(i)},b.on("snap.util.getattr.filter",function(){b.stop();var c=h(this.node,"filter");if(c){var d=g(c).match(f);return d&&a.select(d[1])}}),b.on("snap.util.attr.filter",function(d){if(d instanceof c&&"filter"==d.type){b.stop();var e=d.node.id;e||(h(d.node,{id:d.id}),e=d.id),h(this.node,{filter:a.url(e)})}d&&"none"!=d||(b.stop(),this.node.removeAttribute("filter"))}),a.filter.blur=function(b,c){null==b&&(b=2);var d=null==c?b:[b,c];return a.format('<feGaussianBlur stdDeviation="{def}"/>',{def:d})},a.filter.blur.toString=function(){return this()},a.filter.shadow=function(b,c,d,e,f){return"string"==typeof d&&(e=d,f=e,d=4),"string"!=typeof e&&(f=e,e="#000"),e=e||"#000",null==d&&(d=4),null==f&&(f=1),null==b&&(b=0,c=2),null==c&&(c=b),e=a.color(e),a.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>',{color:e,dx:b,dy:c,blur:d,opacity:f})},a.filter.shadow.toString=function(){return this()},a.filter.grayscale=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>',{a:.2126+.7874*(1-b),b:.7152-.7152*(1-b),c:.0722-.0722*(1-b),d:.2126-.2126*(1-b),e:.7152+.2848*(1-b),f:.0722-.0722*(1-b),g:.2126-.2126*(1-b),h:.0722+.9278*(1-b)})},a.filter.grayscale.toString=function(){return this()},a.filter.sepia=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>',{a:.393+.607*(1-b),b:.769-.769*(1-b),c:.189-.189*(1-b),d:.349-.349*(1-b),e:.686+.314*(1-b),f:.168-.168*(1-b),g:.272-.272*(1-b),h:.534-.534*(1-b),i:.131+.869*(1-b)})},a.filter.sepia.toString=function(){return this()},a.filter.saturate=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="saturate" values="{amount}"/>',{amount:1-b})},a.filter.saturate.toString=function(){return this()},a.filter.hueRotate=function(b){return b=b||0,a.format('<feColorMatrix type="hueRotate" values="{angle}"/>',{angle:b})},a.filter.hueRotate.toString=function(){return this()},a.filter.invert=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>',{amount:b,amount2:1-b})},a.filter.invert.toString=function(){return this()},a.filter.brightness=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>',{amount:b})},a.filter.brightness.toString=function(){return this()},a.filter.contrast=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>',{amount:b,amount2:.5-b/2})},a.filter.contrast.toString=function(){return this()}}),d.plugin(function(a,b){var c=a._.box,d=a.is,e=/^[^a-z]*([tbmlrc])/i,f=function(){return"T"+this.dx+","+this.dy};b.prototype.getAlign=function(a,b){null==b&&d(a,"string")&&(b=a,a=null),a=a||this.paper;var g=a.getBBox?a.getBBox():c(a),h=this.getBBox(),i={};switch(b=b&&b.match(e),b=b?b[1].toLowerCase():"c"){case"t":i.dx=0,i.dy=g.y-h.y;break;case"b":i.dx=0,i.dy=g.y2-h.y2;break;case"m":i.dx=0,i.dy=g.cy-h.cy;break;case"l":i.dx=g.x-h.x,i.dy=0;break;case"r":i.dx=g.x2-h.x2,i.dy=0;break;default:i.dx=g.cx-h.cx,i.dy=0}return i.toString=f,i},b.prototype.align=function(a,b){return this.transform("..."+this.getAlign(a,b))}}),d});
diff --git a/web/pgadmin/misc/static/explain/js/snap.svg.js b/web/pgadmin/misc/static/explain/js/snap.svg.js
new file mode 100644
index 0000000..ef0fb6d
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg.js
@@ -0,0 +1,8149 @@
+// Snap.svg 0.4.1
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// build: 2015-04-13
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ┌────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.4.2 - JavaScript Events Library                      │ \\
+// ├────────────────────────────────────────────────────────────┤ \\
+// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+// └────────────────────────────────────────────────────────────┘ \\
+(function (glob) {
+    var version = "0.4.2",
+        has = "hasOwnProperty",
+        separator = /[\.\/]/,
+        comaseparator = /\s*,\s*/,
+        wildcard = "*",
+        fun = function () {},
+        numsort = function (a, b) {
+            return a - b;
+        },
+        current_event,
+        stop,
+        events = {n: {}},
+        firstDefined = function () {
+            for (var i = 0, ii = this.length; i < ii; i++) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+        lastDefined = function () {
+            var i = this.length;
+            while (--i) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+    /*\
+     * eve
+     [ method ]
+     * Fires event with given `name`, given scope and other parameters.
+     > Arguments
+     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
+     - scope (object) context for the event handlers
+     - varargs (...) the rest of arguments will be sent to event handlers
+     = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value.
+    \*/
+        eve = function (name, scope) {
+            name = String(name);
+            var e = events,
+                oldstop = stop,
+                args = Array.prototype.slice.call(arguments, 2),
+                listeners = eve.listeners(name),
+                z = 0,
+                f = false,
+                l,
+                indexed = [],
+                queue = {},
+                out = [],
+                ce = current_event,
+                errors = [];
+            out.firstDefined = firstDefined;
+            out.lastDefined = lastDefined;
+            current_event = name;
+            stop = 0;
+            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                indexed.push(listeners[i].zIndex);
+                if (listeners[i].zIndex < 0) {
+                    queue[listeners[i].zIndex] = listeners[i];
+                }
+            }
+            indexed.sort(numsort);
+            while (indexed[z] < 0) {
+                l = queue[indexed[z++]];
+                out.push(l.apply(scope, args));
+                if (stop) {
+                    stop = oldstop;
+                    return out;
+                }
+            }
+            for (i = 0; i < ii; i++) {
+                l = listeners[i];
+                if ("zIndex" in l) {
+                    if (l.zIndex == indexed[z]) {
+                        out.push(l.apply(scope, args));
+                        if (stop) {
+                            break;
+                        }
+                        do {
+                            z++;
+                            l = queue[indexed[z]];
+                            l && out.push(l.apply(scope, args));
+                            if (stop) {
+                                break;
+                            }
+                        } while (l)
+                    } else {
+                        queue[l.zIndex] = l;
+                    }
+                } else {
+                    out.push(l.apply(scope, args));
+                    if (stop) {
+                        break;
+                    }
+                }
+            }
+            stop = oldstop;
+            current_event = ce;
+            return out;
+        };
+        // Undocumented. Debug only.
+        eve._events = events;
+    /*\
+     * eve.listeners
+     [ method ]
+     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
+     > Arguments
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated
+     = (array) array of event handlers
+    \*/
+    eve.listeners = function (name) {
+        var names = name.split(separator),
+            e = events,
+            item,
+            items,
+            k,
+            i,
+            ii,
+            j,
+            jj,
+            nes,
+            es = [e],
+            out = [];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            nes = [];
+            for (j = 0, jj = es.length; j < jj; j++) {
+                e = es[j].n;
+                items = [e[names[i]], e[wildcard]];
+                k = 2;
+                while (k--) {
+                    item = items[k];
+                    if (item) {
+                        nes.push(item);
+                        out = out.concat(item.f || []);
+                    }
+                }
+            }
+            es = nes;
+        }
+        return out;
+    };
+    /*\
+     * eve.on
+     [ method ]
+     **
+     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
+     | eve.on("*.under.*", f);
+     | eve("mouse.under.floor"); // triggers f
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
+     > Example:
+     | eve.on("mouse", eatIt)(2);
+     | eve.on("mouse", scream);
+     | eve.on("mouse", catchIt)(1);
+     * This will ensure that `catchIt` function will be called before `eatIt`.
+     *
+     * If you want to put your handler before non-indexed handlers, specify a negative value.
+     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
+    \*/
+    eve.on = function (name, f) {
+        name = String(name);
+        if (typeof f != "function") {
+            return function () {};
+        }
+        var names = name.split(comaseparator);
+        for (var i = 0, ii = names.length; i < ii; i++) {
+            (function (name) {
+                var names = name.split(separator),
+                    e = events,
+                    exist;
+                for (var i = 0, ii = names.length; i < ii; i++) {
+                    e = e.n;
+                    e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
+                }
+                e.f = e.f || [];
+                for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+                    exist = true;
+                    break;
+                }
+                !exist && e.f.push(f);
+            }(names[i]));
+        }
+        return function (zIndex) {
+            if (+zIndex == +zIndex) {
+                f.zIndex = +zIndex;
+            }
+        };
+    };
+    /*\
+     * eve.f
+     [ method ]
+     **
+     * Returns function that will fire given event with optional arguments.
+     * Arguments that will be passed to the result function will be also
+     * concated to the list of final arguments.
+     | el.onclick = eve.f("click", 1, 2);
+     | eve.on("click", function (a, b, c) {
+     |     console.log(a, b, c); // 1, 2, [event object]
+     | });
+     > Arguments
+     - event (string) event name
+     - varargs (…) and any other arguments
+     = (function) possible event handler function
+    \*/
+    eve.f = function (event) {
+        var attrs = [].slice.call(arguments, 1);
+        return function () {
+            eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
+        };
+    };
+    /*\
+     * eve.stop
+     [ method ]
+     **
+     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
+    \*/
+    eve.stop = function () {
+        stop = 1;
+    };
+    /*\
+     * eve.nt
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     > Arguments
+     **
+     - subname (string) #optional subname of the event
+     **
+     = (string) name of the event, if `subname` is not specified
+     * or
+     = (boolean) `true`, if current event’s name contains `subname`
+    \*/
+    eve.nt = function (subname) {
+        if (subname) {
+            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+        }
+        return current_event;
+    };
+    /*\
+     * eve.nts
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     **
+     = (array) names of the event
+    \*/
+    eve.nts = function () {
+        return current_event.split(separator);
+    };
+    /*\
+     * eve.off
+     [ method ]
+     **
+     * Removes given function from the list of event listeners assigned to given name.
+     * If no arguments specified all the events will be cleared.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+    \*/
+    /*\
+     * eve.unbind
+     [ method ]
+     **
+     * See @eve.off
+    \*/
+    eve.off = eve.unbind = function (name, f) {
+        if (!name) {
+            eve._events = events = {n: {}};
+            return;
+        }
+        var names = name.split(comaseparator);
+        if (names.length > 1) {
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                eve.off(names[i], f);
+            }
+            return;
+        }
+        names = name.split(separator);
+        var e,
+            key,
+            splice,
+            i, ii, j, jj,
+            cur = [events];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
+                splice = [j, 1];
+                e = cur[j].n;
+                if (names[i] != wildcard) {
+                    if (e[names[i]]) {
+                        splice.push(e[names[i]]);
+                    }
+                } else {
+                    for (key in e) if (e[has](key)) {
+                        splice.push(e[key]);
+                    }
+                }
+                cur.splice.apply(cur, splice);
+            }
+        }
+        for (i = 0, ii = cur.length; i < ii; i++) {
+            e = cur[i];
+            while (e.n) {
+                if (f) {
+                    if (e.f) {
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
+                            break;
+                        }
+                        !e.f.length && delete e.f;
+                    }
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        var funcs = e.n[key].f;
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
+                            break;
+                        }
+                        !funcs.length && delete e.n[key].f;
+                    }
+                } else {
+                    delete e.f;
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        delete e.n[key].f;
+                    }
+                }
+                e = e.n;
+            }
+        }
+    };
+    /*\
+     * eve.once
+     [ method ]
+     **
+     * Binds given event handler with a given name to only run once then unbind itself.
+     | eve.once("login", f);
+     | eve("login"); // triggers f
+     | eve("login"); // no listeners
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) same return function as @eve.on
+    \*/
+    eve.once = function (name, f) {
+        var f2 = function () {
+            eve.unbind(name, f2);
+            return f.apply(this, arguments);
+        };
+        return eve.on(name, f2);
+    };
+    /*\
+     * eve.version
+     [ property (string) ]
+     **
+     * Current version of the library.
+    \*/
+    eve.version = version;
+    eve.toString = function () {
+        return "You are running Eve " + version;
+    };
+    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
+})(this);
+
+(function (glob, factory) {
+    // AMD support
+    if (typeof define == "function" && define.amd) {
+        // Define as an anonymous module
+        define(["eve"], function (eve) {
+            return factory(glob, eve);
+        });
+    } else if (typeof exports != 'undefined') {
+        // Next for Node.js or CommonJS
+        var eve = require('eve');
+        module.exports = factory(glob, eve);
+    } else {
+        // Browser globals (glob is window)
+        // Snap adds itself to window
+        factory(glob, glob.eve);
+    }
+}(window || this, function (window, eve) {
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+var mina = (function (eve) {
+    var animations = {},
+    requestAnimFrame = window.requestAnimationFrame       ||
+                       window.webkitRequestAnimationFrame ||
+                       window.mozRequestAnimationFrame    ||
+                       window.oRequestAnimationFrame      ||
+                       window.msRequestAnimationFrame     ||
+                       function (callback) {
+                           setTimeout(callback, 16);
+                       },
+    isArray = Array.isArray || function (a) {
+        return a instanceof Array ||
+            Object.prototype.toString.call(a) == "[object Array]";
+    },
+    idgen = 0,
+    idprefix = "M" + (+new Date).toString(36),
+    ID = function () {
+        return idprefix + (idgen++).toString(36);
+    },
+    diff = function (a, b, A, B) {
+        if (isArray(a)) {
+            res = [];
+            for (var i = 0, ii = a.length; i < ii; i++) {
+                res[i] = diff(a[i], b, A[i], B);
+            }
+            return res;
+        }
+        var dif = (A - a) / (B - b);
+        return function (bb) {
+            return a + dif * (bb - b);
+        };
+    },
+    timer = Date.now || function () {
+        return +new Date;
+    },
+    sta = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.s;
+        }
+        var ds = a.s - val;
+        a.b += a.dur * ds;
+        a.B += a.dur * ds;
+        a.s = val;
+    },
+    speed = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.spd;
+        }
+        a.spd = val;
+    },
+    duration = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.dur;
+        }
+        a.s = a.s * val / a.dur;
+        a.dur = val;
+    },
+    stopit = function () {
+        var a = this;
+        delete animations[a.id];
+        a.update();
+        eve("mina.stop." + a.id, a);
+    },
+    pause = function () {
+        var a = this;
+        if (a.pdif) {
+            return;
+        }
+        delete animations[a.id];
+        a.update();
+        a.pdif = a.get() - a.b;
+    },
+    resume = function () {
+        var a = this;
+        if (!a.pdif) {
+            return;
+        }
+        a.b = a.get() - a.pdif;
+        delete a.pdif;
+        animations[a.id] = a;
+    },
+    update = function () {
+        var a = this,
+            res;
+        if (isArray(a.start)) {
+            res = [];
+            for (var j = 0, jj = a.start.length; j < jj; j++) {
+                res[j] = +a.start[j] +
+                    (a.end[j] - a.start[j]) * a.easing(a.s);
+            }
+        } else {
+            res = +a.start + (a.end - a.start) * a.easing(a.s);
+        }
+        a.set(res);
+    },
+    frame = function () {
+        var len = 0;
+        for (var i in animations) if (animations.hasOwnProperty(i)) {
+            var a = animations[i],
+                b = a.get(),
+                res;
+            len++;
+            a.s = (b - a.b) / (a.dur / a.spd);
+            if (a.s >= 1) {
+                delete animations[i];
+                a.s = 1;
+                len--;
+                (function (a) {
+                    setTimeout(function () {
+                        eve("mina.finish." + a.id, a);
+                    });
+                }(a));
+            }
+            a.update();
+        }
+        len && requestAnimFrame(frame);
+    },
+    /*\
+     * mina
+     [ method ]
+     **
+     * Generic animation of numbers
+     **
+     - a (number) start _slave_ number
+     - A (number) end _slave_ number
+     - b (number) start _master_ number (start time in general case)
+     - B (number) end _master_ number (end time in gereal case)
+     - get (function) getter of _master_ number (see @mina.time)
+     - set (function) setter of _slave_ number
+     - easing (function) #optional easing function, default is @mina.linear
+     = (object) animation descriptor
+     o {
+     o         id (string) animation id,
+     o         start (number) start _slave_ number,
+     o         end (number) end _slave_ number,
+     o         b (number) start _master_ number,
+     o         s (number) animation status (0..1),
+     o         dur (number) animation duration,
+     o         spd (number) animation speed,
+     o         get (function) getter of _master_ number (see @mina.time),
+     o         set (function) setter of _slave_ number,
+     o         easing (function) easing function, default is @mina.linear,
+     o         status (function) status getter/setter,
+     o         speed (function) speed getter/setter,
+     o         duration (function) duration getter/setter,
+     o         stop (function) animation stopper
+     o         pause (function) pauses the animation
+     o         resume (function) resumes the animation
+     o         update (function) calles setter with the right value of the animation
+     o }
+    \*/
+    mina = function (a, A, b, B, get, set, easing) {
+        var anim = {
+            id: ID(),
+            start: a,
+            end: A,
+            b: b,
+            s: 0,
+            dur: B - b,
+            spd: 1,
+            get: get,
+            set: set,
+            easing: easing || mina.linear,
+            status: sta,
+            speed: speed,
+            duration: duration,
+            stop: stopit,
+            pause: pause,
+            resume: resume,
+            update: update
+        };
+        animations[anim.id] = anim;
+        var len = 0, i;
+        for (i in animations) if (animations.hasOwnProperty(i)) {
+            len++;
+            if (len == 2) {
+                break;
+            }
+        }
+        len == 1 && requestAnimFrame(frame);
+        return anim;
+    };
+    /*\
+     * mina.time
+     [ method ]
+     **
+     * Returns the current time. Equivalent to:
+     | function () {
+     |     return (new Date).getTime();
+     | }
+    \*/
+    mina.time = timer;
+    /*\
+     * mina.getById
+     [ method ]
+     **
+     * Returns an animation by its id
+     - id (string) animation's id
+     = (object) See @mina
+    \*/
+    mina.getById = function (id) {
+        return animations[id] || null;
+    };
+
+    /*\
+     * mina.linear
+     [ method ]
+     **
+     * Default linear easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.linear = function (n) {
+        return n;
+    };
+    /*\
+     * mina.easeout
+     [ method ]
+     **
+     * Easeout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeout = function (n) {
+        return Math.pow(n, 1.7);
+    };
+    /*\
+     * mina.easein
+     [ method ]
+     **
+     * Easein easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easein = function (n) {
+        return Math.pow(n, .48);
+    };
+    /*\
+     * mina.easeinout
+     [ method ]
+     **
+     * Easeinout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeinout = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        if (n == 0) {
+            return 0;
+        }
+        var q = .48 - n / 1.04,
+            Q = Math.sqrt(.1734 + q * q),
+            x = Q - q,
+            X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+            y = -Q - q,
+            Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+            t = X + Y + .5;
+        return (1 - t) * 3 * t * t + t * t * t;
+    };
+    /*\
+     * mina.backin
+     [ method ]
+     **
+     * Backin easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backin = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        var s = 1.70158;
+        return n * n * ((s + 1) * n - s);
+    };
+    /*\
+     * mina.backout
+     [ method ]
+     **
+     * Backout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backout = function (n) {
+        if (n == 0) {
+            return 0;
+        }
+        n = n - 1;
+        var s = 1.70158;
+        return n * n * ((s + 1) * n + s) + 1;
+    };
+    /*\
+     * mina.elastic
+     [ method ]
+     **
+     * Elastic easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.elastic = function (n) {
+        if (n == !!n) {
+            return n;
+        }
+        return Math.pow(2, -10 * n) * Math.sin((n - .075) *
+            (2 * Math.PI) / .3) + 1;
+    };
+    /*\
+     * mina.bounce
+     [ method ]
+     **
+     * Bounce easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.bounce = function (n) {
+        var s = 7.5625,
+            p = 2.75,
+            l;
+        if (n < (1 / p)) {
+            l = s * n * n;
+        } else {
+            if (n < (2 / p)) {
+                n -= (1.5 / p);
+                l = s * n * n + .75;
+            } else {
+                if (n < (2.5 / p)) {
+                    n -= (2.25 / p);
+                    l = s * n * n + .9375;
+                } else {
+                    n -= (2.625 / p);
+                    l = s * n * n + .984375;
+                }
+            }
+        }
+        return l;
+    };
+    window.mina = mina;
+    return mina;
+})(typeof eve == "undefined" ? function () {} : eve);
+// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var Snap = (function(root) {
+Snap.version = "0.4.0";
+/*\
+ * Snap
+ [ method ]
+ **
+ * Creates a drawing surface or wraps existing SVG element.
+ **
+ - width (number|string) width of surface
+ - height (number|string) height of surface
+ * or
+ - DOM (SVGElement) element to be wrapped into Snap structure
+ * or
+ - array (array) array of elements (will return set of elements)
+ * or
+ - query (string) CSS query selector
+ = (object) @Element
+\*/
+function Snap(w, h) {
+    if (w) {
+        if (w.nodeType) {
+            return wrap(w);
+        }
+        if (is(w, "array") && Snap.set) {
+            return Snap.set.apply(Snap, w);
+        }
+        if (w instanceof Element) {
+            return w;
+        }
+        if (h == null) {
+            w = glob.doc.querySelector(String(w));
+            return wrap(w);
+        }
+    }
+    w = w == null ? "100%" : w;
+    h = h == null ? "100%" : h;
+    return new Paper(w, h);
+}
+Snap.toString = function () {
+    return "Snap v" + this.version;
+};
+Snap._ = {};
+var glob = {
+    win: root.window,
+    doc: root.window.document
+};
+Snap._.glob = glob;
+var has = "hasOwnProperty",
+    Str = String,
+    toFloat = parseFloat,
+    toInt = parseInt,
+    math = Math,
+    mmax = math.max,
+    mmin = math.min,
+    abs = math.abs,
+    pow = math.pow,
+    PI = math.PI,
+    round = math.round,
+    E = "",
+    S = " ",
+    objectToString = Object.prototype.toString,
+    ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+    colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,
+    bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+    reURLValue = /^url\(#?([^)]+)\)$/,
+    separator = Snap._.separator = /[,\s]+/,
+    whitespace = /[\s]/g,
+    commaSpaces = /[\s]*,[\s]*/,
+    hsrg = {hs: 1, rg: 1},
+    pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig,
+    idgen = 0,
+    idprefix = "S" + (+new Date).toString(36),
+    ID = function (el) {
+        return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36);
+    },
+    xlink = "http://www.w3.org/1999/xlink",
+    xmlns = "http://www.w3.org/2000/svg",
+    hub = {},
+    URL = Snap.url = function (url) {
+        return "url('#" + url + "')";
+    };
+
+function $(el, attr) {
+    if (attr) {
+        if (el == "#text") {
+            el = glob.doc.createTextNode(attr.text || attr["#text"] || "");
+        }
+        if (el == "#comment") {
+            el = glob.doc.createComment(attr.text || attr["#text"] || "");
+        }
+        if (typeof el == "string") {
+            el = $(el);
+        }
+        if (typeof attr == "string") {
+            if (el.nodeType == 1) {
+                if (attr.substring(0, 6) == "xlink:") {
+                    return el.getAttributeNS(xlink, attr.substring(6));
+                }
+                if (attr.substring(0, 4) == "xml:") {
+                    return el.getAttributeNS(xmlns, attr.substring(4));
+                }
+                return el.getAttribute(attr);
+            } else if (attr == "text") {
+                return el.nodeValue;
+            } else {
+                return null;
+            }
+        }
+        if (el.nodeType == 1) {
+            for (var key in attr) if (attr[has](key)) {
+                var val = Str(attr[key]);
+                if (val) {
+                    if (key.substring(0, 6) == "xlink:") {
+                        el.setAttributeNS(xlink, key.substring(6), val);
+                    } else if (key.substring(0, 4) == "xml:") {
+                        el.setAttributeNS(xmlns, key.substring(4), val);
+                    } else {
+                        el.setAttribute(key, val);
+                    }
+                } else {
+                    el.removeAttribute(key);
+                }
+            }
+        } else if ("text" in attr) {
+            el.nodeValue = attr.text;
+        }
+    } else {
+        el = glob.doc.createElementNS(xmlns, el);
+    }
+    return el;
+}
+Snap._.$ = $;
+Snap._.id = ID;
+function getAttrs(el) {
+    var attrs = el.attributes,
+        name,
+        out = {};
+    for (var i = 0; i < attrs.length; i++) {
+        if (attrs[i].namespaceURI == xlink) {
+            name = "xlink:";
+        } else {
+            name = "";
+        }
+        name += attrs[i].name;
+        out[name] = attrs[i].textContent;
+    }
+    return out;
+}
+function is(o, type) {
+    type = Str.prototype.toLowerCase.call(type);
+    if (type == "finite") {
+        return isFinite(o);
+    }
+    if (type == "array" &&
+        (o instanceof Array || Array.isArray && Array.isArray(o))) {
+        return true;
+    }
+    return  (type == "null" && o === null) ||
+            (type == typeof o && o !== null) ||
+            (type == "object" && o === Object(o)) ||
+            objectToString.call(o).slice(8, -1).toLowerCase() == type;
+}
+/*\
+ * Snap.format
+ [ method ]
+ **
+ * Replaces construction of type `{<name>}` to the corresponding argument
+ **
+ - token (string) string to format
+ - json (object) object which properties are used as a replacement
+ = (string) formatted string
+ > Usage
+ | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z"
+ | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
+ |     x: 10,
+ |     y: 20,
+ |     dim: {
+ |         width: 40,
+ |         height: 50,
+ |         "negative width": -40
+ |     }
+ | }));
+\*/
+Snap.format = (function () {
+    var tokenRegex = /\{([^\}]+)\}/g,
+        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+        replacer = function (all, key, obj) {
+            var res = obj;
+            key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+                name = name || quotedName;
+                if (res) {
+                    if (name in res) {
+                        res = res[name];
+                    }
+                    typeof res == "function" && isFunc && (res = res());
+                }
+            });
+            res = (res == null || res == obj ? all : res) + "";
+            return res;
+        };
+    return function (str, obj) {
+        return Str(str).replace(tokenRegex, function (all, key) {
+            return replacer(all, key, obj);
+        });
+    };
+})();
+function clone(obj) {
+    if (typeof obj == "function" || Object(obj) !== obj) {
+        return obj;
+    }
+    var res = new obj.constructor;
+    for (var key in obj) if (obj[has](key)) {
+        res[key] = clone(obj[key]);
+    }
+    return res;
+}
+Snap._.clone = clone;
+function repush(array, item) {
+    for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+        return array.push(array.splice(i, 1)[0]);
+    }
+}
+function cacher(f, scope, postprocessor) {
+    function newf() {
+        var arg = Array.prototype.slice.call(arguments, 0),
+            args = arg.join("\u2400"),
+            cache = newf.cache = newf.cache || {},
+            count = newf.count = newf.count || [];
+        if (cache[has](args)) {
+            repush(count, args);
+            return postprocessor ? postprocessor(cache[args]) : cache[args];
+        }
+        count.length >= 1e3 && delete cache[count.shift()];
+        count.push(args);
+        cache[args] = f.apply(scope, arg);
+        return postprocessor ? postprocessor(cache[args]) : cache[args];
+    }
+    return newf;
+}
+Snap._.cacher = cacher;
+function angle(x1, y1, x2, y2, x3, y3) {
+    if (x3 == null) {
+        var x = x1 - x2,
+            y = y1 - y2;
+        if (!x && !y) {
+            return 0;
+        }
+        return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+    } else {
+        return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3);
+    }
+}
+function rad(deg) {
+    return deg % 360 * PI / 180;
+}
+function deg(rad) {
+    return rad * 180 / PI % 360;
+}
+function x_y() {
+    return this.x + S + this.y;
+}
+function x_y_w_h() {
+    return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+}
+
+/*\
+ * Snap.rad
+ [ method ]
+ **
+ * Transform angle to radians
+ - deg (number) angle in degrees
+ = (number) angle in radians
+\*/
+Snap.rad = rad;
+/*\
+ * Snap.deg
+ [ method ]
+ **
+ * Transform angle to degrees
+ - rad (number) angle in radians
+ = (number) angle in degrees
+\*/
+Snap.deg = deg;
+/*\
+ * Snap.sin
+ [ method ]
+ **
+ * Equivalent to `Math.sin()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) sin
+\*/
+Snap.sin = function (angle) {
+    return math.sin(Snap.rad(angle));
+};
+/*\
+ * Snap.tan
+ [ method ]
+ **
+ * Equivalent to `Math.tan()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) tan
+\*/
+Snap.tan = function (angle) {
+    return math.tan(Snap.rad(angle));
+};
+/*\
+ * Snap.cos
+ [ method ]
+ **
+ * Equivalent to `Math.cos()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) cos
+\*/
+Snap.cos = function (angle) {
+    return math.cos(Snap.rad(angle));
+};
+/*\
+ * Snap.asin
+ [ method ]
+ **
+ * Equivalent to `Math.asin()` only works with degrees, not radians.
+ - num (number) value
+ = (number) asin in degrees
+\*/
+Snap.asin = function (num) {
+    return Snap.deg(math.asin(num));
+};
+/*\
+ * Snap.acos
+ [ method ]
+ **
+ * Equivalent to `Math.acos()` only works with degrees, not radians.
+ - num (number) value
+ = (number) acos in degrees
+\*/
+Snap.acos = function (num) {
+    return Snap.deg(math.acos(num));
+};
+/*\
+ * Snap.atan
+ [ method ]
+ **
+ * Equivalent to `Math.atan()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan in degrees
+\*/
+Snap.atan = function (num) {
+    return Snap.deg(math.atan(num));
+};
+/*\
+ * Snap.atan2
+ [ method ]
+ **
+ * Equivalent to `Math.atan2()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan2 in degrees
+\*/
+Snap.atan2 = function (num) {
+    return Snap.deg(math.atan2(num));
+};
+/*\
+ * Snap.angle
+ [ method ]
+ **
+ * Returns an angle between two or three points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ - x3 (number) #optional x coord of third point
+ - y3 (number) #optional y coord of third point
+ = (number) angle in degrees
+\*/
+Snap.angle = angle;
+/*\
+ * Snap.len
+ [ method ]
+ **
+ * Returns distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len = function (x1, y1, x2, y2) {
+    return Math.sqrt(Snap.len2(x1, y1, x2, y2));
+};
+/*\
+ * Snap.len2
+ [ method ]
+ **
+ * Returns squared distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len2 = function (x1, y1, x2, y2) {
+    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
+};
+/*\
+ * Snap.closestPoint
+ [ method ]
+ **
+ * Returns closest point to a given one on a given path.
+ > Parameters
+ - path (Element) path element
+ - x (number) x coord of a point
+ - y (number) y coord of a point
+ = (object) in format
+ {
+    x (number) x coord of the point on the path
+    y (number) y coord of the point on the path
+    length (number) length of the path to the point
+    distance (number) distance from the given point to the path
+ }
+\*/
+// Copied from http://bl.ocks.org/mbostock/8027637
+Snap.closestPoint = function (path, x, y) {
+    function distance2(p) {
+        var dx = p.x - x,
+            dy = p.y - y;
+        return dx * dx + dy * dy;
+    }
+    var pathNode = path.node,
+        pathLength = pathNode.getTotalLength(),
+        precision = pathLength / pathNode.pathSegList.numberOfItems * .125,
+        best,
+        bestLength,
+        bestDistance = Infinity;
+
+    // linear scan for coarse approximation
+    for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
+        if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
+            best = scan, bestLength = scanLength, bestDistance = scanDistance;
+        }
+    }
+
+    // binary search for precise estimate
+    precision *= .5;
+    while (precision > .5) {
+        var before,
+            after,
+            beforeLength,
+            afterLength,
+            beforeDistance,
+            afterDistance;
+        if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
+            best = before, bestLength = beforeLength, bestDistance = beforeDistance;
+        } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
+            best = after, bestLength = afterLength, bestDistance = afterDistance;
+        } else {
+            precision *= .5;
+        }
+    }
+
+    best = {
+        x: best.x,
+        y: best.y,
+        length: bestLength,
+        distance: Math.sqrt(bestDistance)
+    };
+    return best;
+}
+/*\
+ * Snap.is
+ [ method ]
+ **
+ * Handy replacement for the `typeof` operator
+ - o (…) any object or primitive
+ - type (string) name of the type, e.g., `string`, `function`, `number`, etc.
+ = (boolean) `true` if given value is of given type
+\*/
+Snap.is = is;
+/*\
+ * Snap.snapTo
+ [ method ]
+ **
+ * Snaps given value to given grid
+ - values (array|number) given array of values or step of the grid
+ - value (number) value to adjust
+ - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`.
+ = (number) adjusted value
+\*/
+Snap.snapTo = function (values, value, tolerance) {
+    tolerance = is(tolerance, "finite") ? tolerance : 10;
+    if (is(values, "array")) {
+        var i = values.length;
+        while (i--) if (abs(values[i] - value) <= tolerance) {
+            return values[i];
+        }
+    } else {
+        values = +values;
+        var rem = value % values;
+        if (rem < tolerance) {
+            return value - rem;
+        }
+        if (rem > values - tolerance) {
+            return value - rem + values;
+        }
+    }
+    return value;
+};
+// Colour
+/*\
+ * Snap.getRGB
+ [ method ]
+ **
+ * Parses color string as RGB object
+ - color (string) color string in one of the following formats:
+ # <ul>
+ #     <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li>
+ #     <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li>
+ #     <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li>
+ #     <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200,&nbsp;100,&nbsp;0)</code>)</li>
+ #     <li>rgba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li>
+ #     <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>)</li>
+ #     <li>hsba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;0.5)</code>)</li>
+ #     <li>hsla(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li>
+ # </ul>
+ * Note that `%` can be used any time: `rgb(20%, 255, 50%)`.
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) true if string can't be parsed
+ o }
+\*/
+Snap.getRGB = cacher(function (colour) {
+    if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    if (colour == "none") {
+        return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString};
+    }
+    !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+    if (!colour) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    var res,
+        red,
+        green,
+        blue,
+        opacity,
+        t,
+        values,
+        rgb = colour.match(colourRegExp);
+    if (rgb) {
+        if (rgb[2]) {
+            blue = toInt(rgb[2].substring(5), 16);
+            green = toInt(rgb[2].substring(3, 5), 16);
+            red = toInt(rgb[2].substring(1, 3), 16);
+        }
+        if (rgb[3]) {
+            blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+            green = toInt((t = rgb[3].charAt(2)) + t, 16);
+            red = toInt((t = rgb[3].charAt(1)) + t, 16);
+        }
+        if (rgb[4]) {
+            values = rgb[4].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red *= 2.55);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green *= 2.55);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue *= 2.55);
+            rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+        }
+        if (rgb[5]) {
+            values = rgb[5].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsb2rgb(red, green, blue, opacity);
+        }
+        if (rgb[6]) {
+            values = rgb[6].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsl2rgb(red, green, blue, opacity);
+        }
+        red = mmin(math.round(red), 255);
+        green = mmin(math.round(green), 255);
+        blue = mmin(math.round(blue), 255);
+        opacity = mmin(mmax(opacity, 0), 1);
+        rgb = {r: red, g: green, b: blue, toString: rgbtoString};
+        rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+        rgb.opacity = is(opacity, "finite") ? opacity : 1;
+        return rgb;
+    }
+    return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+}, Snap);
+/*\
+ * Snap.hsb
+ [ method ]
+ **
+ * Converts HSB values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - b (number) value or brightness
+ = (string) hex representation of the color
+\*/
+Snap.hsb = cacher(function (h, s, b) {
+    return Snap.hsb2rgb(h, s, b).hex;
+});
+/*\
+ * Snap.hsl
+ [ method ]
+ **
+ * Converts HSL values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (string) hex representation of the color
+\*/
+Snap.hsl = cacher(function (h, s, l) {
+    return Snap.hsl2rgb(h, s, l).hex;
+});
+/*\
+ * Snap.rgb
+ [ method ]
+ **
+ * Converts RGB values to a hex representation of the color
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (string) hex representation of the color
+\*/
+Snap.rgb = cacher(function (r, g, b, o) {
+    if (is(o, "finite")) {
+        var round = math.round;
+        return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")";
+    }
+    return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+});
+var toHex = function (color) {
+    var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0],
+        red = "rgb(255, 0, 0)";
+    toHex = cacher(function (color) {
+        if (color.toLowerCase() == "red") {
+            return red;
+        }
+        i.style.color = red;
+        i.style.color = color;
+        var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+        return out == red ? null : out;
+    });
+    return toHex(color);
+},
+hsbtoString = function () {
+    return "hsb(" + [this.h, this.s, this.b] + ")";
+},
+hsltoString = function () {
+    return "hsl(" + [this.h, this.s, this.l] + ")";
+},
+rgbtoString = function () {
+    return this.opacity == 1 || this.opacity == null ?
+            this.hex :
+            "rgba(" + [this.r, this.g, this.b, this.opacity] + ")";
+},
+prepareRGB = function (r, g, b) {
+    if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) {
+        b = r.b;
+        g = r.g;
+        r = r.r;
+    }
+    if (g == null && is(r, string)) {
+        var clr = Snap.getRGB(r);
+        r = clr.r;
+        g = clr.g;
+        b = clr.b;
+    }
+    if (r > 1 || g > 1 || b > 1) {
+        r /= 255;
+        g /= 255;
+        b /= 255;
+    }
+
+    return [r, g, b];
+},
+packageRGB = function (r, g, b, o) {
+    r = math.round(r * 255);
+    g = math.round(g * 255);
+    b = math.round(b * 255);
+    var rgb = {
+        r: r,
+        g: g,
+        b: b,
+        opacity: is(o, "finite") ? o : 1,
+        hex: Snap.rgb(r, g, b),
+        toString: rgbtoString
+    };
+    is(o, "finite") && (rgb.opacity = o);
+    return rgb;
+};
+/*\
+ * Snap.color
+ [ method ]
+ **
+ * Parses the color string and returns an object featuring the color's component values
+ - clr (string) color string in one of the supported formats (see @Snap.getRGB)
+ = (object) Combined RGB/HSB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) `true` if string can't be parsed,
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     v (number) value (brightness),
+ o     l (number) lightness
+ o }
+\*/
+Snap.color = function (clr) {
+    var rgb;
+    if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+        rgb = Snap.hsb2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+        rgb = Snap.hsl2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else {
+        if (is(clr, "string")) {
+            clr = Snap.getRGB(clr);
+        }
+        if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) {
+            rgb = Snap.rgb2hsl(clr);
+            clr.h = rgb.h;
+            clr.s = rgb.s;
+            clr.l = rgb.l;
+            rgb = Snap.rgb2hsb(clr);
+            clr.v = rgb.b;
+        } else {
+            clr = {hex: "none"};
+            clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+            clr.error = 1;
+        }
+    }
+    clr.toString = rgbtoString;
+    return clr;
+};
+/*\
+ * Snap.hsb2rgb
+ [ method ]
+ **
+ * Converts HSB values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - v (number) value or brightness
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsb2rgb = function (h, s, v, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "b" in h) {
+        v = h.b;
+        s = h.s;
+        o = h.o;
+        h = h.h;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = v * s;
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = v - C;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.hsl2rgb
+ [ method ]
+ **
+ * Converts HSL values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsl2rgb = function (h, s, l, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "l" in h) {
+        l = h.l;
+        s = h.s;
+        h = h.h;
+    }
+    if (h > 1 || s > 1 || l > 1) {
+        h /= 360;
+        s /= 100;
+        l /= 100;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = 2 * s * (l < .5 ? l : 1 - l);
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = l - C / 2;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.rgb2hsb
+ [ method ]
+ **
+ * Converts RGB values to an HSB object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSB object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     b (number) brightness
+ o }
+\*/
+Snap.rgb2hsb = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, V, C;
+    V = mmax(r, g, b);
+    C = V - mmin(r, g, b);
+    H = (C == 0 ? null :
+         V == r ? (g - b) / C :
+         V == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4
+        );
+    H = ((H + 360) % 6) * 60 / 360;
+    S = C == 0 ? 0 : C / V;
+    return {h: H, s: S, b: V, toString: hsbtoString};
+};
+/*\
+ * Snap.rgb2hsl
+ [ method ]
+ **
+ * Converts RGB values to an HSL object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSL object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     l (number) luminosity
+ o }
+\*/
+Snap.rgb2hsl = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, L, M, m, C;
+    M = mmax(r, g, b);
+    m = mmin(r, g, b);
+    C = M - m;
+    H = (C == 0 ? null :
+         M == r ? (g - b) / C :
+         M == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4);
+    H = ((H + 360) % 6) * 60 / 360;
+    L = (M + m) / 2;
+    S = (C == 0 ? 0 :
+         L < .5 ? C / (2 * L) :
+                  C / (2 - 2 * L));
+    return {h: H, s: S, l: L, toString: hsltoString};
+};
+
+// Transformations
+/*\
+ * Snap.parsePathString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given path string into an array of arrays of path segments
+ - pathString (string|array) path string or array of segments (in the last case it is returned straight away)
+ = (array) array of segments
+\*/
+Snap.parsePathString = function (pathString) {
+    if (!pathString) {
+        return null;
+    }
+    var pth = Snap.path(pathString);
+    if (pth.arr) {
+        return Snap.path.clone(pth.arr);
+    }
+
+    var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
+        data = [];
+    if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
+        data = Snap.path.clone(pathString);
+    }
+    if (!data.length) {
+        Str(pathString).replace(pathCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            if (name == "m" && params.length > 2) {
+                data.push([b].concat(params.splice(0, 2)));
+                name = "l";
+                b = b == "m" ? "l" : "L";
+            }
+            if (name == "o" && params.length == 1) {
+                data.push([b, params[0]]);
+            }
+            if (name == "r") {
+                data.push([b].concat(params));
+            } else while (params.length >= paramCounts[name]) {
+                data.push([b].concat(params.splice(0, paramCounts[name])));
+                if (!paramCounts[name]) {
+                    break;
+                }
+            }
+        });
+    }
+    data.toString = Snap.path.toString;
+    pth.arr = Snap.path.clone(data);
+    return data;
+};
+/*\
+ * Snap.parseTransformString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given transform string into an array of transformations
+ - TString (string|array) transform string or array of transformations (in the last case it is returned straight away)
+ = (array) array of transformations
+\*/
+var parseTransformString = Snap.parseTransformString = function (TString) {
+    if (!TString) {
+        return null;
+    }
+    var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+        data = [];
+    if (is(TString, "array") && is(TString[0], "array")) { // rough assumption
+        data = Snap.path.clone(TString);
+    }
+    if (!data.length) {
+        Str(TString).replace(tCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            data.push([b].concat(params));
+        });
+    }
+    data.toString = Snap.path.toString;
+    return data;
+};
+function svgTransform2string(tstr) {
+    var res = [];
+    tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
+        params = params.split(/\s*,\s*|\s+/);
+        if (name == "rotate" && params.length == 1) {
+            params.push(0, 0);
+        }
+        if (name == "scale") {
+            if (params.length > 2) {
+                params = params.slice(0, 2);
+            } else if (params.length == 2) {
+                params.push(0, 0);
+            }
+            if (params.length == 1) {
+                params.push(params[0], 0, 0);
+            }
+        }
+        if (name == "skewX") {
+            res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]);
+        } else if (name == "skewY") {
+            res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]);
+        } else {
+            res.push([name.charAt(0)].concat(params));
+        }
+        return all;
+    });
+    return res;
+}
+Snap._.svgTransform2string = svgTransform2string;
+Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i;
+function transform2matrix(tstr, bbox) {
+    var tdata = parseTransformString(tstr),
+        m = new Snap.Matrix;
+    if (tdata) {
+        for (var i = 0, ii = tdata.length; i < ii; i++) {
+            var t = tdata[i],
+                tlen = t.length,
+                command = Str(t[0]).toLowerCase(),
+                absolute = t[0] != command,
+                inver = absolute ? m.invert() : 0,
+                x1,
+                y1,
+                x2,
+                y2,
+                bb;
+            if (command == "t" && tlen == 2){
+                m.translate(t[1], 0);
+            } else if (command == "t" && tlen == 3) {
+                if (absolute) {
+                    x1 = inver.x(0, 0);
+                    y1 = inver.y(0, 0);
+                    x2 = inver.x(t[1], t[2]);
+                    y2 = inver.y(t[1], t[2]);
+                    m.translate(x2 - x1, y2 - y1);
+                } else {
+                    m.translate(t[1], t[2]);
+                }
+            } else if (command == "r") {
+                if (tlen == 2) {
+                    bb = bb || bbox;
+                    m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.rotate(t[1], x2, y2);
+                    } else {
+                        m.rotate(t[1], t[2], t[3]);
+                    }
+                }
+            } else if (command == "s") {
+                if (tlen == 2 || tlen == 3) {
+                    bb = bb || bbox;
+                    m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.scale(t[1], t[1], x2, y2);
+                    } else {
+                        m.scale(t[1], t[1], t[2], t[3]);
+                    }
+                } else if (tlen == 5) {
+                    if (absolute) {
+                        x2 = inver.x(t[3], t[4]);
+                        y2 = inver.y(t[3], t[4]);
+                        m.scale(t[1], t[2], x2, y2);
+                    } else {
+                        m.scale(t[1], t[2], t[3], t[4]);
+                    }
+                }
+            } else if (command == "m" && tlen == 7) {
+                m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+            }
+        }
+    }
+    return m;
+}
+Snap._.transform2matrix = transform2matrix;
+Snap._unit2px = unit2px;
+var contains = glob.doc.contains || glob.doc.compareDocumentPosition ?
+    function (a, b) {
+        var adown = a.nodeType == 9 ? a.documentElement : a,
+            bup = b && b.parentNode;
+            return a == bup || !!(bup && bup.nodeType == 1 && (
+                adown.contains ?
+                    adown.contains(bup) :
+                    a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
+            ));
+    } :
+    function (a, b) {
+        if (b) {
+            while (b) {
+                b = b.parentNode;
+                if (b == a) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
+function getSomeDefs(el) {
+    var p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) ||
+            (el.node.parentNode && wrap(el.node.parentNode)) ||
+            Snap.select("svg") ||
+            Snap(0, 0),
+        pdefs = p.select("defs"),
+        defs  = pdefs == null ? false : pdefs.node;
+    if (!defs) {
+        defs = make("defs", p.node).node;
+    }
+    return defs;
+}
+function getSomeSVG(el) {
+    return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg");
+}
+Snap._.getSomeDefs = getSomeDefs;
+Snap._.getSomeSVG = getSomeSVG;
+function unit2px(el, name, value) {
+    var svg = getSomeSVG(el).node,
+        out = {},
+        mgr = svg.querySelector(".svg---mgr");
+    if (!mgr) {
+        mgr = $("rect");
+        $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"});
+        svg.appendChild(mgr);
+    }
+    function getW(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {width: val});
+        try {
+            return mgr.getBBox().width;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function getH(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {height: val});
+        try {
+            return mgr.getBBox().height;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function set(nam, f) {
+        if (name == null) {
+            out[nam] = f(el.attr(nam) || 0);
+        } else if (nam == name) {
+            out = f(value == null ? el.attr(nam) || 0 : value);
+        }
+    }
+    switch (el.type) {
+        case "rect":
+            set("rx", getW);
+            set("ry", getH);
+        case "image":
+            set("width", getW);
+            set("height", getH);
+        case "text":
+            set("x", getW);
+            set("y", getH);
+        break;
+        case "circle":
+            set("cx", getW);
+            set("cy", getH);
+            set("r", getW);
+        break;
+        case "ellipse":
+            set("cx", getW);
+            set("cy", getH);
+            set("rx", getW);
+            set("ry", getH);
+        break;
+        case "line":
+            set("x1", getW);
+            set("x2", getW);
+            set("y1", getH);
+            set("y2", getH);
+        break;
+        case "marker":
+            set("refX", getW);
+            set("markerWidth", getW);
+            set("refY", getH);
+            set("markerHeight", getH);
+        break;
+        case "radialGradient":
+            set("fx", getW);
+            set("fy", getH);
+        break;
+        case "tspan":
+            set("dx", getW);
+            set("dy", getH);
+        break;
+        default:
+            set(name, getW);
+    }
+    svg.removeChild(mgr);
+    return out;
+}
+/*\
+ * Snap.select
+ [ method ]
+ **
+ * Wraps a DOM element specified by CSS selector as @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.select = function (query) {
+    query = Str(query).replace(/([^\\]):/g, "$1\\:");
+    return wrap(glob.doc.querySelector(query));
+};
+/*\
+ * Snap.selectAll
+ [ method ]
+ **
+ * Wraps DOM elements specified by CSS selector as set or array of @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.selectAll = function (query) {
+    var nodelist = glob.doc.querySelectorAll(query),
+        set = (Snap.set || Array)();
+    for (var i = 0; i < nodelist.length; i++) {
+        set.push(wrap(nodelist[i]));
+    }
+    return set;
+};
+
+function add2group(list) {
+    if (!is(list, "array")) {
+        list = Array.prototype.slice.call(arguments, 0);
+    }
+    var i = 0,
+        j = 0,
+        node = this.node;
+    while (this[i]) delete this[i++];
+    for (i = 0; i < list.length; i++) {
+        if (list[i].type == "set") {
+            list[i].forEach(function (el) {
+                node.appendChild(el.node);
+            });
+        } else {
+            node.appendChild(list[i].node);
+        }
+    }
+    var children = node.childNodes;
+    for (i = 0; i < children.length; i++) {
+        this[j++] = wrap(children[i]);
+    }
+    return this;
+}
+// Hub garbage collector every 10s
+setInterval(function () {
+    for (var key in hub) if (hub[has](key)) {
+        var el = hub[key],
+            node = el.node;
+        if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
+            delete hub[key];
+        }
+    }
+}, 1e4);
+function Element(el) {
+    if (el.snap in hub) {
+        return hub[el.snap];
+    }
+    var svg;
+    try {
+        svg = el.ownerSVGElement;
+    } catch(e) {}
+    /*\
+     * Element.node
+     [ property (object) ]
+     **
+     * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
+     > Usage
+     | // draw a circle at coordinate 10,10 with radius of 10
+     | var c = paper.circle(10, 10, 10);
+     | c.node.onclick = function () {
+     |     c.attr("fill", "red");
+     | };
+    \*/
+    this.node = el;
+    if (svg) {
+        this.paper = new Paper(svg);
+    }
+    /*\
+     * Element.type
+     [ property (string) ]
+     **
+     * SVG tag name of the given element.
+    \*/
+    this.type = el.tagName || el.nodeName;
+    var id = this.id = ID(this);
+    this.anims = {};
+    this._ = {
+        transform: []
+    };
+    el.snap = id;
+    hub[id] = this;
+    if (this.type == "g") {
+        this.add = add2group;
+    }
+    if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) {
+        for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
+            this[method] = Paper.prototype[method];
+        }
+    }
+}
+   /*\
+     * Element.attr
+     [ method ]
+     **
+     * Gets or sets given attributes of the element.
+     **
+     - params (object) contains key-value pairs of attributes you want to set
+     * or
+     - param (string) name of the attribute
+     = (Element) the current element
+     * or
+     = (string) value of attribute
+     > Usage
+     | el.attr({
+     |     fill: "#fc0",
+     |     stroke: "#000",
+     |     strokeWidth: 2, // CamelCase...
+     |     "fill-opacity": 0.5, // or dash-separated names
+     |     width: "*=2" // prefixed values
+     | });
+     | console.log(el.attr("fill")); // #fc0
+     * Prefixed values in format `"+=10"` supported. All four operations
+     * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+`
+     * and `-`: `"+=2em"`.
+    \*/
+    Element.prototype.attr = function (params, value) {
+        var el = this,
+            node = el.node;
+        if (!params) {
+            if (node.nodeType != 1) {
+                return {
+                    text: node.nodeValue
+                };
+            }
+            var attr = node.attributes,
+                out = {};
+            for (var i = 0, ii = attr.length; i < ii; i++) {
+                out[attr[i].nodeName] = attr[i].nodeValue;
+            }
+            return out;
+        }
+        if (is(params, "string")) {
+            if (arguments.length > 1) {
+                var json = {};
+                json[params] = value;
+                params = json;
+            } else {
+                return eve("snap.util.getattr." + params, el).firstDefined();
+            }
+        }
+        for (var att in params) {
+            if (params[has](att)) {
+                eve("snap.util.attr." + att, el, params[att]);
+            }
+        }
+        return el;
+    };
+/*\
+ * Snap.parse
+ [ method ]
+ **
+ * Parses SVG fragment and converts it into a @Fragment
+ **
+ - svg (string) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.parse = function (svg) {
+    var f = glob.doc.createDocumentFragment(),
+        full = true,
+        div = glob.doc.createElement("div");
+    svg = Str(svg);
+    if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) {
+        svg = "<svg>" + svg + "</svg>";
+        full = false;
+    }
+    div.innerHTML = svg;
+    svg = div.getElementsByTagName("svg")[0];
+    if (svg) {
+        if (full) {
+            f = svg;
+        } else {
+            while (svg.firstChild) {
+                f.appendChild(svg.firstChild);
+            }
+        }
+    }
+    return new Fragment(f);
+};
+function Fragment(frag) {
+    this.node = frag;
+}
+/*\
+ * Snap.fragment
+ [ method ]
+ **
+ * Creates a DOM fragment from a given list of elements or strings
+ **
+ - varargs (…) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.fragment = function () {
+    var args = Array.prototype.slice.call(arguments, 0),
+        f = glob.doc.createDocumentFragment();
+    for (var i = 0, ii = args.length; i < ii; i++) {
+        var item = args[i];
+        if (item.node && item.node.nodeType) {
+            f.appendChild(item.node);
+        }
+        if (item.nodeType) {
+            f.appendChild(item);
+        }
+        if (typeof item == "string") {
+            f.appendChild(Snap.parse(item).node);
+        }
+    }
+    return new Fragment(f);
+};
+
+function make(name, parent) {
+    var res = $(name);
+    parent.appendChild(res);
+    var el = wrap(res);
+    return el;
+}
+function Paper(w, h) {
+    var res,
+        desc,
+        defs,
+        proto = Paper.prototype;
+    if (w && w.tagName == "svg") {
+        if (w.snap in hub) {
+            return hub[w.snap];
+        }
+        var doc = w.ownerDocument;
+        res = new Element(w);
+        desc = w.getElementsByTagName("desc")[0];
+        defs = w.getElementsByTagName("defs")[0];
+        if (!desc) {
+            desc = $("desc");
+            desc.appendChild(doc.createTextNode("Created with Snap"));
+            res.node.appendChild(desc);
+        }
+        if (!defs) {
+            defs = $("defs");
+            res.node.appendChild(defs);
+        }
+        res.defs = defs;
+        for (var key in proto) if (proto[has](key)) {
+            res[key] = proto[key];
+        }
+        res.paper = res.root = res;
+    } else {
+        res = make("svg", glob.doc.body);
+        $(res.node, {
+            height: h,
+            version: 1.1,
+            width: w,
+            xmlns: xmlns
+        });
+    }
+    return res;
+}
+function wrap(dom) {
+    if (!dom) {
+        return dom;
+    }
+    if (dom instanceof Element || dom instanceof Fragment) {
+        return dom;
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "svg") {
+        return new Paper(dom);
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") {
+        return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]);
+    }
+    return new Element(dom);
+}
+
+Snap._.make = make;
+Snap._.wrap = wrap;
+/*\
+ * Paper.el
+ [ method ]
+ **
+ * Creates an element on paper with a given name and no attributes
+ **
+ - name (string) tag name
+ - attr (object) attributes
+ = (Element) the current element
+ > Usage
+ | var c = paper.circle(10, 10, 10); // is the same as...
+ | var c = paper.el("circle").attr({
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+ | // and the same as
+ | var c = paper.el("circle", {
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+\*/
+Paper.prototype.el = function (name, attr) {
+    var el = make(name, this.node);
+    attr && el.attr(attr);
+    return el;
+};
+/*\
+ * Element.children
+ [ method ]
+ **
+ * Returns array of all the children of the element.
+ = (array) array of Elements
+\*/
+Element.prototype.children = function () {
+    var out = [],
+        ch = this.node.childNodes;
+    for (var i = 0, ii = ch.length; i < ii; i++) {
+        out[i] = Snap(ch[i]);
+    }
+    return out;
+};
+function jsonFiller(root, o) {
+    for (var i = 0, ii = root.length; i < ii; i++) {
+        var item = {
+                type: root[i].type,
+                attr: root[i].attr()
+            },
+            children = root[i].children();
+        o.push(item);
+        if (children.length) {
+            jsonFiller(children, item.childNodes = []);
+        }
+    }
+}
+/*\
+ * Element.toJSON
+ [ method ]
+ **
+ * Returns object representation of the given element and all its children.
+ = (object) in format
+ o {
+ o     type (string) this.type,
+ o     attr (object) attributes map,
+ o     childNodes (array) optional array of children in the same format
+ o }
+\*/
+Element.prototype.toJSON = function () {
+    var out = [];
+    jsonFiller([this], out);
+    return out[0];
+};
+// default
+eve.on("snap.util.getattr", function () {
+    var att = eve.nt();
+    att = att.substring(att.lastIndexOf(".") + 1);
+    var css = att.replace(/[A-Z]/g, function (letter) {
+        return "-" + letter.toLowerCase();
+    });
+    if (cssAttr[has](css)) {
+        return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css);
+    } else {
+        return $(this.node, att);
+    }
+});
+var cssAttr = {
+    "alignment-baseline": 0,
+    "baseline-shift": 0,
+    "clip": 0,
+    "clip-path": 0,
+    "clip-rule": 0,
+    "color": 0,
+    "color-interpolation": 0,
+    "color-interpolation-filters": 0,
+    "color-profile": 0,
+    "color-rendering": 0,
+    "cursor": 0,
+    "direction": 0,
+    "display": 0,
+    "dominant-baseline": 0,
+    "enable-background": 0,
+    "fill": 0,
+    "fill-opacity": 0,
+    "fill-rule": 0,
+    "filter": 0,
+    "flood-color": 0,
+    "flood-opacity": 0,
+    "font": 0,
+    "font-family": 0,
+    "font-size": 0,
+    "font-size-adjust": 0,
+    "font-stretch": 0,
+    "font-style": 0,
+    "font-variant": 0,
+    "font-weight": 0,
+    "glyph-orientation-horizontal": 0,
+    "glyph-orientation-vertical": 0,
+    "image-rendering": 0,
+    "kerning": 0,
+    "letter-spacing": 0,
+    "lighting-color": 0,
+    "marker": 0,
+    "marker-end": 0,
+    "marker-mid": 0,
+    "marker-start": 0,
+    "mask": 0,
+    "opacity": 0,
+    "overflow": 0,
+    "pointer-events": 0,
+    "shape-rendering": 0,
+    "stop-color": 0,
+    "stop-opacity": 0,
+    "stroke": 0,
+    "stroke-dasharray": 0,
+    "stroke-dashoffset": 0,
+    "stroke-linecap": 0,
+    "stroke-linejoin": 0,
+    "stroke-miterlimit": 0,
+    "stroke-opacity": 0,
+    "stroke-width": 0,
+    "text-anchor": 0,
+    "text-decoration": 0,
+    "text-rendering": 0,
+    "unicode-bidi": 0,
+    "visibility": 0,
+    "word-spacing": 0,
+    "writing-mode": 0
+};
+
+eve.on("snap.util.attr", function (value) {
+    var att = eve.nt(),
+        attr = {};
+    att = att.substring(att.lastIndexOf(".") + 1);
+    attr[att] = value;
+    var style = att.replace(/-(\w)/gi, function (all, letter) {
+            return letter.toUpperCase();
+        }),
+        css = att.replace(/[A-Z]/g, function (letter) {
+            return "-" + letter.toLowerCase();
+        });
+    if (cssAttr[has](css)) {
+        this.node.style[style] = value == null ? E : value;
+    } else {
+        $(this.node, attr);
+    }
+});
+(function (proto) {}(Paper.prototype));
+
+// simple ajax
+/*\
+ * Snap.ajax
+ [ method ]
+ **
+ * Simple implementation of Ajax
+ **
+ - url (string) URL
+ - postData (object|string) data for post request
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ * or
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ = (XMLHttpRequest) the XMLHttpRequest object, just in case
+\*/
+Snap.ajax = function (url, postData, callback, scope){
+    var req = new XMLHttpRequest,
+        id = ID();
+    if (req) {
+        if (is(postData, "function")) {
+            scope = callback;
+            callback = postData;
+            postData = null;
+        } else if (is(postData, "object")) {
+            var pd = [];
+            for (var key in postData) if (postData.hasOwnProperty(key)) {
+                pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
+            }
+            postData = pd.join("&");
+        }
+        req.open((postData ? "POST" : "GET"), url, true);
+        if (postData) {
+            req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+            req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+        }
+        if (callback) {
+            eve.once("snap.ajax." + id + ".0", callback);
+            eve.once("snap.ajax." + id + ".200", callback);
+            eve.once("snap.ajax." + id + ".304", callback);
+        }
+        req.onreadystatechange = function() {
+            if (req.readyState != 4) return;
+            eve("snap.ajax." + id + "." + req.status, scope, req);
+        };
+        if (req.readyState == 4) {
+            return req;
+        }
+        req.send(postData);
+        return req;
+    }
+};
+/*\
+ * Snap.load
+ [ method ]
+ **
+ * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX)
+ **
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+\*/
+Snap.load = function (url, callback, scope) {
+    Snap.ajax(url, function (req) {
+        var f = Snap.parse(req.responseText);
+        scope ? callback.call(scope, f) : callback(f);
+    });
+};
+var getOffset = function (elem) {
+    var box = elem.getBoundingClientRect(),
+        doc = elem.ownerDocument,
+        body = doc.body,
+        docElem = doc.documentElement,
+        clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+        top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
+        left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+    return {
+        y: top,
+        x: left
+    };
+};
+/*\
+ * Snap.getElementByPoint
+ [ method ]
+ **
+ * Returns you topmost element under given point.
+ **
+ = (object) Snap element object
+ - x (number) x coordinate from the top left corner of the window
+ - y (number) y coordinate from the top left corner of the window
+ > Usage
+ | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
+\*/
+Snap.getElementByPoint = function (x, y) {
+    var paper = this,
+        svg = paper.canvas,
+        target = glob.doc.elementFromPoint(x, y);
+    if (glob.win.opera && target.tagName == "svg") {
+        var so = getOffset(target),
+            sr = target.createSVGRect();
+        sr.x = x - so.x;
+        sr.y = y - so.y;
+        sr.width = sr.height = 1;
+        var hits = target.getIntersectionList(sr, null);
+        if (hits.length) {
+            target = hits[hits.length - 1];
+        }
+    }
+    if (!target) {
+        return null;
+    }
+    return wrap(target);
+};
+/*\
+ * Snap.plugin
+ [ method ]
+ **
+ * Let you write plugins. You pass in a function with five arguments, like this:
+ | Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
+ |     Snap.newmethod = function () {};
+ |     Element.prototype.newmethod = function () {};
+ |     Paper.prototype.newmethod = function () {};
+ | });
+ * Inside the function you have access to all main objects (and their
+ * prototypes). This allow you to extend anything you want.
+ **
+ - f (function) your plugin body
+\*/
+Snap.plugin = function (f) {
+    f(Snap, Element, Paper, glob, Fragment);
+};
+glob.win.Snap = Snap;
+return Snap;
+}(window || this));
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        Str = String,
+        unit2px = Snap._unit2px,
+        $ = Snap._.$,
+        make = Snap._.make,
+        getSomeDefs = Snap._.getSomeDefs,
+        has = "hasOwnProperty",
+        wrap = Snap._.wrap;
+    /*\
+     * Element.getBBox
+     [ method ]
+     **
+     * Returns the bounding box descriptor for the given element
+     **
+     = (object) bounding box descriptor:
+     o {
+     o     cx: (number) x of the center,
+     o     cy: (number) x of the center,
+     o     h: (number) height,
+     o     height: (number) height,
+     o     path: (string) path command for the box,
+     o     r0: (number) radius of a circle that fully encloses the box,
+     o     r1: (number) radius of the smallest circle that can be enclosed,
+     o     r2: (number) radius of the largest circle that can be enclosed,
+     o     vb: (string) box as a viewbox command,
+     o     w: (number) width,
+     o     width: (number) width,
+     o     x2: (number) x of the right side,
+     o     x: (number) x of the left side,
+     o     y2: (number) y of the bottom edge,
+     o     y: (number) y of the top edge
+     o }
+    \*/
+    elproto.getBBox = function (isWithoutTransform) {
+        if (!Snap.Matrix || !Snap.path) {
+            return this.node.getBBox();
+        }
+        var el = this,
+            m = new Snap.Matrix;
+        if (el.removed) {
+            return Snap._.box();
+        }
+        while (el.type == "use") {
+            if (!isWithoutTransform) {
+                m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0));
+            }
+            if (el.original) {
+                el = el.original;
+            } else {
+                var href = el.attr("xlink:href");
+                el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1));
+            }
+        }
+        var _ = el._,
+            pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt;
+        try {
+            if (isWithoutTransform) {
+                _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox());
+                return Snap._.box(_.bboxwt);
+            } else {
+                el.realPath = pathfinder(el);
+                el.matrix = el.transform().localMatrix;
+                _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix)));
+                return Snap._.box(_.bbox);
+            }
+        } catch (e) {
+            // Firefox doesn’t give you bbox of hidden element
+            return Snap._.box();
+        }
+    };
+    var propString = function () {
+        return this.string;
+    };
+    function extractTransform(el, tstr) {
+        if (tstr == null) {
+            var doReturn = true;
+            if (el.type == "linearGradient" || el.type == "radialGradient") {
+                tstr = el.node.getAttribute("gradientTransform");
+            } else if (el.type == "pattern") {
+                tstr = el.node.getAttribute("patternTransform");
+            } else {
+                tstr = el.node.getAttribute("transform");
+            }
+            if (!tstr) {
+                return new Snap.Matrix;
+            }
+            tstr = Snap._.svgTransform2string(tstr);
+        } else {
+            if (!Snap._.rgTransform.test(tstr)) {
+                tstr = Snap._.svgTransform2string(tstr);
+            } else {
+                tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || "");
+            }
+            if (is(tstr, "array")) {
+                tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr);
+            }
+            el._.transform = tstr;
+        }
+        var m = Snap._.transform2matrix(tstr, el.getBBox(1));
+        if (doReturn) {
+            return m;
+        } else {
+            el.matrix = m;
+        }
+    }
+    /*\
+     * Element.transform
+     [ method ]
+     **
+     * Gets or sets transformation of the element
+     **
+     - tstr (string) transform string in Snap or SVG format
+     = (Element) the current element
+     * or
+     = (object) transformation descriptor:
+     o {
+     o     string (string) transform string,
+     o     globalMatrix (Matrix) matrix of all transformations applied to element or its parents,
+     o     localMatrix (Matrix) matrix of transformations applied only to the element,
+     o     diffMatrix (Matrix) matrix of difference between global and local transformations,
+     o     global (string) global transformation as string,
+     o     local (string) local transformation as string,
+     o     toString (function) returns `string` property
+     o }
+    \*/
+    elproto.transform = function (tstr) {
+        var _ = this._;
+        if (tstr == null) {
+            var papa = this,
+                global = new Snap.Matrix(this.node.getCTM()),
+                local = extractTransform(this),
+                ms = [local],
+                m = new Snap.Matrix,
+                i,
+                localString = local.toTransformString(),
+                string = Str(local) == Str(this.matrix) ?
+                            Str(_.transform) : localString;
+            while (papa.type != "svg" && (papa = papa.parent())) {
+                ms.push(extractTransform(papa));
+            }
+            i = ms.length;
+            while (i--) {
+                m.add(ms[i]);
+            }
+            return {
+                string: string,
+                globalMatrix: global,
+                totalMatrix: m,
+                localMatrix: local,
+                diffMatrix: global.clone().add(local.invert()),
+                global: global.toTransformString(),
+                total: m.toTransformString(),
+                local: localString,
+                toString: propString
+            };
+        }
+        if (tstr instanceof Snap.Matrix) {
+            this.matrix = tstr;
+            this._.transform = tstr.toTransformString();
+        } else {
+            extractTransform(this, tstr);
+        }
+
+        if (this.node) {
+            if (this.type == "linearGradient" || this.type == "radialGradient") {
+                $(this.node, {gradientTransform: this.matrix});
+            } else if (this.type == "pattern") {
+                $(this.node, {patternTransform: this.matrix});
+            } else {
+                $(this.node, {transform: this.matrix});
+            }
+        }
+
+        return this;
+    };
+    /*\
+     * Element.parent
+     [ method ]
+     **
+     * Returns the element's parent
+     **
+     = (Element) the parent element
+    \*/
+    elproto.parent = function () {
+        return wrap(this.node.parentNode);
+    };
+    /*\
+     * Element.append
+     [ method ]
+     **
+     * Appends the given element to current one
+     **
+     - el (Element|Set) element to append
+     = (Element) the parent element
+    \*/
+    /*\
+     * Element.add
+     [ method ]
+     **
+     * See @Element.append
+    \*/
+    elproto.append = elproto.add = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this;
+                el.forEach(function (el) {
+                    it.add(el);
+                });
+                return this;
+            }
+            el = wrap(el);
+            this.node.appendChild(el.node);
+            el.paper = this.paper;
+        }
+        return this;
+    };
+    /*\
+     * Element.appendTo
+     [ method ]
+     **
+     * Appends the current element to the given one
+     **
+     - el (Element) parent element to append to
+     = (Element) the child element
+    \*/
+    elproto.appendTo = function (el) {
+        if (el) {
+            el = wrap(el);
+            el.append(this);
+        }
+        return this;
+    };
+    /*\
+     * Element.prepend
+     [ method ]
+     **
+     * Prepends the given element to the current one
+     **
+     - el (Element) element to prepend
+     = (Element) the parent element
+    \*/
+    elproto.prepend = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this,
+                    first;
+                el.forEach(function (el) {
+                    if (first) {
+                        first.after(el);
+                    } else {
+                        it.prepend(el);
+                    }
+                    first = el;
+                });
+                return this;
+            }
+            el = wrap(el);
+            var parent = el.parent();
+            this.node.insertBefore(el.node, this.node.firstChild);
+            this.add && this.add();
+            el.paper = this.paper;
+            this.parent() && this.parent().add();
+            parent && parent.add();
+        }
+        return this;
+    };
+    /*\
+     * Element.prependTo
+     [ method ]
+     **
+     * Prepends the current element to the given one
+     **
+     - el (Element) parent element to prepend to
+     = (Element) the child element
+    \*/
+    elproto.prependTo = function (el) {
+        el = wrap(el);
+        el.prepend(this);
+        return this;
+    };
+    /*\
+     * Element.before
+     [ method ]
+     **
+     * Inserts given element before the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.before = function (el) {
+        if (el.type == "set") {
+            var it = this;
+            el.forEach(function (el) {
+                var parent = el.parent();
+                it.node.parentNode.insertBefore(el.node, it.node);
+                parent && parent.add();
+            });
+            this.parent().add();
+            return this;
+        }
+        el = wrap(el);
+        var parent = el.parent();
+        this.node.parentNode.insertBefore(el.node, this.node);
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.after
+     [ method ]
+     **
+     * Inserts given element after the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.after = function (el) {
+        el = wrap(el);
+        var parent = el.parent();
+        if (this.node.nextSibling) {
+            this.node.parentNode.insertBefore(el.node, this.node.nextSibling);
+        } else {
+            this.node.parentNode.appendChild(el.node);
+        }
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.insertBefore
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertBefore = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.insertAfter
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertAfter = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node.nextSibling);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.remove
+     [ method ]
+     **
+     * Removes element from the DOM
+     = (Element) the detached element
+    \*/
+    elproto.remove = function () {
+        var parent = this.parent();
+        this.node.parentNode && this.node.parentNode.removeChild(this.node);
+        delete this.paper;
+        this.removed = true;
+        parent && parent.add();
+        return this;
+    };
+    /*\
+     * Element.select
+     [ method ]
+     **
+     * Gathers the nested @Element matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Element) result of query selection
+    \*/
+    elproto.select = function (query) {
+        return wrap(this.node.querySelector(query));
+    };
+    /*\
+     * Element.selectAll
+     [ method ]
+     **
+     * Gathers nested @Element objects matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Set|array) result of query selection
+    \*/
+    elproto.selectAll = function (query) {
+        var nodelist = this.node.querySelectorAll(query),
+            set = (Snap.set || Array)();
+        for (var i = 0; i < nodelist.length; i++) {
+            set.push(wrap(nodelist[i]));
+        }
+        return set;
+    };
+    /*\
+     * Element.asPX
+     [ method ]
+     **
+     * Returns given attribute of the element as a `px` value (not %, em, etc.)
+     **
+     - attr (string) attribute name
+     - value (string) #optional attribute value
+     = (Element) result of query selection
+    \*/
+    elproto.asPX = function (attr, value) {
+        if (value == null) {
+            value = this.attr(attr);
+        }
+        return +unit2px(this, attr, value);
+    };
+    // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned <use> instantiates. It's a part of SVG with which ordinary web developers may be least familiar.
+    /*\
+     * Element.use
+     [ method ]
+     **
+     * Creates a `<use>` element linked to the current element
+     **
+     = (Element) the `<use>` element
+    \*/
+    elproto.use = function () {
+        var use,
+            id = this.node.id;
+        if (!id) {
+            id = this.id;
+            $(this.node, {
+                id: id
+            });
+        }
+        if (this.type == "linearGradient" || this.type == "radialGradient" ||
+            this.type == "pattern") {
+            use = make(this.type, this.node.parentNode);
+        } else {
+            use = make("use", this.node.parentNode);
+        }
+        $(use.node, {
+            "xlink:href": "#" + id
+        });
+        use.original = this;
+        return use;
+    };
+    function fixids(el) {
+        var els = el.selectAll("*"),
+            it,
+            url = /^\s*url\(("|'|)(.*)\1\)\s*$/,
+            ids = [],
+            uses = {};
+        function urltest(it, name) {
+            var val = $(it.node, name);
+            val = val && val.match(url);
+            val = val && val[2];
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    var attr = {};
+                    attr[name] = URL(id);
+                    $(it.node, attr);
+                });
+            }
+        }
+        function linktest(it) {
+            var val = $(it.node, "xlink:href");
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    it.attr("xlink:href", "#" + id);
+                });
+            }
+        }
+        for (var i = 0, ii = els.length; i < ii; i++) {
+            it = els[i];
+            urltest(it, "fill");
+            urltest(it, "stroke");
+            urltest(it, "filter");
+            urltest(it, "mask");
+            urltest(it, "clip-path");
+            linktest(it);
+            var oldid = $(it.node, "id");
+            if (oldid) {
+                $(it.node, {id: it.id});
+                ids.push({
+                    old: oldid,
+                    id: it.id
+                });
+            }
+        }
+        for (i = 0, ii = ids.length; i < ii; i++) {
+            var fs = uses[ids[i].old];
+            if (fs) {
+                for (var j = 0, jj = fs.length; j < jj; j++) {
+                    fs[j](ids[i].id);
+                }
+            }
+        }
+    }
+    /*\
+     * Element.clone
+     [ method ]
+     **
+     * Creates a clone of the element and inserts it after the element
+     **
+     = (Element) the clone
+    \*/
+    elproto.clone = function () {
+        var clone = wrap(this.node.cloneNode(true));
+        if ($(clone.node, "id")) {
+            $(clone.node, {id: clone.id});
+        }
+        fixids(clone);
+        clone.insertAfter(this);
+        return clone;
+    };
+    /*\
+     * Element.toDefs
+     [ method ]
+     **
+     * Moves element to the shared `<defs>` area
+     **
+     = (Element) the element
+    \*/
+    elproto.toDefs = function () {
+        var defs = getSomeDefs(this);
+        defs.appendChild(this.node);
+        return this;
+    };
+    /*\
+     * Element.toPattern
+     [ method ]
+     **
+     * Creates a `<pattern>` element from the current element
+     **
+     * To create a pattern you have to specify the pattern rect:
+     - x (string|number)
+     - y (string|number)
+     - width (string|number)
+     - height (string|number)
+     = (Element) the `<pattern>` element
+     * You can use pattern later on as an argument for `fill` attribute:
+     | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
+     |         fill: "none",
+     |         stroke: "#bada55",
+     |         strokeWidth: 5
+     |     }).pattern(0, 0, 10, 10),
+     |     c = paper.circle(200, 200, 100);
+     | c.attr({
+     |     fill: p
+     | });
+    \*/
+    elproto.pattern = elproto.toPattern = function (x, y, width, height) {
+        var p = make("pattern", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        $(p.node, {
+            x: x,
+            y: y,
+            width: width,
+            height: height,
+            patternUnits: "userSpaceOnUse",
+            id: p.id,
+            viewBox: [x, y, width, height].join(" ")
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+// SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path.
+// SIERRA Element.marker(): I suggest the method should accept default reference point values.  Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where?  Couldn't they also be assigned default values?
+    /*\
+     * Element.marker
+     [ method ]
+     **
+     * Creates a `<marker>` element from the current element
+     **
+     * To create a marker you have to specify the bounding rect and reference point:
+     - x (number)
+     - y (number)
+     - width (number)
+     - height (number)
+     - refX (number)
+     - refY (number)
+     = (Element) the `<marker>` element
+     * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end.
+    \*/
+    // TODO add usage for markers
+    elproto.marker = function (x, y, width, height, refX, refY) {
+        var p = make("marker", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            refX = x.refX || x.cx;
+            refY = x.refY || x.cy;
+            x = x.x;
+        }
+        $(p.node, {
+            viewBox: [x, y, width, height].join(" "),
+            markerWidth: width,
+            markerHeight: height,
+            orient: "auto",
+            refX: refX || 0,
+            refY: refY || 0,
+            id: p.id
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+    // animation
+    function slice(from, to, f) {
+        return function (arr) {
+            var res = arr.slice(from, to);
+            if (res.length == 1) {
+                res = res[0];
+            }
+            return f ? f(res) : res;
+        };
+    }
+    var Animation = function (attr, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        this.attr = attr;
+        this.dur = ms;
+        easing && (this.easing = easing);
+        callback && (this.callback = callback);
+    };
+    Snap._.Animation = Animation;
+    /*\
+     * Snap.animation
+     [ method ]
+     **
+     * Creates an animation object
+     **
+     - attr (object) attributes of final destination
+     - duration (number) duration of the animation, in milliseconds
+     - easing (function) #optional one of easing functions of @mina or custom one
+     - callback (function) #optional callback function that fires when animation ends
+     = (object) animation object
+    \*/
+    Snap.animation = function (attr, ms, easing, callback) {
+        return new Animation(attr, ms, easing, callback);
+    };
+    /*\
+     * Element.inAnim
+     [ method ]
+     **
+     * Returns a set of animations that may be able to manipulate the current element
+     **
+     = (object) in format:
+     o {
+     o     anim (object) animation object,
+     o     mina (object) @mina object,
+     o     curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+    \*/
+    elproto.inAnim = function () {
+        var el = this,
+            res = [];
+        for (var id in el.anims) if (el.anims[has](id)) {
+            (function (a) {
+                res.push({
+                    anim: new Animation(a._attrs, a.dur, a.easing, a._callback),
+                    mina: a,
+                    curStatus: a.status(),
+                    status: function (val) {
+                        return a.status(val);
+                    },
+                    stop: function () {
+                        a.stop();
+                    }
+                });
+            }(el.anims[id]));
+        }
+        return res;
+    };
+    /*\
+     * Snap.animate
+     [ method ]
+     **
+     * Runs generic animation of one number into another with a caring function
+     **
+     - from (number|array) number or array of numbers
+     - to (number|array) number or array of numbers
+     - setter (function) caring function that accepts one number argument
+     - duration (number) duration, in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function to execute when animation ends
+     = (object) animation object in @mina format
+     o {
+     o     id (string) animation id, consider it read-only,
+     o     duration (function) gets or sets the duration of the animation,
+     o     easing (function) easing,
+     o     speed (function) gets or sets the speed of the animation,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+     | var rect = Snap().rect(0, 0, 10, 10);
+     | Snap.animate(0, 10, function (val) {
+     |     rect.attr({
+     |         x: val
+     |     });
+     | }, 1000);
+     | // in given context is equivalent to
+     | rect.animate({x: 10}, 1000);
+    \*/
+    Snap.animate = function (from, to, setter, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        var now = mina.time(),
+            anim = mina(from, to, now, now + ms, mina.time, setter, easing);
+        callback && eve.once("mina.finish." + anim.id, callback);
+        return anim;
+    };
+    /*\
+     * Element.stop
+     [ method ]
+     **
+     * Stops all the animations for the current element
+     **
+     = (Element) the current element
+    \*/
+    elproto.stop = function () {
+        var anims = this.inAnim();
+        for (var i = 0, ii = anims.length; i < ii; i++) {
+            anims[i].stop();
+        }
+        return this;
+    };
+    /*\
+     * Element.animate
+     [ method ]
+     **
+     * Animates the given attributes of the element
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     = (Element) the current element
+    \*/
+    elproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = attrs.dur;
+            attrs = attrs.attr;
+        }
+        var fkeys = [], tkeys = [], keys = {}, from, to, f, eq,
+            el = this;
+        for (var key in attrs) if (attrs[has](key)) {
+            if (el.equal) {
+                eq = el.equal(key, Str(attrs[key]));
+                from = eq.from;
+                to = eq.to;
+                f = eq.f;
+            } else {
+                from = +el.attr(key);
+                to = +attrs[key];
+            }
+            var len = is(from, "array") ? from.length : 1;
+            keys[key] = slice(fkeys.length, fkeys.length + len, f);
+            fkeys = fkeys.concat(from);
+            tkeys = tkeys.concat(to);
+        }
+        var now = mina.time(),
+            anim = mina(fkeys, tkeys, now, now + ms, mina.time, function (val) {
+                var attr = {};
+                for (var key in keys) if (keys[has](key)) {
+                    attr[key] = keys[key](val);
+                }
+                el.attr(attr);
+            }, easing);
+        el.anims[anim.id] = anim;
+        anim._attrs = attrs;
+        anim._callback = callback;
+        eve("snap.animcreated." + el.id, anim);
+        eve.once("mina.finish." + anim.id, function () {
+            delete el.anims[anim.id];
+            callback && callback.call(el);
+        });
+        eve.once("mina.stop." + anim.id, function () {
+            delete el.anims[anim.id];
+        });
+        return el;
+    };
+    var eldata = {};
+    /*\
+     * Element.data
+     [ method ]
+     **
+     * Adds or retrieves given value associated with given key. (Don’t confuse
+     * with `data-` attributes)
+     *
+     * See also @Element.removeData
+     - key (string) key to store data
+     - value (any) #optional value to store
+     = (object) @Element
+     * or, if value is not specified:
+     = (any) value
+     > Usage
+     | for (var i = 0, i < 5, i++) {
+     |     paper.circle(10 + 15 * i, 10, 10)
+     |          .attr({fill: "#000"})
+     |          .data("i", i)
+     |          .click(function () {
+     |             alert(this.data("i"));
+     |          });
+     | }
+    \*/
+    elproto.data = function (key, value) {
+        var data = eldata[this.id] = eldata[this.id] || {};
+        if (arguments.length == 0){
+            eve("snap.data.get." + this.id, this, data, null);
+            return data;
+        }
+        if (arguments.length == 1) {
+            if (Snap.is(key, "object")) {
+                for (var i in key) if (key[has](i)) {
+                    this.data(i, key[i]);
+                }
+                return this;
+            }
+            eve("snap.data.get." + this.id, this, data[key], key);
+            return data[key];
+        }
+        data[key] = value;
+        eve("snap.data.set." + this.id, this, value, key);
+        return this;
+    };
+    /*\
+     * Element.removeData
+     [ method ]
+     **
+     * Removes value associated with an element by given key.
+     * If key is not provided, removes all the data of the element.
+     - key (string) #optional key
+     = (object) @Element
+    \*/
+    elproto.removeData = function (key) {
+        if (key == null) {
+            eldata[this.id] = {};
+        } else {
+            eldata[this.id] && delete eldata[this.id][key];
+        }
+        return this;
+    };
+    /*\
+     * Element.outerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element, equivalent to HTML's `outerHTML`.
+     *
+     * See also @Element.innerSVG
+     = (string) SVG code for the element
+    \*/
+    /*\
+     * Element.toString
+     [ method ]
+     **
+     * See @Element.outerSVG
+    \*/
+    elproto.outerSVG = elproto.toString = toString(1);
+    /*\
+     * Element.innerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML`
+     = (string) SVG code for the element
+    \*/
+    elproto.innerSVG = toString();
+    function toString(type) {
+        return function () {
+            var res = type ? "<" + this.type : "",
+                attr = this.node.attributes,
+                chld = this.node.childNodes;
+            if (type) {
+                for (var i = 0, ii = attr.length; i < ii; i++) {
+                    res += " " + attr[i].name + '="' +
+                            attr[i].value.replace(/"/g, '\\"') + '"';
+                }
+            }
+            if (chld.length) {
+                type && (res += ">");
+                for (i = 0, ii = chld.length; i < ii; i++) {
+                    if (chld[i].nodeType == 3) {
+                        res += chld[i].nodeValue;
+                    } else if (chld[i].nodeType == 1) {
+                        res += wrap(chld[i]).toString();
+                    }
+                }
+                type && (res += "</" + this.type + ">");
+            } else {
+                type && (res += "/>");
+            }
+            return res;
+        };
+    }
+    elproto.toDataURL = function () {
+        if (window && window.btoa) {
+            var bb = this.getBBox(),
+                svg = Snap.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>', {
+                x: +bb.x.toFixed(3),
+                y: +bb.y.toFixed(3),
+                width: +bb.width.toFixed(3),
+                height: +bb.height.toFixed(3),
+                contents: this.outerSVG()
+            });
+            return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
+        }
+    };
+    /*\
+     * Fragment.select
+     [ method ]
+     **
+     * See @Element.select
+    \*/
+    Fragment.prototype.select = elproto.select;
+    /*\
+     * Fragment.selectAll
+     [ method ]
+     **
+     * See @Element.selectAll
+    \*/
+    Fragment.prototype.selectAll = elproto.selectAll;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var objectToString = Object.prototype.toString,
+        Str = String,
+        math = Math,
+        E = "";
+    function Matrix(a, b, c, d, e, f) {
+        if (b == null && objectToString.call(a) == "[object SVGMatrix]") {
+            this.a = a.a;
+            this.b = a.b;
+            this.c = a.c;
+            this.d = a.d;
+            this.e = a.e;
+            this.f = a.f;
+            return;
+        }
+        if (a != null) {
+            this.a = +a;
+            this.b = +b;
+            this.c = +c;
+            this.d = +d;
+            this.e = +e;
+            this.f = +f;
+        } else {
+            this.a = 1;
+            this.b = 0;
+            this.c = 0;
+            this.d = 1;
+            this.e = 0;
+            this.f = 0;
+        }
+    }
+    (function (matrixproto) {
+        /*\
+         * Matrix.add
+         [ method ]
+         **
+         * Adds the given matrix to existing one
+         - a (number)
+         - b (number)
+         - c (number)
+         - d (number)
+         - e (number)
+         - f (number)
+         * or
+         - matrix (object) @Matrix
+        \*/
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
+
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
+
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
+                }
+            }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+            return this;
+        };
+        /*\
+         * Matrix.invert
+         [ method ]
+         **
+         * Returns an inverted version of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        /*\
+         * Matrix.clone
+         [ method ]
+         **
+         * Returns a copy of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        /*\
+         * Matrix.translate
+         [ method ]
+         **
+         * Translate the matrix
+         - x (number) horizontal offset distance
+         - y (number) vertical offset distance
+        \*/
+        matrixproto.translate = function (x, y) {
+            return this.add(1, 0, 0, 1, x, y);
+        };
+        /*\
+         * Matrix.scale
+         [ method ]
+         **
+         * Scales the matrix
+         - x (number) amount to be scaled, with `1` resulting in no change
+         - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.)
+         - cx (number) #optional horizontal origin point from which to scale
+         - cy (number) #optional vertical origin point from which to scale
+         * Default cx, cy is the middle point of the element.
+        \*/
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+            return this;
+        };
+        /*\
+         * Matrix.rotate
+         [ method ]
+         **
+         * Rotates the matrix
+         - a (number) angle of rotation, in degrees
+         - x (number) horizontal origin point from which to rotate
+         - y (number) vertical origin point from which to rotate
+        \*/
+        matrixproto.rotate = function (a, x, y) {
+            a = Snap.rad(a);
+            x = x || 0;
+            y = y || 0;
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            return this.add(1, 0, 0, 1, -x, -y);
+        };
+        /*\
+         * Matrix.x
+         [ method ]
+         **
+         * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y
+         - x (number)
+         - y (number)
+         = (number) x
+        \*/
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        /*\
+         * Matrix.y
+         [ method ]
+         **
+         * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x
+         - x (number)
+         - y (number)
+         = (number) y
+        \*/
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
+        }
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] && (a[0] /= mag);
+            a[1] && (a[1] /= mag);
+        }
+        /*\
+         * Matrix.determinant
+         [ method ]
+         **
+         * Finds determinant of the given matrix.
+         = (number) determinant
+        \*/
+        matrixproto.determinant = function () {
+            return this.a * this.d - this.b * this.c;
+        };
+        /*\
+         * Matrix.split
+         [ method ]
+         **
+         * Splits matrix into primitive transformations
+         = (object) in format:
+         o dx (number) translation by x
+         o dy (number) translation by y
+         o scalex (number) scale by x
+         o scaley (number) scale by y
+         o shear (number) shear
+         o rotate (number) rotation in deg
+         o isSimple (boolean) could it be represented via simple transformations
+        \*/
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
+
+            if (this.determinant() < 0) {
+                out.scalex = -out.scalex;
+            }
+
+            // rotation
+            var sin = -row[0][1],
+                cos = row[1][1];
+            if (cos < 0) {
+                out.rotate = Snap.deg(math.acos(cos));
+                if (sin < 0) {
+                    out.rotate = 360 - out.rotate;
+                }
+            } else {
+                out.rotate = Snap.deg(math.asin(sin));
+            }
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+            return out;
+        };
+        /*\
+         * Matrix.toTransformString
+         [ method ]
+         **
+         * Returns transform string that represents given matrix
+         = (string) transform string
+        \*/
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this.split();
+            if (!+s.shear.toFixed(9)) {
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) +
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E);
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+    /*\
+     * Snap.Matrix
+     [ method ]
+     **
+     * Matrix constructor, extend on your own risk.
+     * To create matrices use @Snap.matrix.
+    \*/
+    Snap.Matrix = Matrix;
+    /*\
+     * Snap.matrix
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns a matrix based on the given parameters
+     - a (number)
+     - b (number)
+     - c (number)
+     - d (number)
+     - e (number)
+     - f (number)
+     * or
+     - svgMatrix (SVGMatrix)
+     = (object) @Matrix
+    \*/
+    Snap.matrix = function (a, b, c, d, e, f) {
+        return new Matrix(a, b, c, d, e, f);
+    };
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var has = "hasOwnProperty",
+        make = Snap._.make,
+        wrap = Snap._.wrap,
+        is = Snap.is,
+        getSomeDefs = Snap._.getSomeDefs,
+        reURLValue = /^url\(#?([^)]+)\)$/,
+        $ = Snap._.$,
+        URL = Snap.url,
+        Str = String,
+        separator = Snap._.separator,
+        E = "";
+    // Attributes event handlers
+    eve.on("snap.util.attr.mask", function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value.type == "mask") {
+                var mask = value;
+            } else {
+                mask = make("mask", getSomeDefs(this));
+                mask.node.appendChild(value.node);
+            }
+            !mask.node.id && $(mask.node, {
+                id: mask.id
+            });
+            $(this.node, {
+                mask: URL(mask.id)
+            });
+        }
+    });
+    (function (clipIt) {
+        eve.on("snap.util.attr.clip", clipIt);
+        eve.on("snap.util.attr.clip-path", clipIt);
+        eve.on("snap.util.attr.clipPath", clipIt);
+    }(function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value.type == "clipPath") {
+                var clip = value;
+            } else {
+                clip = make("clipPath", getSomeDefs(this));
+                clip.node.appendChild(value.node);
+                !clip.node.id && $(clip.node, {
+                    id: clip.id
+                });
+            }
+            $(this.node, {
+                "clip-path": URL(clip.node.id || clip.id)
+            });
+        }
+    }));
+    function fillStroke(name) {
+        return function (value) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1 &&
+                (value.node.firstChild.tagName == "radialGradient" ||
+                value.node.firstChild.tagName == "linearGradient" ||
+                value.node.firstChild.tagName == "pattern")) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value instanceof Element) {
+                if (value.type == "radialGradient" || value.type == "linearGradient"
+                   || value.type == "pattern") {
+                    if (!value.node.id) {
+                        $(value.node, {
+                            id: value.id
+                        });
+                    }
+                    var fill = URL(value.node.id);
+                } else {
+                    fill = value.attr(name);
+                }
+            } else {
+                fill = Snap.color(value);
+                if (fill.error) {
+                    var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value);
+                    if (grad) {
+                        if (!grad.node.id) {
+                            $(grad.node, {
+                                id: grad.id
+                            });
+                        }
+                        fill = URL(grad.node.id);
+                    } else {
+                        fill = value;
+                    }
+                } else {
+                    fill = Str(fill);
+                }
+            }
+            var attrs = {};
+            attrs[name] = fill;
+            $(this.node, attrs);
+            this.node.style[name] = E;
+        };
+    }
+    eve.on("snap.util.attr.fill", fillStroke("fill"));
+    eve.on("snap.util.attr.stroke", fillStroke("stroke"));
+    var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i;
+    eve.on("snap.util.grad.parse", function parseGrad(string) {
+        string = Str(string);
+        var tokens = string.match(gradrg);
+        if (!tokens) {
+            return null;
+        }
+        var type = tokens[1],
+            params = tokens[2],
+            stops = tokens[3];
+        params = params.split(/\s*,\s*/).map(function (el) {
+            return +el == el ? +el : el;
+        });
+        if (params.length == 1 && params[0] == 0) {
+            params = [];
+        }
+        stops = stops.split("-");
+        stops = stops.map(function (el) {
+            el = el.split(":");
+            var out = {
+                color: el[0]
+            };
+            if (el[1]) {
+                out.offset = parseFloat(el[1]);
+            }
+            return out;
+        });
+        return {
+            type: type,
+            params: params,
+            stops: stops
+        };
+    });
+
+    eve.on("snap.util.attr.d", function (value) {
+        eve.stop();
+        if (is(value, "array") && is(value[0], "array")) {
+            value = Snap.path.toString.call(value);
+        }
+        value = Str(value);
+        if (value.match(/[ruo]/i)) {
+            value = Snap.path.toAbsolute(value);
+        }
+        $(this.node, {d: value});
+    })(-1);
+    eve.on("snap.util.attr.#text", function (value) {
+        eve.stop();
+        value = Str(value);
+        var txt = glob.doc.createTextNode(value);
+        while (this.node.firstChild) {
+            this.node.removeChild(this.node.firstChild);
+        }
+        this.node.appendChild(txt);
+    })(-1);
+    eve.on("snap.util.attr.path", function (value) {
+        eve.stop();
+        this.attr({d: value});
+    })(-1);
+    eve.on("snap.util.attr.class", function (value) {
+        eve.stop();
+        this.node.className.baseVal = value;
+    })(-1);
+    eve.on("snap.util.attr.viewBox", function (value) {
+        var vb;
+        if (is(value, "object") && "x" in value) {
+            vb = [value.x, value.y, value.width, value.height].join(" ");
+        } else if (is(value, "array")) {
+            vb = value.join(" ");
+        } else {
+            vb = value;
+        }
+        $(this.node, {
+            viewBox: vb
+        });
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.transform", function (value) {
+        this.transform(value);
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.r", function (value) {
+        if (this.type == "rect") {
+            eve.stop();
+            $(this.node, {
+                rx: value,
+                ry: value
+            });
+        }
+    })(-1);
+    eve.on("snap.util.attr.textpath", function (value) {
+        eve.stop();
+        if (this.type == "text") {
+            var id, tp, node;
+            if (!value && this.textPath) {
+                tp = this.textPath;
+                while (tp.node.firstChild) {
+                    this.node.appendChild(tp.node.firstChild);
+                }
+                tp.remove();
+                delete this.textPath;
+                return;
+            }
+            if (is(value, "string")) {
+                var defs = getSomeDefs(this),
+                    path = wrap(defs.parentNode).path(value);
+                defs.appendChild(path.node);
+                id = path.id;
+                path.attr({id: id});
+            } else {
+                value = wrap(value);
+                if (value instanceof Element) {
+                    id = value.attr("id");
+                    if (!id) {
+                        id = value.id;
+                        value.attr({id: id});
+                    }
+                }
+            }
+            if (id) {
+                tp = this.textPath;
+                node = this.node;
+                if (tp) {
+                    tp.attr({"xlink:href": "#" + id});
+                } else {
+                    tp = $("textPath", {
+                        "xlink:href": "#" + id
+                    });
+                    while (node.firstChild) {
+                        tp.appendChild(node.firstChild);
+                    }
+                    node.appendChild(tp);
+                    this.textPath = wrap(tp);
+                }
+            }
+        }
+    })(-1);
+    eve.on("snap.util.attr.text", function (value) {
+        if (this.type == "text") {
+            var i = 0,
+                node = this.node,
+                tuner = function (chunk) {
+                    var out = $("tspan");
+                    if (is(chunk, "array")) {
+                        for (var i = 0; i < chunk.length; i++) {
+                            out.appendChild(tuner(chunk[i]));
+                        }
+                    } else {
+                        out.appendChild(glob.doc.createTextNode(chunk));
+                    }
+                    out.normalize && out.normalize();
+                    return out;
+                };
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            var tuned = tuner(value);
+            while (tuned.firstChild) {
+                node.appendChild(tuned.firstChild);
+            }
+        }
+        eve.stop();
+    })(-1);
+    function setFontSize(value) {
+        eve.stop();
+        if (value == +value) {
+            value += "px";
+        }
+        this.node.style.fontSize = value;
+    }
+    eve.on("snap.util.attr.fontSize", setFontSize)(-1);
+    eve.on("snap.util.attr.font-size", setFontSize)(-1);
+
+
+    eve.on("snap.util.getattr.transform", function () {
+        eve.stop();
+        return this.transform();
+    })(-1);
+    eve.on("snap.util.getattr.textpath", function () {
+        eve.stop();
+        return this.textPath;
+    })(-1);
+    // Markers
+    (function () {
+        function getter(end) {
+            return function () {
+                eve.stop();
+                var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end);
+                if (style == "none") {
+                    return style;
+                } else {
+                    return Snap(glob.doc.getElementById(style.match(reURLValue)[1]));
+                }
+            };
+        }
+        function setter(end) {
+            return function (value) {
+                eve.stop();
+                var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1);
+                if (value == "" || !value) {
+                    this.node.style[name] = "none";
+                    return;
+                }
+                if (value.type == "marker") {
+                    var id = value.node.id;
+                    if (!id) {
+                        $(value.node, {id: value.id});
+                    }
+                    this.node.style[name] = URL(id);
+                    return;
+                }
+            };
+        }
+        eve.on("snap.util.getattr.marker-end", getter("end"))(-1);
+        eve.on("snap.util.getattr.markerEnd", getter("end"))(-1);
+        eve.on("snap.util.getattr.marker-start", getter("start"))(-1);
+        eve.on("snap.util.getattr.markerStart", getter("start"))(-1);
+        eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1);
+        eve.on("snap.util.getattr.markerMid", getter("mid"))(-1);
+        eve.on("snap.util.attr.marker-end", setter("end"))(-1);
+        eve.on("snap.util.attr.markerEnd", setter("end"))(-1);
+        eve.on("snap.util.attr.marker-start", setter("start"))(-1);
+        eve.on("snap.util.attr.markerStart", setter("start"))(-1);
+        eve.on("snap.util.attr.marker-mid", setter("mid"))(-1);
+        eve.on("snap.util.attr.markerMid", setter("mid"))(-1);
+    }());
+    eve.on("snap.util.getattr.r", function () {
+        if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
+            eve.stop();
+            return $(this.node, "rx");
+        }
+    })(-1);
+    function textExtract(node) {
+        var out = [];
+        var children = node.childNodes;
+        for (var i = 0, ii = children.length; i < ii; i++) {
+            var chi = children[i];
+            if (chi.nodeType == 3) {
+                out.push(chi.nodeValue);
+            }
+            if (chi.tagName == "tspan") {
+                if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) {
+                    out.push(chi.firstChild.nodeValue);
+                } else {
+                    out.push(textExtract(chi));
+                }
+            }
+        }
+        return out;
+    }
+    eve.on("snap.util.getattr.text", function () {
+        if (this.type == "text" || this.type == "tspan") {
+            eve.stop();
+            var out = textExtract(this.node);
+            return out.length == 1 ? out[0] : out;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.#text", function () {
+        return this.node.textContent;
+    })(-1);
+    eve.on("snap.util.getattr.viewBox", function () {
+        eve.stop();
+        var vb = $(this.node, "viewBox");
+        if (vb) {
+            vb = vb.split(separator);
+            return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.points", function () {
+        var p = $(this.node, "points");
+        eve.stop();
+        if (p) {
+            return p.split(separator);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.path", function () {
+        var p = $(this.node, "d");
+        eve.stop();
+        return p;
+    })(-1);
+    eve.on("snap.util.getattr.class", function () {
+        return this.node.className.baseVal;
+    })(-1);
+    function getFontSize() {
+        eve.stop();
+        return this.node.style.fontSize;
+    }
+    eve.on("snap.util.getattr.fontSize", getFontSize)(-1);
+    eve.on("snap.util.getattr.font-size", getFontSize)(-1);
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var rgNotSpace = /\S+/g,
+        rgBadSpace = /[\t\r\n\f]/g,
+        rgTrim = /(^\s+|\s+$)/g,
+        Str = String,
+        elproto = Element.prototype;
+    /*\
+     * Element.addClass
+     [ method ]
+     **
+     * Adds given class name or list of class names to the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.addClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+
+        if (classes.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (!~pos) {
+                    curClasses.push(clazz);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.removeClass
+     [ method ]
+     **
+     * Removes given class name or list of class names from the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.removeClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        if (curClasses.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (~pos) {
+                    curClasses.splice(pos, 1);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.hasClass
+     [ method ]
+     **
+     * Checks if the element has a given class name in the list of class names applied to it.
+     - value (string) class name
+     **
+     = (boolean) `true` if the element has given class
+    \*/
+    elproto.hasClass = function (value) {
+        var elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [];
+        return !!~curClasses.indexOf(value);
+    };
+    /*\
+     * Element.toggleClass
+     [ method ]
+     **
+     * Add or remove one or more classes from the element, depending on either
+     * the class’s presence or the value of the `flag` argument.
+     - value (string) class name or space separated list of class names
+     - flag (boolean) value to determine whether the class should be added or removed
+     **
+     = (Element) original element.
+    \*/
+    elproto.toggleClass = function (value, flag) {
+        if (flag != null) {
+            if (flag) {
+                return this.addClass(value);
+            } else {
+                return this.removeClass(value);
+            }
+        }
+        var classes = (value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        j = 0;
+        while ((clazz = classes[j++])) {
+            pos = curClasses.indexOf(clazz);
+            if (~pos) {
+                curClasses.splice(pos, 1);
+            } else {
+                curClasses.push(clazz);
+            }
+        }
+
+        finalValue = curClasses.join(" ");
+        if (className != finalValue) {
+            elem.className.baseVal = finalValue;
+        }
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var operators = {
+            "+": function (x, y) {
+                    return x + y;
+                },
+            "-": function (x, y) {
+                    return x - y;
+                },
+            "/": function (x, y) {
+                    return x / y;
+                },
+            "*": function (x, y) {
+                    return x * y;
+                }
+        },
+        Str = String,
+        reUnit = /[a-z]+$/i,
+        reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    eve.on("snap.util.attr", function (val) {
+        var plus = Str(val).match(reAddon);
+        if (plus) {
+            var evnt = eve.nt(),
+                name = evnt.substring(evnt.lastIndexOf(".") + 1),
+                a = this.attr(name),
+                atr = {};
+            eve.stop();
+            var unit = plus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[plus[1]];
+            if (aUnit && aUnit == unit) {
+                val = op(parseFloat(a), +plus[2]);
+            } else {
+                a = this.asPX(name);
+                val = op(this.asPX(name), this.asPX(name, plus[2] + unit));
+            }
+            if (isNaN(a) || isNaN(val)) {
+                return;
+            }
+            atr[name] = val;
+            this.attr(atr);
+        }
+    })(-10);
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this,
+            bplus = Str(b).match(reAddon);
+        if (bplus) {
+            eve.stop();
+            var unit = bplus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[bplus[1]];
+            if (aUnit && aUnit == unit) {
+                return {
+                    from: parseFloat(a),
+                    to: op(parseFloat(a), +bplus[2]),
+                    f: getUnit(aUnit)
+                };
+            } else {
+                a = this.asPX(name);
+                return {
+                    from: a,
+                    to: op(a, this.asPX(name, bplus[2] + unit)),
+                    f: getNumber
+                };
+            }
+        }
+    })(-10);
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var proto = Paper.prototype,
+        is = Snap.is;
+    /*\
+     * Paper.rect
+     [ method ]
+     *
+     * Draws a rectangle
+     **
+     - x (number) x coordinate of the top left corner
+     - y (number) y coordinate of the top left corner
+     - width (number) width
+     - height (number) height
+     - rx (number) #optional horizontal radius for rounded corners, default is 0
+     - ry (number) #optional vertical radius for rounded corners, default is rx or 0
+     = (object) the `rect` element
+     **
+     > Usage
+     | // regular rectangle
+     | var c = paper.rect(10, 10, 50, 50);
+     | // rectangle with rounded corners
+     | var c = paper.rect(40, 40, 50, 50, 10);
+    \*/
+    proto.rect = function (x, y, w, h, rx, ry) {
+        var attr;
+        if (ry == null) {
+            ry = rx;
+        }
+        if (is(x, "object") && x == "[object Object]") {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                width: w,
+                height: h
+            };
+            if (rx != null) {
+                attr.rx = rx;
+                attr.ry = ry;
+            }
+        }
+        return this.el("rect", attr);
+    };
+    /*\
+     * Paper.circle
+     [ method ]
+     **
+     * Draws a circle
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - r (number) radius
+     = (object) the `circle` element
+     **
+     > Usage
+     | var c = paper.circle(50, 50, 40);
+    \*/
+    proto.circle = function (cx, cy, r) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr = {
+                cx: cx,
+                cy: cy,
+                r: r
+            };
+        }
+        return this.el("circle", attr);
+    };
+
+    var preload = (function () {
+        function onerror() {
+            this.parentNode.removeChild(this);
+        }
+        return function (src, f) {
+            var img = glob.doc.createElement("img"),
+                body = glob.doc.body;
+            img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+            img.onload = function () {
+                f.call(img);
+                img.onload = img.onerror = null;
+                body.removeChild(img);
+            };
+            img.onerror = onerror;
+            body.appendChild(img);
+            img.src = src;
+        };
+    }());
+
+    /*\
+     * Paper.image
+     [ method ]
+     **
+     * Places an image on the surface
+     **
+     - src (string) URI of the source image
+     - x (number) x offset position
+     - y (number) y offset position
+     - width (number) width of the image
+     - height (number) height of the image
+     = (object) the `image` element
+     * or
+     = (object) Snap element object with type `image`
+     **
+     > Usage
+     | var c = paper.image("apple.png", 10, 10, 80, 80);
+    \*/
+    proto.image = function (src, x, y, width, height) {
+        var el = this.el("image");
+        if (is(src, "object") && "src" in src) {
+            el.attr(src);
+        } else if (src != null) {
+            var set = {
+                "xlink:href": src,
+                preserveAspectRatio: "none"
+            };
+            if (x != null && y != null) {
+                set.x = x;
+                set.y = y;
+            }
+            if (width != null && height != null) {
+                set.width = width;
+                set.height = height;
+            } else {
+                preload(src, function () {
+                    Snap._.$(el.node, {
+                        width: this.offsetWidth,
+                        height: this.offsetHeight
+                    });
+                });
+            }
+            Snap._.$(el.node, set);
+        }
+        return el;
+    };
+    /*\
+     * Paper.ellipse
+     [ method ]
+     **
+     * Draws an ellipse
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - rx (number) horizontal radius
+     - ry (number) vertical radius
+     = (object) the `ellipse` element
+     **
+     > Usage
+     | var c = paper.ellipse(50, 50, 40, 20);
+    \*/
+    proto.ellipse = function (cx, cy, rx, ry) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr ={
+                cx: cx,
+                cy: cy,
+                rx: rx,
+                ry: ry
+            };
+        }
+        return this.el("ellipse", attr);
+    };
+    // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier.
+    /*\
+     * Paper.path
+     [ method ]
+     **
+     * Creates a `<path>` element using the given string as the path's definition
+     - pathString (string) #optional path string in SVG format
+     * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:
+     | "M10,20L30,40"
+     * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.
+     *
+     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
+     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
+     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
+     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
+     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
+     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
+     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
+     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
+     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
+     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
+     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
+     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
+     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
+     * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier.
+     * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point.
+     > Usage
+     | var c = paper.path("M10 10L90 90");
+     | // draw a diagonal line:
+     | // move to 10,10, line to 90,90
+    \*/
+    proto.path = function (d) {
+        var attr;
+        if (is(d, "object") && !is(d, "array")) {
+            attr = d;
+        } else if (d) {
+            attr = {d: d};
+        }
+        return this.el("path", attr);
+    };
+    /*\
+     * Paper.g
+     [ method ]
+     **
+     * Creates a group element
+     **
+     - varargs (…) #optional elements to nest within the group
+     = (object) the `g` element
+     **
+     > Usage
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g(c2, c1); // note that the order of elements is different
+     * or
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g();
+     | g.add(c2, c1);
+    \*/
+    /*\
+     * Paper.group
+     [ method ]
+     **
+     * See @Paper.g
+    \*/
+    proto.group = proto.g = function (first) {
+        var attr,
+            el = this.el("g");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.svg
+     [ method ]
+     **
+     * Creates a nested SVG element.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `svg` element
+     **
+    \*/
+    proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) {
+        var attrs = {};
+        if (is(x, "object") && y == null) {
+            attrs = x;
+        } else {
+            if (x != null) {
+                attrs.x = x;
+            }
+            if (y != null) {
+                attrs.y = y;
+            }
+            if (width != null) {
+                attrs.width = width;
+            }
+            if (height != null) {
+                attrs.height = height;
+            }
+            if (vbx != null && vby != null && vbw != null && vbh != null) {
+                attrs.viewBox = [vbx, vby, vbw, vbh];
+            }
+        }
+        return this.el("svg", attrs);
+    };
+    /*\
+     * Paper.mask
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a mask.
+     **
+     = (object) the `mask` element
+     **
+    \*/
+    proto.mask = function (first) {
+        var attr,
+            el = this.el("mask");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.ptrn
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a pattern.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `pattern` element
+     **
+    \*/
+    proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) {
+        if (is(x, "object")) {
+            var attr = x;
+        } else {
+            attr = {patternUnits: "userSpaceOnUse"};
+            if (x) {
+                attr.x = x;
+            }
+            if (y) {
+                attr.y = y;
+            }
+            if (width != null) {
+                attr.width = width;
+            }
+            if (height != null) {
+                attr.height = height;
+            }
+            if (vx != null && vy != null && vw != null && vh != null) {
+                attr.viewBox = [vx, vy, vw, vh];
+            } else {
+                attr.viewBox = [x || 0, y || 0, width || 0, height || 0];
+            }
+        }
+        return this.el("pattern", attr);
+    };
+    /*\
+     * Paper.use
+     [ method ]
+     **
+     * Creates a <use> element.
+     - id (string) @optional id of element to link
+     * or
+     - id (Element) @optional element to link
+     **
+     = (object) the `use` element
+     **
+    \*/
+    proto.use = function (id) {
+        if (id != null) {
+            if (id instanceof Element) {
+                if (!id.attr("id")) {
+                    id.attr({id: Snap._.id(id)});
+                }
+                id = id.attr("id");
+            }
+            if (String(id).charAt() == "#") {
+                id = id.substring(1);
+            }
+            return this.el("use", {"xlink:href": "#" + id});
+        } else {
+            return Element.prototype.use.call(this);
+        }
+    };
+    /*\
+     * Paper.symbol
+     [ method ]
+     **
+     * Creates a <symbol> element.
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     = (object) the `symbol` element
+     **
+    \*/
+    proto.symbol = function (vx, vy, vw, vh) {
+        var attr = {};
+        if (vx != null && vy != null && vw != null && vh != null) {
+            attr.viewBox = [vx, vy, vw, vh];
+        }
+
+        return this.el("symbol", attr);
+    };
+    /*\
+     * Paper.text
+     [ method ]
+     **
+     * Draws a text string
+     **
+     - x (number) x coordinate position
+     - y (number) y coordinate position
+     - text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
+     = (object) the `text` element
+     **
+     > Usage
+     | var t1 = paper.text(50, 50, "Snap");
+     | var t2 = paper.text(50, 50, ["S","n","a","p"]);
+     | // Text path usage
+     | t1.attr({textpath: "M10,10L100,100"});
+     | // or
+     | var pth = paper.path("M10,10L100,100");
+     | t1.attr({textpath: pth});
+    \*/
+    proto.text = function (x, y, text) {
+        var attr = {};
+        if (is(x, "object")) {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                text: text || ""
+            };
+        }
+        return this.el("text", attr);
+    };
+    /*\
+     * Paper.line
+     [ method ]
+     **
+     * Draws a line
+     **
+     - x1 (number) x coordinate position of the start
+     - y1 (number) y coordinate position of the start
+     - x2 (number) x coordinate position of the end
+     - y2 (number) y coordinate position of the end
+     = (object) the `line` element
+     **
+     > Usage
+     | var t1 = paper.line(50, 50, 100, 100);
+    \*/
+    proto.line = function (x1, y1, x2, y2) {
+        var attr = {};
+        if (is(x1, "object")) {
+            attr = x1;
+        } else if (x1 != null) {
+            attr = {
+                x1: x1,
+                x2: x2,
+                y1: y1,
+                y2: y2
+            };
+        }
+        return this.el("line", attr);
+    };
+    /*\
+     * Paper.polyline
+     [ method ]
+     **
+     * Draws a polyline
+     **
+     - points (array) array of points
+     * or
+     - varargs (…) points
+     = (object) the `polyline` element
+     **
+     > Usage
+     | var p1 = paper.polyline([10, 10, 100, 100]);
+     | var p2 = paper.polyline(10, 10, 100, 100);
+    \*/
+    proto.polyline = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polyline", attr);
+    };
+    /*\
+     * Paper.polygon
+     [ method ]
+     **
+     * Draws a polygon. See @Paper.polyline
+    \*/
+    proto.polygon = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polygon", attr);
+    };
+    // gradients
+    (function () {
+        var $ = Snap._.$;
+        // gradients' helpers
+        function Gstops() {
+            return this.selectAll("stop");
+        }
+        function GaddStop(color, offset) {
+            var stop = $("stop"),
+                attr = {
+                    offset: +offset + "%"
+                };
+            color = Snap.color(color);
+            attr["stop-color"] = color.hex;
+            if (color.opacity < 1) {
+                attr["stop-opacity"] = color.opacity;
+            }
+            $(stop, attr);
+            this.node.appendChild(stop);
+            return this;
+        }
+        function GgetBBox() {
+            if (this.type == "linearGradient") {
+                var x1 = $(this.node, "x1") || 0,
+                    x2 = $(this.node, "x2") || 1,
+                    y1 = $(this.node, "y1") || 0,
+                    y2 = $(this.node, "y2") || 0;
+                return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1));
+            } else {
+                var cx = this.node.cx || .5,
+                    cy = this.node.cy || .5,
+                    r = this.node.r || 0;
+                return Snap._.box(cx - r, cy - r, r * 2, r * 2);
+            }
+        }
+        function gradient(defs, str) {
+            var grad = eve("snap.util.grad.parse", null, str).firstDefined(),
+                el;
+            if (!grad) {
+                return null;
+            }
+            grad.params.unshift(defs);
+            if (grad.type.toLowerCase() == "l") {
+                el = gradientLinear.apply(0, grad.params);
+            } else {
+                el = gradientRadial.apply(0, grad.params);
+            }
+            if (grad.type != grad.type.toLowerCase()) {
+                $(el.node, {
+                    gradientUnits: "userSpaceOnUse"
+                });
+            }
+            var stops = grad.stops,
+                len = stops.length,
+                start = 0,
+                j = 0;
+            function seed(i, end) {
+                var step = (end - start) / (i - j);
+                for (var k = j; k < i; k++) {
+                    stops[k].offset = +(+start + step * (k - j)).toFixed(2);
+                }
+                j = i;
+                start = end;
+            }
+            len--;
+            for (var i = 0; i < len; i++) if ("offset" in stops[i]) {
+                seed(i, stops[i].offset);
+            }
+            stops[len].offset = stops[len].offset || 100;
+            seed(len, stops[len].offset);
+            for (i = 0; i <= len; i++) {
+                var stop = stops[i];
+                el.addStop(stop.color, stop.offset);
+            }
+            return el;
+        }
+        function gradientLinear(defs, x1, y1, x2, y2) {
+            var el = Snap._.make("linearGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (x1 != null) {
+                $(el.node, {
+                    x1: x1,
+                    y1: y1,
+                    x2: x2,
+                    y2: y2
+                });
+            }
+            return el;
+        }
+        function gradientRadial(defs, cx, cy, r, fx, fy) {
+            var el = Snap._.make("radialGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (cx != null) {
+                $(el.node, {
+                    cx: cx,
+                    cy: cy,
+                    r: r
+                });
+            }
+            if (fx != null && fy != null) {
+                $(el.node, {
+                    fx: fx,
+                    fy: fy
+                });
+            }
+            return el;
+        }
+        /*\
+         * Paper.gradient
+         [ method ]
+         **
+         * Creates a gradient element
+         **
+         - gradient (string) gradient descriptor
+         > Gradient Descriptor
+         * The gradient descriptor is an expression formatted as
+         * follows: `<type>(<coords>)<colors>`.  The `<type>` can be
+         * either linear or radial.  The uppercase `L` or `R` letters
+         * indicate absolute coordinates offset from the SVG surface.
+         * Lowercase `l` or `r` letters indicate coordinates
+         * calculated relative to the element to which the gradient is
+         * applied.  Coordinates specify a linear gradient vector as
+         * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`,
+         * `r` and optional `fx`, `fy` specifying a focal point away
+         * from the center of the circle. Specify `<colors>` as a list
+         * of dash-separated CSS color values.  Each color may be
+         * followed by a custom offset value, separated with a colon
+         * character.
+         > Examples
+         * Linear gradient, relative from top-left corner to bottom-right
+         * corner, from black through red to white:
+         | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
+         * Linear gradient, absolute from (0, 0) to (100, 100), from black
+         * through red at 25% to white:
+         | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");
+         * Radial gradient, relative from the center of the element with radius
+         * half the width, from black to white:
+         | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
+         * To apply the gradient:
+         | paper.circle(50, 50, 40).attr({
+         |     fill: g
+         | });
+         = (object) the `gradient` element
+        \*/
+        proto.gradient = function (str) {
+            return gradient(this.defs, str);
+        };
+        proto.gradientLinear = function (x1, y1, x2, y2) {
+            return gradientLinear(this.defs, x1, y1, x2, y2);
+        };
+        proto.gradientRadial = function (cx, cy, r, fx, fy) {
+            return gradientRadial(this.defs, cx, cy, r, fx, fy);
+        };
+        /*\
+         * Paper.toString
+         [ method ]
+         **
+         * Returns SVG code for the @Paper
+         = (string) SVG code for the @Paper
+        \*/
+        proto.toString = function () {
+            var doc = this.node.ownerDocument,
+                f = doc.createDocumentFragment(),
+                d = doc.createElement("div"),
+                svg = this.node.cloneNode(true),
+                res;
+            f.appendChild(d);
+            d.appendChild(svg);
+            Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"});
+            res = d.innerHTML;
+            f.removeChild(f.firstChild);
+            return res;
+        };
+        /*\
+         * Paper.toDataURL
+         [ method ]
+         **
+         * Returns SVG code for the @Paper as Data URI string.
+         = (string) Data URI string
+        \*/
+        proto.toDataURL = function () {
+            if (window && window.btoa) {
+                return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this)));
+            }
+        };
+        /*\
+         * Paper.clear
+         [ method ]
+         **
+         * Removes all child nodes of the paper, except <defs>.
+        \*/
+        proto.clear = function () {
+            var node = this.node.firstChild,
+                next;
+            while (node) {
+                next = node.nextSibling;
+                if (node.tagName != "defs") {
+                    node.parentNode.removeChild(node);
+                } else {
+                    proto.clear.call({node: node});
+                }
+                node = next;
+            }
+        };
+    }());
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        clone = Snap._.clone,
+        has = "hasOwnProperty",
+        p2s = /,?([a-z]),?/gi,
+        toFloat = parseFloat,
+        math = Math,
+        PI = math.PI,
+        mmin = math.min,
+        mmax = math.max,
+        pow = math.pow,
+        abs = math.abs;
+    function paths(ps) {
+        var p = paths.ps = paths.ps || {};
+        if (p[ps]) {
+            p[ps].sleep = 100;
+        } else {
+            p[ps] = {
+                sleep: 100
+            };
+        }
+        setTimeout(function () {
+            for (var key in p) if (p[has](key) && key != ps) {
+                p[key].sleep--;
+                !p[key].sleep && delete p[key];
+            }
+        });
+        return p[ps];
+    }
+    function box(x, y, width, height) {
+        if (x == null) {
+            x = y = width = height = 0;
+        }
+        if (y == null) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        return {
+            x: x,
+            y: y,
+            width: width,
+            w: width,
+            height: height,
+            h: height,
+            x2: x + width,
+            y2: y + height,
+            cx: x + width / 2,
+            cy: y + height / 2,
+            r1: math.min(width, height) / 2,
+            r2: math.max(width, height) / 2,
+            r0: math.sqrt(width * width + height * height) / 2,
+            path: rectPath(x, y, width, height),
+            vb: [x, y, width, height].join(" ")
+        };
+    }
+    function toString() {
+        return this.join(",").replace(p2s, "$1");
+    }
+    function pathClone(pathArray) {
+        var res = clone(pathArray);
+        res.toString = toString;
+        return res;
+    }
+    function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        if (length == null) {
+            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+        } else {
+            return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
+                getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+        }
+    }
+    function getLengthFactory(istotal, subpath) {
+        function O(val) {
+            return +(+val).toFixed(3);
+        }
+        return Snap._.cacher(function (path, length, onlystart) {
+            if (path instanceof Element) {
+                path = path.attr("d");
+            }
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            sp += [
+                                "C" + O(point.start.x),
+                                O(point.start.y),
+                                O(point.m.x),
+                                O(point.m.y),
+                                O(point.x),
+                                O(point.y)
+                            ];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = [
+                                "M" + O(point.x),
+                                O(point.y) + "C" + O(point.n.x),
+                                O(point.n.y),
+                                O(point.end.x),
+                                O(point.end.y),
+                                O(p[5]),
+                                O(p[6])
+                            ].join();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            return point;
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p.shift() + p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+            return point;
+        }, null, Snap._.clone);
+    }
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            t13 = pow(t1, 3),
+            t12 = pow(t1, 2),
+            t2 = t * t,
+            t3 = t2 * t,
+            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+            ax = t1 * p1x + t * c1x,
+            ay = t1 * p1y + t * c1y,
+            cx = t1 * c2x + t * p2x,
+            cy = t1 * c2y + t * p2y,
+            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+        // (mx > nx || my < ny) && (alpha += 180);
+        return {
+            x: x,
+            y: y,
+            m: {x: mx, y: my},
+            n: {x: nx, y: ny},
+            start: {x: ax, y: ay},
+            end: {x: cx, y: cy},
+            alpha: alpha
+        };
+    }
+    function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        if (!Snap.is(p1x, "array")) {
+            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+        }
+        var bbox = curveDim.apply(null, p1x);
+        return box(
+            bbox.min.x,
+            bbox.min.y,
+            bbox.max.x - bbox.min.x,
+            bbox.max.y - bbox.min.y
+        );
+    }
+    function isPointInsideBBox(bbox, x, y) {
+        return  x >= bbox.x &&
+                x <= bbox.x + bbox.width &&
+                y >= bbox.y &&
+                y <= bbox.y + bbox.height;
+    }
+    function isBBoxIntersect(bbox1, bbox2) {
+        bbox1 = box(bbox1);
+        bbox2 = box(bbox2);
+        return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2)
+            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
+                || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
+                || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+    }
+    function base3(t, p1, p2, p3, p4) {
+        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+        return t * t2 - 3 * p1 + 3 * p2;
+    }
+    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+        if (z == null) {
+            z = 1;
+        }
+        z = z > 1 ? 1 : z < 0 ? 0 : z;
+        var z2 = z / 2,
+            n = 12,
+            Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
+            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
+            sum = 0;
+        for (var i = 0; i < n; i++) {
+            var ct = z2 * Tvalues[i] + z2,
+                xbase = base3(ct, x1, x2, x3, x4),
+                ybase = base3(ct, y1, y2, y3, y4),
+                comb = xbase * xbase + ybase * ybase;
+            sum += Cvalues[i] * math.sqrt(comb);
+        }
+        return z2 * sum;
+    }
+    function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+            return;
+        }
+        var t = 1,
+            step = t / 2,
+            t2 = t - step,
+            l,
+            e = .01;
+        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        while (abs(l - ll) > e) {
+            step /= 2;
+            t2 += (l < ll ? 1 : -1) * step;
+            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        }
+        return t2;
+    }
+    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+        if (
+            mmax(x1, x2) < mmin(x3, x4) ||
+            mmin(x1, x2) > mmax(x3, x4) ||
+            mmax(y1, y2) < mmin(y3, y4) ||
+            mmin(y1, y2) > mmax(y3, y4)
+        ) {
+            return;
+        }
+        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+        if (!denominator) {
+            return;
+        }
+        var px = nx / denominator,
+            py = ny / denominator,
+            px2 = +px.toFixed(2),
+            py2 = +py.toFixed(2);
+        if (
+            px2 < +mmin(x1, x2).toFixed(2) ||
+            px2 > +mmax(x1, x2).toFixed(2) ||
+            px2 < +mmin(x3, x4).toFixed(2) ||
+            px2 > +mmax(x3, x4).toFixed(2) ||
+            py2 < +mmin(y1, y2).toFixed(2) ||
+            py2 > +mmax(y1, y2).toFixed(2) ||
+            py2 < +mmin(y3, y4).toFixed(2) ||
+            py2 > +mmax(y3, y4).toFixed(2)
+        ) {
+            return;
+        }
+        return {x: px, y: py};
+    }
+    function inter(bez1, bez2) {
+        return interHelper(bez1, bez2);
+    }
+    function interCount(bez1, bez2) {
+        return interHelper(bez1, bez2, 1);
+    }
+    function interHelper(bez1, bez2, justCount) {
+        var bbox1 = bezierBBox(bez1),
+            bbox2 = bezierBBox(bez2);
+        if (!isBBoxIntersect(bbox1, bbox2)) {
+            return justCount ? 0 : [];
+        }
+        var l1 = bezlen.apply(0, bez1),
+            l2 = bezlen.apply(0, bez2),
+            n1 = ~~(l1 / 8),
+            n2 = ~~(l2 / 8),
+            dots1 = [],
+            dots2 = [],
+            xy = {},
+            res = justCount ? 0 : [];
+        for (var i = 0; i < n1 + 1; i++) {
+            var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
+            dots1.push({x: p.x, y: p.y, t: i / n1});
+        }
+        for (i = 0; i < n2 + 1; i++) {
+            p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
+            dots2.push({x: p.x, y: p.y, t: i / n2});
+        }
+        for (i = 0; i < n1; i++) {
+            for (var j = 0; j < n2; j++) {
+                var di = dots1[i],
+                    di1 = dots1[i + 1],
+                    dj = dots2[j],
+                    dj1 = dots2[j + 1],
+                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+                if (is) {
+                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+                        continue;
+                    }
+                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
+                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+                        if (justCount) {
+                            res++;
+                        } else {
+                            res.push({
+                                x: is.x,
+                                y: is.y,
+                                t1: t1,
+                                t2: t2
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function pathIntersection(path1, path2) {
+        return interPathHelper(path1, path2);
+    }
+    function pathIntersectionNumber(path1, path2) {
+        return interPathHelper(path1, path2, 1);
+    }
+    function interPathHelper(path1, path2, justCount) {
+        path1 = path2curve(path1);
+        path2 = path2curve(path2);
+        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+            res = justCount ? 0 : [];
+        for (var i = 0, ii = path1.length; i < ii; i++) {
+            var pi = path1[i];
+            if (pi[0] == "M") {
+                x1 = x1m = pi[1];
+                y1 = y1m = pi[2];
+            } else {
+                if (pi[0] == "C") {
+                    bez1 = [x1, y1].concat(pi.slice(1));
+                    x1 = bez1[6];
+                    y1 = bez1[7];
+                } else {
+                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+                    x1 = x1m;
+                    y1 = y1m;
+                }
+                for (var j = 0, jj = path2.length; j < jj; j++) {
+                    var pj = path2[j];
+                    if (pj[0] == "M") {
+                        x2 = x2m = pj[1];
+                        y2 = y2m = pj[2];
+                    } else {
+                        if (pj[0] == "C") {
+                            bez2 = [x2, y2].concat(pj.slice(1));
+                            x2 = bez2[6];
+                            y2 = bez2[7];
+                        } else {
+                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+                            x2 = x2m;
+                            y2 = y2m;
+                        }
+                        var intr = interHelper(bez1, bez2, justCount);
+                        if (justCount) {
+                            res += intr;
+                        } else {
+                            for (var k = 0, kk = intr.length; k < kk; k++) {
+                                intr[k].segment1 = i;
+                                intr[k].segment2 = j;
+                                intr[k].bez1 = bez1;
+                                intr[k].bez2 = bez2;
+                            }
+                            res = res.concat(intr);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function isPointInsidePath(path, x, y) {
+        var bbox = pathBBox(path);
+        return isPointInsideBBox(bbox, x, y) &&
+               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+    }
+    function pathBBox(path) {
+        var pth = paths(path);
+        if (pth.bbox) {
+            return clone(pth.bbox);
+        }
+        if (!path) {
+            return box();
+        }
+        path = path2curve(path);
+        var x = 0,
+            y = 0,
+            X = [],
+            Y = [],
+            p;
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            p = path[i];
+            if (p[0] == "M") {
+                x = p[1];
+                y = p[2];
+                X.push(x);
+                Y.push(y);
+            } else {
+                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                X = X.concat(dim.min.x, dim.max.x);
+                Y = Y.concat(dim.min.y, dim.max.y);
+                x = p[5];
+                y = p[6];
+            }
+        }
+        var xmin = mmin.apply(0, X),
+            ymin = mmin.apply(0, Y),
+            xmax = mmax.apply(0, X),
+            ymax = mmax.apply(0, Y),
+            bb = box(xmin, ymin, xmax - xmin, ymax - ymin);
+        pth.bbox = clone(bb);
+        return bb;
+    }
+    function rectPath(x, y, w, h, r) {
+        if (r) {
+            return [
+                ["M", +x + (+r), y],
+                ["l", w - r * 2, 0],
+                ["a", r, r, 0, 0, 1, r, r],
+                ["l", 0, h - r * 2],
+                ["a", r, r, 0, 0, 1, -r, r],
+                ["l", r * 2 - w, 0],
+                ["a", r, r, 0, 0, 1, -r, -r],
+                ["l", 0, r * 2 - h],
+                ["a", r, r, 0, 0, 1, r, -r],
+                ["z"]
+            ];
+        }
+        var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+        res.toString = toString;
+        return res;
+    }
+    function ellipsePath(x, y, rx, ry, a) {
+        if (a == null && ry == null) {
+            ry = rx;
+        }
+        x = +x;
+        y = +y;
+        rx = +rx;
+        ry = +ry;
+        if (a != null) {
+            var rad = Math.PI / 180,
+                x1 = x + rx * Math.cos(-ry * rad),
+                x2 = x + rx * Math.cos(-a * rad),
+                y1 = y + rx * Math.sin(-ry * rad),
+                y2 = y + rx * Math.sin(-a * rad),
+                res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]];
+        } else {
+            res = [
+                ["M", x, y],
+                ["m", 0, -ry],
+                ["a", rx, ry, 0, 1, 1, 0, 2 * ry],
+                ["a", rx, ry, 0, 1, 1, 0, -2 * ry],
+                ["z"]
+            ];
+        }
+        res.toString = toString;
+        return res;
+    }
+    var unit2px = Snap._unit2px,
+        getPath = {
+        path: function (el) {
+            return el.attr("path");
+        },
+        circle: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx, attr.cy, attr.r);
+        },
+        ellipse: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry);
+        },
+        rect: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry);
+        },
+        image: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height);
+        },
+        line: function (el) {
+            return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")];
+        },
+        polyline: function (el) {
+            return "M" + el.attr("points");
+        },
+        polygon: function (el) {
+            return "M" + el.attr("points") + "z";
+        },
+        deflt: function (el) {
+            var bbox = el.node.getBBox();
+            return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+        }
+    };
+    function pathToRelative(pathArray) {
+        var pth = paths(pathArray),
+            lowerCase = String.prototype.toLowerCase;
+        if (pth.rel) {
+            return pathClone(pth.rel);
+        }
+        if (!Snap.is(pathArray, "array") || !Snap.is(pathArray && pathArray[0], "array")) {
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0;
+        if (pathArray[0][0] == "M") {
+            x = pathArray[0][1];
+            y = pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res.push(["M", x, y]);
+        }
+        for (var i = start, ii = pathArray.length; i < ii; i++) {
+            var r = res[i] = [],
+                pa = pathArray[i];
+            if (pa[0] != lowerCase.call(pa[0])) {
+                r[0] = lowerCase.call(pa[0]);
+                switch (r[0]) {
+                    case "a":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +(pa[6] - x).toFixed(3);
+                        r[7] = +(pa[7] - y).toFixed(3);
+                        break;
+                    case "v":
+                        r[1] = +(pa[1] - y).toFixed(3);
+                        break;
+                    case "m":
+                        mx = pa[1];
+                        my = pa[2];
+                    default:
+                        for (var j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+                        }
+                }
+            } else {
+                r = res[i] = [];
+                if (pa[0] == "m") {
+                    mx = pa[1] + x;
+                    my = pa[2] + y;
+                }
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    res[i][k] = pa[k];
+                }
+            }
+            var len = res[i].length;
+            switch (res[i][0]) {
+                case "z":
+                    x = mx;
+                    y = my;
+                    break;
+                case "h":
+                    x += +res[i][len - 1];
+                    break;
+                case "v":
+                    y += +res[i][len - 1];
+                    break;
+                default:
+                    x += +res[i][len - 2];
+                    y += +res[i][len - 1];
+            }
+        }
+        res.toString = toString;
+        pth.rel = pathClone(res);
+        return res;
+    }
+    function pathToAbsolute(pathArray) {
+        var pth = paths(pathArray);
+        if (pth.abs) {
+            return pathClone(pth.abs);
+        }
+        if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        if (!pathArray || !pathArray.length) {
+            return [["M", 0, 0]];
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0,
+            pa0;
+        if (pathArray[0][0] == "M") {
+            x = +pathArray[0][1];
+            y = +pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res[0] = ["M", x, y];
+        }
+        var crz = pathArray.length == 3 &&
+            pathArray[0][0] == "M" &&
+            pathArray[1][0].toUpperCase() == "R" &&
+            pathArray[2][0].toUpperCase() == "Z";
+        for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+            res.push(r = []);
+            pa = pathArray[i];
+            pa0 = pa[0];
+            if (pa0 != pa0.toUpperCase()) {
+                r[0] = pa0.toUpperCase();
+                switch (r[0]) {
+                    case "A":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +pa[6] + x;
+                        r[7] = +pa[7] + y;
+                        break;
+                    case "V":
+                        r[1] = +pa[1] + y;
+                        break;
+                    case "H":
+                        r[1] = +pa[1] + x;
+                        break;
+                    case "R":
+                        var dots = [x, y].concat(pa.slice(1));
+                        for (var j = 2, jj = dots.length; j < jj; j++) {
+                            dots[j] = +dots[j] + x;
+                            dots[++j] = +dots[j] + y;
+                        }
+                        res.pop();
+                        res = res.concat(catmullRom2bezier(dots, crz));
+                        break;
+                    case "O":
+                        res.pop();
+                        dots = ellipsePath(x, y, pa[1], pa[2]);
+                        dots.push(dots[0]);
+                        res = res.concat(dots);
+                        break;
+                    case "U":
+                        res.pop();
+                        res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                        r = ["U"].concat(res[res.length - 1].slice(-2));
+                        break;
+                    case "M":
+                        mx = +pa[1] + x;
+                        my = +pa[2] + y;
+                    default:
+                        for (j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +pa[j] + ((j % 2) ? x : y);
+                        }
+                }
+            } else if (pa0 == "R") {
+                dots = [x, y].concat(pa.slice(1));
+                res.pop();
+                res = res.concat(catmullRom2bezier(dots, crz));
+                r = ["R"].concat(pa.slice(-2));
+            } else if (pa0 == "O") {
+                res.pop();
+                dots = ellipsePath(x, y, pa[1], pa[2]);
+                dots.push(dots[0]);
+                res = res.concat(dots);
+            } else if (pa0 == "U") {
+                res.pop();
+                res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                r = ["U"].concat(res[res.length - 1].slice(-2));
+            } else {
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    r[k] = pa[k];
+                }
+            }
+            pa0 = pa0.toUpperCase();
+            if (pa0 != "O") {
+                switch (r[0]) {
+                    case "Z":
+                        x = +mx;
+                        y = +my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
+                    default:
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
+                }
+            }
+        }
+        res.toString = toString;
+        pth.abs = pathClone(res);
+        return res;
+    }
+    function l2c(x1, y1, x2, y2) {
+        return [x1, y1, x2, y2, x2, y2];
+    }
+    function q2c(x1, y1, ax, ay, x2, y2) {
+        var _13 = 1 / 3,
+            _23 = 2 / 3;
+        return [
+                _13 * x1 + _23 * ax,
+                _13 * y1 + _23 * ay,
+                _13 * x2 + _23 * ax,
+                _13 * y2 + _23 * ay,
+                x2,
+                y2
+            ];
+    }
+    function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+        // for more information of where this math came from visit:
+        // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+        var _120 = PI * 120 / 180,
+            rad = PI / 180 * (+angle || 0),
+            res = [],
+            xy,
+            rotate = Snap._.cacher(function (x, y, rad) {
+                var X = x * math.cos(rad) - y * math.sin(rad),
+                    Y = x * math.sin(rad) + y * math.cos(rad);
+                return {x: X, y: Y};
+            });
+        if (!recursive) {
+            xy = rotate(x1, y1, -rad);
+            x1 = xy.x;
+            y1 = xy.y;
+            xy = rotate(x2, y2, -rad);
+            x2 = xy.x;
+            y2 = xy.y;
+            var cos = math.cos(PI / 180 * angle),
+                sin = math.sin(PI / 180 * angle),
+                x = (x1 - x2) / 2,
+                y = (y1 - y2) / 2;
+            var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+            if (h > 1) {
+                h = math.sqrt(h);
+                rx = h * rx;
+                ry = h * ry;
+            }
+            var rx2 = rx * rx,
+                ry2 = ry * ry,
+                k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                    math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                cx = k * rx * y / ry + (x1 + x2) / 2,
+                cy = k * -ry * x / rx + (y1 + y2) / 2,
+                f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+                f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+            f1 = x1 < cx ? PI - f1 : f1;
+            f2 = x2 < cx ? PI - f2 : f2;
+            f1 < 0 && (f1 = PI * 2 + f1);
+            f2 < 0 && (f2 = PI * 2 + f2);
+            if (sweep_flag && f1 > f2) {
+                f1 = f1 - PI * 2;
+            }
+            if (!sweep_flag && f2 > f1) {
+                f2 = f2 - PI * 2;
+            }
+        } else {
+            f1 = recursive[0];
+            f2 = recursive[1];
+            cx = recursive[2];
+            cy = recursive[3];
+        }
+        var df = f2 - f1;
+        if (abs(df) > _120) {
+            var f2old = f2,
+                x2old = x2,
+                y2old = y2;
+            f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+            x2 = cx + rx * math.cos(f2);
+            y2 = cy + ry * math.sin(f2);
+            res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+        }
+        df = f2 - f1;
+        var c1 = math.cos(f1),
+            s1 = math.sin(f1),
+            c2 = math.cos(f2),
+            s2 = math.sin(f2),
+            t = math.tan(df / 4),
+            hx = 4 / 3 * rx * t,
+            hy = 4 / 3 * ry * t,
+            m1 = [x1, y1],
+            m2 = [x1 + hx * s1, y1 - hy * c1],
+            m3 = [x2 + hx * s2, y2 - hy * c2],
+            m4 = [x2, y2];
+        m2[0] = 2 * m1[0] - m2[0];
+        m2[1] = 2 * m1[1] - m2[1];
+        if (recursive) {
+            return [m2, m3, m4].concat(res);
+        } else {
+            res = [m2, m3, m4].concat(res).join().split(",");
+            var newres = [];
+            for (var i = 0, ii = res.length; i < ii; i++) {
+                newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+            }
+            return newres;
+        }
+    }
+    function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t;
+        return {
+            x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+            y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+        };
+    }
+
+    // Returns bounding box of cubic bezier curve.
+    // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+    // Original version: NISHIO Hirokazu
+    // Modifications: https://github.com/timo22345
+    function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) {
+        var tvalues = [],
+            bounds = [[], []],
+            a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+        for (var i = 0; i < 2; ++i) {
+            if (i == 0) {
+                b = 6 * x0 - 12 * x1 + 6 * x2;
+                a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+                c = 3 * x1 - 3 * x0;
+            } else {
+                b = 6 * y0 - 12 * y1 + 6 * y2;
+                a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+                c = 3 * y1 - 3 * y0;
+            }
+            if (abs(a) < 1e-12) {
+                if (abs(b) < 1e-12) {
+                    continue;
+                }
+                t = -c / b;
+                if (0 < t && t < 1) {
+                    tvalues.push(t);
+                }
+                continue;
+            }
+            b2ac = b * b - 4 * c * a;
+            sqrtb2ac = math.sqrt(b2ac);
+            if (b2ac < 0) {
+                continue;
+            }
+            t1 = (-b + sqrtb2ac) / (2 * a);
+            if (0 < t1 && t1 < 1) {
+                tvalues.push(t1);
+            }
+            t2 = (-b - sqrtb2ac) / (2 * a);
+            if (0 < t2 && t2 < 1) {
+                tvalues.push(t2);
+            }
+        }
+
+        var x, y, j = tvalues.length,
+            jlen = j,
+            mt;
+        while (j--) {
+            t = tvalues[j];
+            mt = 1 - t;
+            bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
+            bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
+        }
+
+        bounds[0][jlen] = x0;
+        bounds[1][jlen] = y0;
+        bounds[0][jlen + 1] = x3;
+        bounds[1][jlen + 1] = y3;
+        bounds[0].length = bounds[1].length = jlen + 2;
+
+
+        return {
+          min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])},
+          max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])}
+        };
+    }
+
+    function path2curve(path, path2) {
+        var pth = !path2 && paths(path);
+        if (!path2 && pth.curve) {
+            return pathClone(pth.curve);
+        }
+        var p = pathToAbsolute(path),
+            p2 = path2 && pathToAbsolute(path2),
+            attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            processPath = function (path, d, pcom) {
+                var nx, ny;
+                if (!path) {
+                    return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+                }
+                !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
+                switch (path[0]) {
+                    case "M":
+                        d.X = path[1];
+                        d.Y = path[2];
+                        break;
+                    case "A":
+                        path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
+                        break;
+                    case "S":
+                        if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
+                            nx = d.x * 2 - d.bx;          // And reflect the previous
+                            ny = d.y * 2 - d.by;          // command's control point relative to the current point.
+                        }
+                        else {                            // or some else or nothing
+                            nx = d.x;
+                            ny = d.y;
+                        }
+                        path = ["C", nx, ny].concat(path.slice(1));
+                        break;
+                    case "T":
+                        if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
+                            d.qx = d.x * 2 - d.qx;        // And make a reflection similar
+                            d.qy = d.y * 2 - d.qy;        // to case "S".
+                        }
+                        else {                            // or something else or nothing
+                            d.qx = d.x;
+                            d.qy = d.y;
+                        }
+                        path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+                        break;
+                    case "Q":
+                        d.qx = path[1];
+                        d.qy = path[2];
+                        path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                        break;
+                    case "L":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
+                        break;
+                    case "H":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
+                        break;
+                    case "V":
+                        path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
+                        break;
+                    case "Z":
+                        path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
+                        break;
+                }
+                return path;
+            },
+            fixArc = function (pp, i) {
+                if (pp[i].length > 7) {
+                    pp[i].shift();
+                    var pi = pp[i];
+                    while (pi.length) {
+                        pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved
+                        p2 && (pcoms2[i] = "A"); // the same as above
+                        pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
+                    }
+                    pp.splice(i, 1);
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            fixM = function (path1, path2, a1, a2, i) {
+                if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                    path2.splice(i, 0, ["M", a2.x, a2.y]);
+                    a1.bx = 0;
+                    a1.by = 0;
+                    a1.x = path1[i][1];
+                    a1.y = path1[i][2];
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            pcoms1 = [], // path commands of original path p
+            pcoms2 = [], // path commands of original path p2
+            pfirst = "", // temporary holder for original path command
+            pcom = ""; // holder for previous path command of original path
+        for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+            p[i] && (pfirst = p[i][0]); // save current path command
+
+            if (pfirst != "C") // C is not saved yet, because it may be result of conversion
+            {
+                pcoms1[i] = pfirst; // Save current path command
+                i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom
+            }
+            p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
+
+            if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command
+            // which may produce multiple C:s
+            // so we have to make sure that C is also C in original path
+
+            fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
+
+            if (p2) { // the same procedures is done to p2
+                p2[i] && (pfirst = p2[i][0]);
+                if (pfirst != "C") {
+                    pcoms2[i] = pfirst;
+                    i && (pcom = pcoms2[i - 1]);
+                }
+                p2[i] = processPath(p2[i], attrs2, pcom);
+
+                if (pcoms2[i] != "A" && pfirst == "C") {
+                    pcoms2[i] = "C";
+                }
+
+                fixArc(p2, i);
+            }
+            fixM(p, p2, attrs, attrs2, i);
+            fixM(p2, p, attrs2, attrs, i);
+            var seg = p[i],
+                seg2 = p2 && p2[i],
+                seglen = seg.length,
+                seg2len = p2 && seg2.length;
+            attrs.x = seg[seglen - 2];
+            attrs.y = seg[seglen - 1];
+            attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+            attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+            attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+            attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+            attrs2.x = p2 && seg2[seg2len - 2];
+            attrs2.y = p2 && seg2[seg2len - 1];
+        }
+        if (!p2) {
+            pth.curve = pathClone(p);
+        }
+        return p2 ? [p, p2] : p;
+    }
+    function mapPath(path, matrix) {
+        if (!matrix) {
+            return path;
+        }
+        var x, y, i, j, ii, jj, pathi;
+        path = path2curve(path);
+        for (i = 0, ii = path.length; i < ii; i++) {
+            pathi = path[i];
+            for (j = 1, jj = pathi.length; j < jj; j += 2) {
+                x = matrix.x(pathi[j], pathi[j + 1]);
+                y = matrix.y(pathi[j], pathi[j + 1]);
+                pathi[j] = x;
+                pathi[j + 1] = y;
+            }
+        }
+        return path;
+    }
+
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp, z) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+            var p = [
+                        {x: +crp[i - 2], y: +crp[i - 1]},
+                        {x: +crp[i],     y: +crp[i + 1]},
+                        {x: +crp[i + 2], y: +crp[i + 3]},
+                        {x: +crp[i + 4], y: +crp[i + 5]}
+                    ];
+            if (z) {
+                if (!i) {
+                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+                } else if (iLen - 4 == i) {
+                    p[3] = {x: +crp[0], y: +crp[1]};
+                } else if (iLen - 2 == i) {
+                    p[2] = {x: +crp[0], y: +crp[1]};
+                    p[3] = {x: +crp[2], y: +crp[3]};
+                }
+            } else {
+                if (iLen - 4 == i) {
+                    p[3] = p[2];
+                } else if (!i) {
+                    p[0] = {x: +crp[i], y: +crp[i + 1]};
+                }
+            }
+            d.push(["C",
+                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                  (p[1].y + 6*p[2].y - p[3].y) / 6,
+                  p[2].x,
+                  p[2].y
+            ]);
+        }
+
+        return d;
+    }
+
+    // export
+    Snap.path = paths;
+
+    /*\
+     * Snap.path.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the given path in pixels
+     **
+     - path (string) SVG path string
+     **
+     = (number) length
+    \*/
+    Snap.path.getTotalLength = getTotalLength;
+    /*\
+     * Snap.path.getPointAtLength
+     [ method ]
+     **
+     * Returns the coordinates of the point located at the given length along the given path
+     **
+     - path (string) SVG path string
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    Snap.path.getPointAtLength = getPointAtLength;
+    /*\
+     * Snap.path.getSubpath
+     [ method ]
+     **
+     * Returns the subpath of a given path between given start and end lengths
+     **
+     - path (string) SVG path string
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    Snap.path.getSubpath = function (path, from, to) {
+        if (this.getTotalLength(path) - to < 1e-6) {
+            return getSubpathsAtLength(path, from).end;
+        }
+        var a = getSubpathsAtLength(path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+    /*\
+     * Element.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the path in pixels (only works for `path` elements)
+     = (number) length
+    \*/
+    elproto.getTotalLength = function () {
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+    };
+    // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a <path> is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length?
+    /*\
+     * Element.getPointAtLength
+     [ method ]
+     **
+     * Returns coordinates of the point located at the given length on the given path (only works for `path` elements)
+     **
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    elproto.getPointAtLength = function (length) {
+        return getPointAtLength(this.attr("d"), length);
+    };
+    // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear.
+    /*\
+     * Element.getSubpath
+     [ method ]
+     **
+     * Returns subpath of a given element from given start and end lengths (only works for `path` elements)
+     **
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    elproto.getSubpath = function (from, to) {
+        return Snap.path.getSubpath(this.attr("d"), from, to);
+    };
+    Snap._.box = box;
+    /*\
+     * Snap.path.findDotsAtSegment
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds dot coordinates on the given cubic beziér curve at the given t
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     - t (number) position on the curve (0..1)
+     = (object) point information in format:
+     o {
+     o     x: (number) x coordinate of the point,
+     o     y: (number) y coordinate of the point,
+     o     m: {
+     o         x: (number) x coordinate of the left anchor,
+     o         y: (number) y coordinate of the left anchor
+     o     },
+     o     n: {
+     o         x: (number) x coordinate of the right anchor,
+     o         y: (number) y coordinate of the right anchor
+     o     },
+     o     start: {
+     o         x: (number) x coordinate of the start of the curve,
+     o         y: (number) y coordinate of the start of the curve
+     o     },
+     o     end: {
+     o         x: (number) x coordinate of the end of the curve,
+     o         y: (number) y coordinate of the end of the curve
+     o     },
+     o     alpha: (number) angle of the curve derivative at the point
+     o }
+    \*/
+    Snap.path.findDotsAtSegment = findDotsAtSegment;
+    /*\
+     * Snap.path.bezierBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given cubic beziér curve
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     * or
+     - bez (array) array of six points for beziér curve
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.bezierBBox = bezierBBox;
+    /*\
+     * Snap.path.isPointInsideBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside bounding box
+     - bbox (string) bounding box
+     - x (string) x coordinate of the point
+     - y (string) y coordinate of the point
+     = (boolean) `true` if point is inside
+    \*/
+    Snap.path.isPointInsideBBox = isPointInsideBBox;
+    Snap.closest = function (x, y, X, Y) {
+        var r = 100,
+            b = box(x - r / 2, y - r / 2, r, r),
+            inside = [],
+            getter = X[0].hasOwnProperty("x") ? function (i) {
+                return {
+                    x: X[i].x,
+                    y: X[i].y
+                };
+            } : function (i) {
+                return {
+                    x: X[i],
+                    y: Y[i]
+                };
+            },
+            found = 0;
+        while (r <= 1e6 && !found) {
+            for (var i = 0, ii = X.length; i < ii; i++) {
+                var xy = getter(i);
+                if (isPointInsideBBox(b, xy.x, xy.y)) {
+                    found++;
+                    inside.push(xy);
+                    break;
+                }
+            }
+            if (!found) {
+                r *= 2;
+                b = box(x - r / 2, y - r / 2, r, r)
+            }
+        }
+        if (r == 1e6) {
+            return;
+        }
+        var len = Infinity,
+            res;
+        for (i = 0, ii = inside.length; i < ii; i++) {
+            var l = Snap.len(x, y, inside[i].x, inside[i].y);
+            if (len > l) {
+                len = l;
+                inside[i].len = l;
+                res = inside[i];
+            }
+        }
+        return res;
+    };
+    /*\
+     * Snap.path.isBBoxIntersect
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if two bounding boxes intersect
+     - bbox1 (string) first bounding box
+     - bbox2 (string) second bounding box
+     = (boolean) `true` if bounding boxes intersect
+    \*/
+    Snap.path.isBBoxIntersect = isBBoxIntersect;
+    /*\
+     * Snap.path.intersection
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds intersections of two paths
+     - path1 (string) path string
+     - path2 (string) path string
+     = (array) dots of intersection
+     o [
+     o     {
+     o         x: (number) x coordinate of the point,
+     o         y: (number) y coordinate of the point,
+     o         t1: (number) t value for segment of path1,
+     o         t2: (number) t value for segment of path2,
+     o         segment1: (number) order number for segment of path1,
+     o         segment2: (number) order number for segment of path2,
+     o         bez1: (array) eight coordinates representing beziér curve for the segment of path1,
+     o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
+     o     }
+     o ]
+    \*/
+    Snap.path.intersection = pathIntersection;
+    Snap.path.intersectionNumber = pathIntersectionNumber;
+    /*\
+     * Snap.path.isPointInside
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside a given closed path.
+     *
+     * Note: fill mode doesn’t affect the result of this method.
+     - path (string) path string
+     - x (number) x of the point
+     - y (number) y of the point
+     = (boolean) `true` if point is inside the path
+    \*/
+    Snap.path.isPointInside = isPointInsidePath;
+    /*\
+     * Snap.path.getBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given path
+     - path (string) path string
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.getBBox = pathBBox;
+    Snap.path.get = getPath;
+    /*\
+     * Snap.path.toRelative
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into relative values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toRelative = pathToRelative;
+    /*\
+     * Snap.path.toAbsolute
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into absolute values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toAbsolute = pathToAbsolute;
+    /*\
+     * Snap.path.toCubic
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path to a new path where all segments are cubic beziér curves
+     - pathString (string|array) path string or array of segments
+     = (array) array of segments
+    \*/
+    Snap.path.toCubic = path2curve;
+    /*\
+     * Snap.path.map
+     [ method ]
+     **
+     * Transform the path string with the given matrix
+     - path (string) path string
+     - matrix (object) see @Matrix
+     = (string) transformed path string
+    \*/
+    Snap.path.map = mapPath;
+    Snap.path.toString = toString;
+    Snap.path.clone = pathClone;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var mmax = Math.max,
+        mmin = Math.min;
+
+    // Set
+    var Set = function (items) {
+        this.items = [];
+	this.bindings = {};
+        this.length = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items.length; i < ii; i++) {
+                if (items[i]) {
+                    this[this.items.length] = this.items[this.items.length] = items[i];
+                    this.length++;
+                }
+            }
+        }
+    },
+    setproto = Set.prototype;
+    /*\
+     * Set.push
+     [ method ]
+     **
+     * Adds each argument to the current set
+     = (object) original element
+    \*/
+    setproto.push = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments.length; i < ii; i++) {
+            item = arguments[i];
+            if (item) {
+                len = this.items.length;
+                this[len] = this.items[len] = item;
+                this.length++;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.pop
+     [ method ]
+     **
+     * Removes last element and returns it
+     = (object) element
+    \*/
+    setproto.pop = function () {
+        this.length && delete this[this.length--];
+        return this.items.pop();
+    };
+    /*\
+     * Set.forEach
+     [ method ]
+     **
+     * Executes given function for each element in the set
+     *
+     * If the function returns `false`, the loop stops running.
+     **
+     - callback (function) function to run
+     - thisArg (object) context object for the callback
+     = (object) Set object
+    \*/
+    setproto.forEach = function (callback, thisArg) {
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
+                return this;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.animate
+     [ method ]
+     **
+     * Animates each element in set in sync.
+     *
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     * or
+     - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]`
+     > Usage
+     | // animate all elements in set to radius 10
+     | set.animate({r: 10}, 500, mina.easein);
+     | // or
+     | // animate first element to radius 10, but second to radius 20 and in different time
+     | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]);
+     = (Element) the current element
+    \*/
+    setproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Snap._.Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = easing.dur;
+            attrs = attrs.attr;
+        }
+        var args = arguments;
+        if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) {
+            var each = true;
+        }
+        var begin,
+            handler = function () {
+                if (begin) {
+                    this.b = begin;
+                } else {
+                    begin = this.b;
+                }
+            },
+            cb = 0,
+            set = this,
+            callbacker = callback && function () {
+                if (++cb == set.length) {
+                    callback.call(this);
+                }
+            };
+        return this.forEach(function (el, i) {
+            eve.once("snap.animcreated." + el.id, handler);
+            if (each) {
+                args[i] && el.animate.apply(el, args[i]);
+            } else {
+                el.animate(attrs, ms, easing, callbacker);
+            }
+        });
+    };
+    setproto.remove = function () {
+        while (this.length) {
+            this.pop().remove();
+        }
+        return this;
+    };
+    /*\
+     * Set.bind
+     [ method ]
+     **
+     * Specifies how to handle a specific attribute when applied
+     * to a set.
+     *
+     **
+     - attr (string) attribute name
+     - callback (function) function to run
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     - eattr (string) attribute on the element to bind the attribute to
+     = (object) Set object
+    \*/
+    setproto.bind = function (attr, a, b) {
+        var data = {};
+        if (typeof a == "function") {
+            this.bindings[attr] = a;
+        } else {
+            var aname = b || attr;
+            this.bindings[attr] = function (v) {
+                data[aname] = v;
+                a.attr(data);
+            };
+        }
+        return this;
+    };
+    setproto.attr = function (value) {
+        var unbound = {};
+        for (var k in value) {
+            if (this.bindings[k]) {
+                this.bindings[k](value[k]);
+            } else {
+                unbound[k] = value[k];
+            }
+        }
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            this.items[i].attr(unbound);
+        }
+        return this;
+    };
+    /*\
+     * Set.clear
+     [ method ]
+     **
+     * Removes all elements from the set
+    \*/
+    setproto.clear = function () {
+        while (this.length) {
+            this.pop();
+        }
+    };
+    /*\
+     * Set.splice
+     [ method ]
+     **
+     * Removes range of elements from the set
+     **
+     - index (number) position of the deletion
+     - count (number) number of element to remove
+     - insertion… (object) #optional elements to insert
+     = (object) set elements that were deleted
+    \*/
+    setproto.splice = function (index, count, insertion) {
+        index = index < 0 ? mmax(this.length + index, 0) : index;
+        count = mmax(0, mmin(this.length - index, count));
+        var tail = [],
+            todel = [],
+            args = [],
+            i;
+        for (i = 2; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        }
+        for (i = 0; i < count; i++) {
+            todel.push(this[index + i]);
+        }
+        for (; i < this.length - index; i++) {
+            tail.push(this[index + i]);
+        }
+        var arglen = args.length;
+        for (i = 0; i < arglen + tail.length; i++) {
+            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+        }
+        i = this.items.length = this.length -= count - arglen;
+        while (this[i]) {
+            delete this[i++];
+        }
+        return new Set(todel);
+    };
+    /*\
+     * Set.exclude
+     [ method ]
+     **
+     * Removes given element from the set
+     **
+     - element (object) element to remove
+     = (boolean) `true` if object was found and removed from the set
+    \*/
+    setproto.exclude = function (el) {
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
+            return true;
+        }
+        return false;
+    };
+    setproto.insertAfter = function (el) {
+        var i = this.items.length;
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    setproto.getBBox = function () {
+        var x = [],
+            y = [],
+            x2 = [],
+            y2 = [];
+        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+            var box = this.items[i].getBBox();
+            x.push(box.x);
+            y.push(box.y);
+            x2.push(box.x + box.width);
+            y2.push(box.y + box.height);
+        }
+        x = mmin.apply(0, x);
+        y = mmin.apply(0, y);
+        x2 = mmax.apply(0, x2);
+        y2 = mmax.apply(0, y2);
+        return {
+            x: x,
+            y: y,
+            x2: x2,
+            y2: y2,
+            width: x2 - x,
+            height: y2 - y,
+            cx: x + (x2 - x) / 2,
+            cy: y + (y2 - y) / 2
+        };
+    };
+    setproto.clone = function (s) {
+        s = new Set;
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            s.push(this.items[i].clone());
+        }
+        return s;
+    };
+    setproto.toString = function () {
+        return "Snap\u2018s set";
+    };
+    setproto.type = "set";
+    // export
+    Snap.Set = Set;
+    Snap.set = function () {
+        var set = new Set;
+        if (arguments.length) {
+            set.push.apply(set, Array.prototype.slice.call(arguments, 0));
+        }
+        return set;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var names = {},
+        reUnit = /[a-z]+$/i,
+        Str = String;
+    names.stroke = names.fill = "colour";
+    function getEmpty(item) {
+        var l = item[0];
+        switch (l.toLowerCase()) {
+            case "t": return [l, 0, 0];
+            case "m": return [l, 1, 0, 0, 1, 0, 0];
+            case "r": if (item.length == 4) {
+                return [l, 0, item[2], item[3]];
+            } else {
+                return [l, 0];
+            }
+            case "s": if (item.length == 5) {
+                return [l, 1, 1, item[3], item[4]];
+            } else if (item.length == 3) {
+                return [l, 1, 1];
+            } else {
+                return [l, 1];
+            }
+        }
+    }
+    function equaliseTransform(t1, t2, getBBox) {
+        t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+        t1 = Snap.parseTransformString(t1) || [];
+        t2 = Snap.parseTransformString(t2) || [];
+        var maxlength = Math.max(t1.length, t2.length),
+            from = [],
+            to = [],
+            i = 0, j, jj,
+            tt1, tt2;
+        for (; i < maxlength; i++) {
+            tt1 = t1[i] || getEmpty(t2[i]);
+            tt2 = t2[i] || getEmpty(tt1);
+            if ((tt1[0] != tt2[0]) ||
+                (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                ) {
+                    t1 = Snap._.transform2matrix(t1, getBBox());
+                    t2 = Snap._.transform2matrix(t2, getBBox());
+                    from = [["m", t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]];
+                    to = [["m", t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]];
+                    break;
+            }
+            from[i] = [];
+            to[i] = [];
+            for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) {
+                j in tt1 && (from[i][j] = tt1[j]);
+                j in tt2 && (to[i][j] = tt2[j]);
+            }
+        }
+        return {
+            from: path2array(from),
+            to: path2array(to),
+            f: getPath(from)
+        };
+    }
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    function getViewBox(val) {
+        return val.join(" ");
+    }
+    function getColour(clr) {
+        return Snap.rgb(clr[0], clr[1], clr[2]);
+    }
+    function getPath(path) {
+        var k = 0, i, ii, j, jj, out, a, b = [];
+        for (i = 0, ii = path.length; i < ii; i++) {
+            out = "[";
+            a = ['"' + path[i][0] + '"'];
+            for (j = 1, jj = path[i].length; j < jj; j++) {
+                a[j] = "val[" + (k++) + "]";
+            }
+            out += a + "]";
+            b[i] = out;
+        }
+        return Function("val", "return Snap.path.toString.call([" + b + "])");
+    }
+    function path2array(path) {
+        var out = [];
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            for (var j = 1, jj = path[i].length; j < jj; j++) {
+                out.push(path[i][j]);
+            }
+        }
+        return out;
+    }
+    function isNumeric(obj) {
+        return isFinite(parseFloat(obj));
+    }
+    function arrayEqual(arr1, arr2) {
+        if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) {
+            return false;
+        }
+        return arr1.toString() == arr2.toString();
+    }
+    Element.prototype.equal = function (name, b) {
+        return eve("snap.util.equal", this, name, b).firstDefined();
+    };
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this;
+        if (isNumeric(a) && isNumeric(b)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getNumber
+            };
+        }
+        if (names[name] == "colour") {
+            A = Snap.color(a);
+            B = Snap.color(b);
+            return {
+                from: [A.r, A.g, A.b, A.opacity],
+                to: [B.r, B.g, B.b, B.opacity],
+                f: getColour
+            };
+        }
+        if (name == "viewBox") {
+            A = this.attr(name).vb.split(" ").map(Number);
+            B = b.split(" ").map(Number);
+            return {
+                from: A,
+                to: B,
+                f: getViewBox
+            };
+        }
+        if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
+            if (b instanceof Snap.Matrix) {
+                b = b.toTransformString();
+            }
+            if (!Snap._.rgTransform.test(b)) {
+                b = Snap._.svgTransform2string(b);
+            }
+            return equaliseTransform(a, b, function () {
+                return el.getBBox(1);
+            });
+        }
+        if (name == "d" || name == "path") {
+            A = Snap.path.toCubic(a, b);
+            return {
+                from: path2array(A[0]),
+                to: path2array(A[1]),
+                f: getPath(A[0])
+            };
+        }
+        if (name == "points") {
+            A = Str(a).split(Snap._.separator);
+            B = Str(b).split(Snap._.separator);
+            return {
+                from: A,
+                to: B,
+                f: function (val) { return val; }
+            };
+        }
+        var aUnit = a.match(reUnit),
+            bUnit = Str(b).match(reUnit);
+        if (aUnit && arrayEqual(aUnit, bUnit)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getUnit(aUnit)
+            };
+        } else {
+            return {
+                from: this.asPX(name),
+                to: this.asPX(name, b),
+                f: getNumber
+            };
+        }
+    });
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+    has = "hasOwnProperty",
+    supportsTouch = "createTouch" in glob.doc,
+    events = [
+        "click", "dblclick", "mousedown", "mousemove", "mouseout",
+        "mouseover", "mouseup", "touchstart", "touchmove", "touchend",
+        "touchcancel"
+    ],
+    touchMap = {
+        mousedown: "touchstart",
+        mousemove: "touchmove",
+        mouseup: "touchend"
+    },
+    getScroll = function (xy, el) {
+        var name = xy == "y" ? "scrollTop" : "scrollLeft",
+            doc = el && el.node ? el.node.ownerDocument : glob.doc;
+        return doc[name in doc.documentElement ? "documentElement" : "body"][name];
+    },
+    preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = function (obj, type, fn, element) {
+        var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+            f = function (e) {
+                var scrollY = getScroll("y", element),
+                    scrollX = getScroll("x", element);
+                if (supportsTouch && touchMap[has](type)) {
+                    for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                        if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) {
+                            var olde = e;
+                            e = e.targetTouches[i];
+                            e.originalEvent = olde;
+                            e.preventDefault = preventTouch;
+                            e.stopPropagation = stopTouch;
+                            break;
+                        }
+                    }
+                }
+                var x = e.clientX + scrollX,
+                    y = e.clientY + scrollY;
+                return fn.call(element, e, x, y);
+            };
+
+        if (type !== realName) {
+            obj.addEventListener(type, f, false);
+        }
+
+        obj.addEventListener(realName, f, false);
+
+        return function () {
+            if (type !== realName) {
+                obj.removeEventListener(type, f, false);
+            }
+
+            obj.removeEventListener(realName, f, false);
+            return true;
+        };
+    },
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = getScroll("y"),
+            scrollX = getScroll("x"),
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches && e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            var node = dragi.el.node,
+                o,
+                next = node.nextSibling,
+                parent = node.parentNode,
+                display = node.style.display;
+            // glob.win.opera && parent.removeChild(node);
+            // node.style.display = "none";
+            // o = dragi.el.paper.getElementByPoint(x, y);
+            // node.style.display = display;
+            // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+            // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o);
+            x += scrollX;
+            y += scrollY;
+            eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        Snap.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+            eve.off("snap.drag.*." + dragi.el.id);
+        }
+        drag = [];
+    };
+    /*\
+     * Element.click
+     [ method ]
+     **
+     * Adds a click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unclick
+     [ method ]
+     **
+     * Removes a click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.dblclick
+     [ method ]
+     **
+     * Adds a double click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.undblclick
+     [ method ]
+     **
+     * Removes a double click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousedown
+     [ method ]
+     **
+     * Adds a mousedown event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousedown
+     [ method ]
+     **
+     * Removes a mousedown event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousemove
+     [ method ]
+     **
+     * Adds a mousemove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousemove
+     [ method ]
+     **
+     * Removes a mousemove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseout
+     [ method ]
+     **
+     * Adds a mouseout event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseout
+     [ method ]
+     **
+     * Removes a mouseout event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseover
+     [ method ]
+     **
+     * Adds a mouseover event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseover
+     [ method ]
+     **
+     * Removes a mouseover event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseup
+     [ method ]
+     **
+     * Adds a mouseup event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseup
+     [ method ]
+     **
+     * Removes a mouseup event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchstart
+     [ method ]
+     **
+     * Adds a touchstart event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchstart
+     [ method ]
+     **
+     * Removes a touchstart event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchmove
+     [ method ]
+     **
+     * Adds a touchmove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchmove
+     [ method ]
+     **
+     * Removes a touchmove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchend
+     [ method ]
+     **
+     * Adds a touchend event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchend
+     [ method ]
+     **
+     * Removes a touchend event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchcancel
+     [ method ]
+     **
+     * Adds a touchcancel event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchcancel
+     [ method ]
+     **
+     * Removes a touchcancel event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    for (var i = events.length; i--;) {
+        (function (eventName) {
+            Snap[eventName] = elproto[eventName] = function (fn, scope) {
+                if (Snap.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({
+                        name: eventName,
+                        f: fn,
+                        unbind: addEvent(this.node || document, eventName, fn, scope || this)
+                    });
+                } else {
+                    for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) {
+                        try {
+                            this.events[i].f.call(this);
+                        } catch (e) {}
+                    }
+                }
+                return this;
+            };
+            Snap["un" + eventName] =
+            elproto["un" + eventName] = function (fn) {
+                var events = this.events || [],
+                    l = events.length;
+                while (l--) if (events[l].name == eventName &&
+                               (events[l].f == fn || !fn)) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    /*\
+     * Element.hover
+     [ method ]
+     **
+     * Adds hover event handlers to the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     - icontext (object) #optional context for hover in handler
+     - ocontext (object) #optional context for hover out handler
+     = (object) @Element
+    \*/
+    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+    };
+    /*\
+     * Element.unhover
+     [ method ]
+     **
+     * Removes hover event handlers from the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     = (object) @Element
+    \*/
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    var draggable = [];
+    // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture.
+    // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from?
+    // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason.
+    // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start.<id> on start, drag.end.<id> on end and drag.move.<id> on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID?
+    /*\
+     * Element.drag
+     [ method ]
+     **
+     * Adds event handlers for an element's drag gesture
+     **
+     - onmove (function) handler for moving
+     - onstart (function) handler for drag start
+     - onend (function) handler for drag end
+     - mcontext (object) #optional context for moving handler
+     - scontext (object) #optional context for drag start handler
+     - econtext (object) #optional context for drag end handler
+     * Additionaly following `drag` events are triggered: `drag.start.<id>` on start,
+     * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element is dragged over another element
+     * `drag.over.<id>` fires as well.
+     *
+     * Start event and start handler are called in specified context or in context of the element with following parameters:
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * Move event and move handler are called in specified context or in context of the element with following parameters:
+     o dx (number) shift by x from the start point
+     o dy (number) shift by y from the start point
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * End event and end handler are called in specified context or in context of the element with following parameters:
+     o event (object) DOM event object
+     = (object) @Element
+    \*/
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        var el = this;
+        if (!arguments.length) {
+            var origTransform;
+            return el.drag(function (dx, dy) {
+                this.attr({
+                    transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
+                });
+            }, function () {
+                origTransform = this.transform().local;
+            });
+        }
+        function start(e, x, y) {
+            (e.originalEvent || e).preventDefault();
+            el._drag.x = x;
+            el._drag.y = y;
+            el._drag.id = e.identifier;
+            !drag.length && Snap.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+            onstart && eve.on("snap.drag.start." + el.id, onstart);
+            onmove && eve.on("snap.drag.move." + el.id, onmove);
+            onend && eve.on("snap.drag.end." + el.id, onend);
+            eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e);
+        }
+        function init(e, x, y) {
+            eve("snap.draginit." + el.id, el, e, x, y);
+        }
+        eve.on("snap.draginit." + el.id, start);
+        el._drag = {};
+        draggable.push({el: el, start: start, init: init});
+        el.mousedown(init);
+        return el;
+    };
+    /*
+     * Element.onDragOver
+     [ method ]
+     **
+     * Shortcut to assign event handler for `drag.over.<id>` event, where `id` is the element's `id` (see @Element.id)
+     - f (function) handler for event, first argument would be the element you are dragging over
+    \*/
+    // elproto.onDragOver = function (f) {
+    //     f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id);
+    // };
+    /*\
+     * Element.undrag
+     [ method ]
+     **
+     * Removes all drag event handlers from the given element
+    \*/
+    elproto.undrag = function () {
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].init);
+            draggable.splice(i, 1);
+            eve.unbind("snap.drag.*." + this.id);
+            eve.unbind("snap.draginit." + this.id);
+        }
+        !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp);
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        pproto = Paper.prototype,
+        rgurl = /^\s*url\((.+)\)/,
+        Str = String,
+        $ = Snap._.$;
+    Snap.filter = {};
+    /*\
+     * Paper.filter
+     [ method ]
+     **
+     * Creates a `<filter>` element
+     **
+     - filstr (string) SVG fragment of filter provided as a string
+     = (object) @Element
+     * Note: It is recommended to use filters embedded into the page inside an empty SVG element.
+     > Usage
+     | var f = paper.filter('<feGaussianBlur stdDeviation="2"/>'),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    pproto.filter = function (filstr) {
+        var paper = this;
+        if (paper.type != "svg") {
+            paper = paper.paper;
+        }
+        var f = Snap.parse(Str(filstr)),
+            id = Snap._.id(),
+            width = paper.node.offsetWidth,
+            height = paper.node.offsetHeight,
+            filter = $("filter");
+        $(filter, {
+            id: id,
+            filterUnits: "userSpaceOnUse"
+        });
+        filter.appendChild(f.node);
+        paper.defs.appendChild(filter);
+        return new Element(filter);
+    };
+
+    eve.on("snap.util.getattr.filter", function () {
+        eve.stop();
+        var p = $(this.node, "filter");
+        if (p) {
+            var match = Str(p).match(rgurl);
+            return match && Snap.select(match[1]);
+        }
+    });
+    eve.on("snap.util.attr.filter", function (value) {
+        if (value instanceof Element && value.type == "filter") {
+            eve.stop();
+            var id = value.node.id;
+            if (!id) {
+                $(value.node, {id: value.id});
+                id = value.id;
+            }
+            $(this.node, {
+                filter: Snap.url(id)
+            });
+        }
+        if (!value || value == "none") {
+            eve.stop();
+            this.node.removeAttribute("filter");
+        }
+    });
+    /*\
+     * Snap.filter.blur
+     [ method ]
+     **
+     * Returns an SVG markup string for the blur filter
+     **
+     - x (number) amount of horizontal blur, in pixels
+     - y (number) #optional amount of vertical blur, in pixels
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.blur(5, 10)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.blur = function (x, y) {
+        if (x == null) {
+            x = 2;
+        }
+        var def = y == null ? x : [x, y];
+        return Snap.format('\<feGaussianBlur stdDeviation="{def}"/>', {
+            def: def
+        });
+    };
+    Snap.filter.blur.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.shadow
+     [ method ]
+     **
+     * Returns an SVG markup string for the shadow filter
+     **
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - blur (number) #optional amount of blur
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * which makes blur default to `4`. Or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - opacity (number) #optional `0..1` opacity of the shadow
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.shadow(0, 2, 3)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.shadow = function (dx, dy, blur, color, opacity) {
+        if (typeof blur == "string") {
+            color = blur;
+            opacity = color;
+            blur = 4;
+        }
+        if (typeof color != "string") {
+            opacity = color;
+            color = "#000";
+        }
+        color = color || "#000";
+        if (blur == null) {
+            blur = 4;
+        }
+        if (opacity == null) {
+            opacity = 1;
+        }
+        if (dx == null) {
+            dx = 0;
+            dy = 2;
+        }
+        if (dy == null) {
+            dy = dx;
+        }
+        color = Snap.color(color);
+        return Snap.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', {
+            color: color,
+            dx: dx,
+            dy: dy,
+            blur: blur,
+            opacity: opacity
+        });
+    };
+    Snap.filter.shadow.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.grayscale
+     [ method ]
+     **
+     * Returns an SVG markup string for the grayscale filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.grayscale = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>', {
+            a: 0.2126 + 0.7874 * (1 - amount),
+            b: 0.7152 - 0.7152 * (1 - amount),
+            c: 0.0722 - 0.0722 * (1 - amount),
+            d: 0.2126 - 0.2126 * (1 - amount),
+            e: 0.7152 + 0.2848 * (1 - amount),
+            f: 0.0722 - 0.0722 * (1 - amount),
+            g: 0.2126 - 0.2126 * (1 - amount),
+            h: 0.0722 + 0.9278 * (1 - amount)
+        });
+    };
+    Snap.filter.grayscale.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.sepia
+     [ method ]
+     **
+     * Returns an SVG markup string for the sepia filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.sepia = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>', {
+            a: 0.393 + 0.607 * (1 - amount),
+            b: 0.769 - 0.769 * (1 - amount),
+            c: 0.189 - 0.189 * (1 - amount),
+            d: 0.349 - 0.349 * (1 - amount),
+            e: 0.686 + 0.314 * (1 - amount),
+            f: 0.168 - 0.168 * (1 - amount),
+            g: 0.272 - 0.272 * (1 - amount),
+            h: 0.534 - 0.534 * (1 - amount),
+            i: 0.131 + 0.869 * (1 - amount)
+        });
+    };
+    Snap.filter.sepia.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.saturate
+     [ method ]
+     **
+     * Returns an SVG markup string for the saturate filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.saturate = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="saturate" values="{amount}"/>', {
+            amount: 1 - amount
+        });
+    };
+    Snap.filter.saturate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.hueRotate
+     [ method ]
+     **
+     * Returns an SVG markup string for the hue-rotate filter
+     **
+     - angle (number) angle of rotation
+     = (string) filter representation
+    \*/
+    Snap.filter.hueRotate = function (angle) {
+        angle = angle || 0;
+        return Snap.format('<feColorMatrix type="hueRotate" values="{angle}"/>', {
+            angle: angle
+        });
+    };
+    Snap.filter.hueRotate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.invert
+     [ method ]
+     **
+     * Returns an SVG markup string for the invert filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.invert = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+//        <feColorMatrix type="matrix" values="-1 0 0 0 1  0 -1 0 0 1  0 0 -1 0 1  0 0 0 1 0" color-interpolation-filters="sRGB"/>
+        return Snap.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: 1 - amount
+        });
+    };
+    Snap.filter.invert.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.brightness
+     [ method ]
+     **
+     * Returns an SVG markup string for the brightness filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.brightness = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>', {
+            amount: amount
+        });
+    };
+    Snap.filter.brightness.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.contrast
+     [ method ]
+     **
+     * Returns an SVG markup string for the contrast filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.contrast = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: .5 - amount / 2
+        });
+    };
+    Snap.filter.contrast.toString = function () {
+        return this();
+    };
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var box = Snap._.box,
+        is = Snap.is,
+        firstLetter = /^[^a-z]*([tbmlrc])/i,
+        toString = function () {
+            return "T" + this.dx + "," + this.dy;
+        };
+    /*\
+     * Element.getAlign
+     [ method ]
+     **
+     * Returns shift needed to align the element relatively to given element.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string
+     > Usage
+     | el.transform(el.getAlign(el2, "top"));
+     * or
+     | var dy = el.getAlign(el2, "top").dy;
+    \*/
+    Element.prototype.getAlign = function (el, way) {
+        if (way == null && is(el, "string")) {
+            way = el;
+            el = null;
+        }
+        el = el || this.paper;
+        var bx = el.getBBox ? el.getBBox() : box(el),
+            bb = this.getBBox(),
+            out = {};
+        way = way && way.match(firstLetter);
+        way = way ? way[1].toLowerCase() : "c";
+        switch (way) {
+            case "t":
+                out.dx = 0;
+                out.dy = bx.y - bb.y;
+            break;
+            case "b":
+                out.dx = 0;
+                out.dy = bx.y2 - bb.y2;
+            break;
+            case "m":
+                out.dx = 0;
+                out.dy = bx.cy - bb.cy;
+            break;
+            case "l":
+                out.dx = bx.x - bb.x;
+                out.dy = 0;
+            break;
+            case "r":
+                out.dx = bx.x2 - bb.x2;
+                out.dy = 0;
+            break;
+            default:
+                out.dx = bx.cx - bb.cx;
+                out.dy = 0;
+            break;
+        }
+        out.toString = toString;
+        return out;
+    };
+    /*\
+     * Element.align
+     [ method ]
+     **
+     * Aligns the element relatively to given one via transformation.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object) this element
+     > Usage
+     | el.align(el2, "top");
+     * or
+     | el.align("middle");
+    \*/
+    Element.prototype.align = function (el, way) {
+        return this.transform("..." + this.getAlign(el, way));
+    };
+});
+
+return Snap;
+}));
diff --git a/web/pgadmin/misc/templates/explain/js/explain.js b/web/pgadmin/misc/templates/explain/js/explain.js
new file mode 100644
index 0000000..bedc51e
--- /dev/null
+++ b/web/pgadmin/misc/templates/explain/js/explain.js
@@ -0,0 +1,688 @@
+define (
+  'pgadmin.misc.explain',
+  ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'backbone', 'snap.svg'],
+  function($, _, S, pgAdmin, Backbone, Snap) {
+
+pgAdmin = pgAdmin || window.pgAdmin || {};
+var pgExplain = pgAdmin.Explain;
+
+// Snap.svg plug-in to write multitext as image name
+Snap.plugin(function (Snap, Element, Paper, glob) {
+  Paper.prototype.multitext = function (x, y, txt, max_width, attributes) {
+    var svg = Snap(),
+        abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+        isWordBroken = false,
+        temp = svg.text(0, 0, abc);
+
+    temp.attr(attributes);
+
+    /*
+     * Find letter width in pixels and
+     * index from where the text should be broken
+     */
+    var letter_width = temp.getBBox().width / abc.length,
+        word_break_index = Math.round((max_width / letter_width)) - 1;
+
+    svg.remove();
+
+    var words = txt.split(" "),
+        width_so_far = 0,
+        lines=[], curr_line = '',
+        /*
+         * Function to divide string into multiple lines
+         * and store them in an array if it size crosses
+         * the max-width boundary.
+         */
+        splitTextInMultiLine = function(leading, so_far, line) {
+          var l = line.length,
+              res = [];
+
+          if (l == 0)
+            return res;
+
+          if (so_far && (so_far + (l * letter_width) > max_width)) {
+            res.push(leading);
+            res = res.concat(splitTextInMultiLine('', 0, line));
+          } else if (so_far) {
+            res.push(leading + ' ' + line);
+          } else {
+            if (leading)
+                res.push(leading);
+            if (line.length > word_break_index + 1)
+                res.push(line.slice(0, word_break_index) + '-');
+            else
+                res.push(line);
+            res = res.concat(splitTextInMultiLine('', 0, line.slice(word_break_index)));
+          }
+
+          return res;
+        };
+
+    for (var i = 0; i < words.length; i++) {
+      var tmpArr = splitTextInMultiLine(
+            curr_line, width_so_far, words[i]
+          );
+
+      if (curr_line) {
+        lines = lines.slice(0, lines.length - 2);
+      }
+      lines = lines.concat(tmpArr);
+      curr_line = lines[lines.length - 1];
+      width_so_far = (curr_line.length * letter_width);
+    }
+
+    // Create multiple tspan for each string in array
+    var t = this.text(x,y,lines).attr(attributes);
+    t.selectAll("tspan:nth-child(n+2)").attr({
+      dy: "1.2em",
+      x: x
+    });
+    return t;
+  };
+});
+
+if (pgAdmin.Explain)
+    return pgAdmin.Explain;
+
+var pgExplain = pgAdmin.Explain = {
+   // Prefix path where images are stored
+   prefix: '{{ url_for('misc.static', filename='explain/img') }}/'
+};
+
+/*
+ * A map which is used to fetch the image to be drawn and
+ * text which will appear below it
+ */
+var imageMapper = {
+    "Aggregate" : {
+        "image":"ex_aggregate.png", "image_text":"Aggregate"
+    },
+    'Append' : {
+        "image":"ex_append.png","image_text":"Append"
+    },
+    "Bitmap Index Scan" : function(data) {
+        return {
+            "image":"ex_bmp_index.png", "image_text":data['Index Name']
+        };
+    },
+    "Bitmap Heap Scan" : function(data) {
+  return {"image":"ex_bmp_heap.png","image_text":data['Relation Name']};
+},
+"BitmapAnd" : {"image":"ex_bmp_and.png","image_text":"Bitmap AND"},
+"BitmapOr" : {"image":"ex_bmp_or.png","image_text":"Bitmap OR"},
+"CTE Scan" : {"image":"ex_cte_scan.png","image_text":"CTE Scan"},
+"Function Scan" : {"image":"ex_result.png","image_text":"Function Scan"},
+"Foreign Scan" : {"image":"ex_foreign_scan.png","image_text":"Foreign Scan"},
+"Gather" : {"image":"ex_gather_motion.png","image_text":"Gather"},
+"Group" : {"image":"ex_group.png","image_text":"Group"},
+"GroupAggregate": {"image":"ex_aggregate.png","image_text":"Group Aggregate"},
+"Hash" : {"image":"ex_hash.png","image_text":"Hash"},
+"Hash Join": function(data) {
+  if (!data['Join Type']) return {"image":"ex_join.png","image_text":"Join"};
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_hash_anti_join.png","image_text":"Hash Anti Join"};
+    case 'Semi': return {"image":"ex_hash_semi_join.png","image_text":"Hash Semi Join"};
+    default: return {"image":"ex_hash.png","image_text":String("Hash " + data['Join Type'] + " Join" )};
+  }
+},
+"HashAggregate" : {"image":"ex_aggregate.png","image_text":"Hash Aggregate"},
+"Index Only Scan" : function(data) {
+  return {"image":"ex_index_only_scan.png","image_text":data['Index Name']};
+},
+"Index Scan" : function(data) {
+  return {"image":"ex_index_scan.png","image_text":data['Index Name']};
+},
+"Index Scan Backword" : {"image":"ex_index_scan.png","image_text":"Index Backward Scan"},
+"Limit" : {"image":"ex_limit.png","image_text":"Limit"},
+"LockRows" : {"image":"ex_lock_rows.png","image_text":"Lock Rows"},
+"Materialize" : {"image":"ex_materialize.png","image_text":"Materialize"},
+"Merge Append": {"image":"ex_merge_append.png","image_text":"Merge Append"},
+"Merge Join": function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_merge_anti_join.png","image_text":"Merge Anti Join"};
+    case 'Semi': return {"image":"ex_merge_semi_join.png","image_text":"Merge Semi Join"};
+    default: return {"image":"ex_merge.png","image_text":String("Merge " + data['Join Type'] + " Join" )};
+  }
+},
+"ModifyTable" : function(data) {
+  switch (data['Operaton']) {
+    case "insert": return { "image":"ex_insert.png",
+                            "image_text":"Insert"
+                           };
+    case "update": return {"image":"ex_update.png","image_text":"Update"};
+    case "Delete": return {"image":"ex_delete.png","image_text":"Delete"};
+  }
+},
+'Nested Loop' : function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_nested_loop_anti_join.png","image_text":"Nested Loop Anti Join"};
+    case 'Semi': return {"image":"ex_nested_loop_semi_join.png","image_text":"Nested Loop Semi Join"};
+    default: return {"image":"ex_nested.png","image_text":"Nested Loop " + data['Join Type'] + " Join"};
+  }
+},
+"Recursive Union" : {"image":"ex_recursive_union.png","image_text":"Recursive Union"},
+"Result" : {"image":"ex_result.png","image_text":"Result"},
+"Sample Scan" : {"image":"ex_scan.png","image_text":"Sample Scan"},
+"Scan" : {"image":"ex_scan.png","image_text":"Scan"},
+"Seek" : {"image":"ex_seek.png","image_text":"Seek"},
+"SetOp" : function(data) {
+  var strategy = data['Strategy'],
+      command = data['Command'];
+
+  if(strategy == "Hashed") {
+    if(command.startsWith("Intersect")) {
+      if(command == "Intersect All")
+        return {"image":"ex_hash_setop_intersect_all.png","image_text":"Hashed Intersect All"};
+      return {"image":"ex_hash_setop_intersect.png","image_text":"Hashed Intersect"};
+    }
+    else if (command.startsWith("Except")) {
+      if(command == "Except All")
+        return {"image":"ex_hash_setop_except_all.png","image_text":"Hashed Except All"};
+      return {"image":"ex_hash_setop_except.png","image_text":"Hash Except"};
+    }
+    return {"image":"ex_hash_setop_unknown.png","image_text":"Hashed SetOp Unknown"};
+  }
+  return {"image":"ex_setop.png","image_text":"SetOp"};
+},
+"Seq Scan": function(data) {
+  return {"image":"ex_scan.png","image_text":data['Relation Name']};
+},
+"Subquery Scan" : {"image":"ex_subplan.png","image_text":"SubQuery Scan"},
+"Sort" : {"image":"ex_sort.png","image_text":"Sort"},
+"Tid Scan" : {"image":"ex_tid_scan.png","image_text":"Tid Scan"},
+"Unique" : {"image":"ex_unique.png","image_text":"Unique"},
+"Values Scan" : {"image":"ex_values_scan.png","image_text":"Values Scan"},
+"WindowAgg" : {"image":"ex_window_aggregate.png","image_text":"Window Aggregate"},
+"WorkTable Scan" : {"image":"ex_worktable_scan.png","image_text":"WorkTable Scan"},
+"Undefined" : {"image":"ex_unknown.png","image_text":"Undefined"},
+}
+
+// Some predefined constants used to calculate image location and its border
+var pWIDTH = pHEIGHT = 100.
+    IMAGE_WIDTH = IMAGE_HEIGHT = 50;
+var offsetX = 200,
+    offsetY = 60;
+var ARROW_WIDTH = 10,
+    ARROW_HEIGHT = 10,
+    DEFAULT_ARROW_SIZE = 2;
+var TXT_ALLIGN = 5,
+    TXT_SIZE = "15px";
+var TOTAL_WIDTH = undefined,
+    TOTAL_HEIGHT = undefined;
+var xMargin = 25,
+    yMargin = 25;
+var MIN_ZOOM_FACTOR = 0.01,
+    MAX_ZOOM_FACTOR = 2,
+    INIT_ZOOM_FACTOR = 1;
+    ZOOM_RATIO = 0.05;
+
+
+// Backbone model for each plan property of input JSON object
+var PlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plans": [],
+        level: [],
+        "image": undefined,
+        "image_text": undefined,
+        xpos: undefined,
+        ypos: undefined,
+        width: pWIDTH,
+        height: pHEIGHT
+    },
+    parse: function(data) {
+        var idx = 1,
+            lvl = data.level = data.level || [idx],
+            plans = [],
+            node_type = data['Node Type'],
+            // Calculating relative xpos of current node from top node
+            xpos = data.xpos = data.xpos - pWIDTH,
+            // Calculating relative ypos of current node from top node
+            ypos = data.ypos,
+            maxChildWidth = 0;
+
+        data['width'] = pWIDTH;
+        data['height'] = pHEIGHT;
+
+        /*
+         * calculating xpos, ypos, width and height if current node is a subplan
+         */
+        if (data['Parent Relationship'] === "SubPlan") {
+            data['width'] += (xMargin * 2) + (xMargin / 2);
+            data['height'] += (yMargin * 2);
+            data['ypos'] += yMargin;
+            xpos -= xMargin;
+            ypos += yMargin;
+        }
+
+        if(node_type.startsWith("(slice"))
+            node_type = node_type.substring(0,7);
+
+        // Get the image information for current node
+        var mapperObj = (_.isFunction(imageMapper[node_type]) &&
+                imageMapper[node_type].apply(undefined, [data])) ||
+                imageMapper[node_type] || 'Undefined';
+
+        data["image"] = mapperObj["image"];
+        data["image_text"] = mapperObj["image_text"];
+
+        // Start calculating xpos, ypos, width and height for child plans if any
+        if ('Plans' in data) {
+
+            data['width'] += offsetX;
+
+            _.each(data['Plans'], function(p) {
+                var level = _.clone(lvl),
+                    plan = new PlanModel();
+
+                level.push(idx);
+                plan.set(plan.parse(_.extend(
+                    p, {
+                        "level": level,
+                        xpos: xpos - offsetX,
+                        ypos: ypos
+                    })));
+
+                if (maxChildWidth < plan.get('width')) {
+                    maxChildWidth = plan.get('width');
+                }
+
+                var childHeight = plan.get('height');
+
+                if (idx !== 1) {
+                    data['height'] = data['height'] + childHeight + offsetY;
+                } else if (childHeight > data['height']) {
+                    data['height'] = childHeight;
+                }
+                ypos += childHeight + offsetY;
+
+                plans.push(plan);
+                idx++;
+            });
+        }
+
+        // Final Width and Height of current node
+        data['width'] += maxChildWidth;
+        data['Plans'] = plans;
+
+        return data;
+    },
+
+    /*
+     * Required to parse and include non-default params of
+     * plan into backbone model
+     */
+    toJSON: function(non_recursive) {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (non_recursive) {
+            delete res['Plans'];
+      } else {
+            var plans = [];
+            _.each(res['Plans'], function(p) {
+              plans.push(p.toJSON());
+            });
+            res['Plans'] = plans;
+      }
+      return res;
+    },
+
+    // Draw an arrow to parent node
+    drawPolyLine: function(g, startX, startY, endX, endY, opts, arrowOpts) {
+      // Calculate end point of first starting straight line (startx1, starty1)
+      // Calculate start point of 2nd straight line (endx1, endy1)
+      var midX1 = startX + ((endX - startX) / 3),
+          midX2 = startX + (2 * ((endX - startX) / 3));
+
+      //create arrow head
+      var arrow = g.polygon(
+                    [0, ARROW_HEIGHT,
+                    (ARROW_WIDTH / 2),ARROW_HEIGHT,
+                    (ARROW_HEIGHT / 4), 0,
+                    0, ARROW_WIDTH]
+                    ).transform("r90");
+      var marker = arrow.marker(
+                         0, 0, ARROW_WIDTH, ARROW_HEIGHT, 0, (ARROW_WIDTH / 2)
+                         ).attr(arrowOpts);
+
+      // First straight line
+      g.line(
+        startX, startY, midX1, startY
+        ).attr(opts);
+
+      // Diagonal line
+      g.line(
+        midX1-1, startY, midX2, endY
+        ).attr(opts);
+
+      // Last straight line
+      var line = g.line(
+                   midX2, endY, endX, endY
+                   ).attr(opts);
+      line.attr({markerEnd: marker})
+    },
+
+    // Draw image, its name and its tooltip
+    draw: function(s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer) {
+        var g = s.g();
+        var currentXpos = xpos + this.get('xpos') ,
+            currentYpos = ypos + this.get('ypos'),
+            isSubPlan = (this.get('Parent Relationship') === "SubPlan");
+
+        // Draw the subplan rectangle
+        if (isSubPlan) {
+          g.rect(
+            currentXpos - this.get('width') + pWIDTH + xMargin,
+            currentYpos - yMargin,
+            this.get('width') - xMargin,
+            this.get('height'), 5
+          ).attr({
+              stroke: '#444444',
+              'strokeWidth': 1.2,
+              fill: 'gray',
+              fillOpacity: 0.2
+          });
+
+          //provide subplan name
+          var text = g.text(
+            currentXpos  + pWIDTH - ( this.get('width') / 2) - xMargin,
+            currentYpos + pHEIGHT  - (this.get('height') / 2) - yMargin,
+            this.get('Subplan Name')
+          ).attr({
+            fontSize: TXT_SIZE, "text-anchor":"start",
+            fill: 'red'
+          });
+        }
+
+        // Draw the actual image for current node
+        var image = g.image(
+            pgExplain.prefix + this.get('image'),
+            currentXpos + (pWIDTH - IMAGE_WIDTH) / 2,
+            currentYpos + (pHEIGHT - IMAGE_HEIGHT) / 2,
+            IMAGE_WIDTH,
+            IMAGE_HEIGHT
+        );
+
+        // Draw tooltip
+        var image_data = this.toJSON();
+        image.mouseover(function(evt){
+
+          // Empty the tooltip content if it has any and add new data
+          toolTipContainer.empty();
+          var tooltip = $('<table></table>',{
+                           class: "pgadmin-tooltip-table"
+                        }).appendTo(toolTipContainer);
+          _.each(image_data, function(value,key) {
+            if(key !== 'image' && key !== 'Plans' &&
+               key !== 'level' && key !== 'image' &&
+               key !== 'image_text' && key !== 'xpos' &&
+               key !== 'ypos' && key !== 'width' &&
+               key !== 'height') {
+              tooltip.append( '<tr><td class="label explain-tooltip">' + key + '</td><td class="label explain-tooltip-val">' + value + '</td></tr>' );
+            };
+          });
+
+          var zoomFactor = graphContainer.data('zoom-factor');
+
+          // Calculate co-ordinates for tooltip
+          var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft());
+          var toolTipY = ((currentYpos + pHEIGHT) * zoomFactor - graphContainer.scrollTop());
+
+          // Recalculate x.y if tooltip is going out of screen
+          if(graphContainer.width() < (toolTipX + toolTipContainer[0].clientWidth))
+            toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH*zoomFactor));
+          //if(document.children[0].clientHeight < (toolTipY + toolTipContainer[0].clientHeight))
+          if(graphContainer.height() < (toolTipY + toolTipContainer[0].clientHeight))
+            toolTipY -= (toolTipContainer[0].clientHeight + ((pHEIGHT/2)*zoomFactor));
+
+          toolTipX = toolTipX < 0 ? 0 : (toolTipX);
+          toolTipY = toolTipY < 0 ? 0 : (toolTipY);
+
+          // Show toolTip at respective x,y coordinates
+          toolTipContainer.css({'opacity': '0.8'});
+          toolTipContainer.css('left', toolTipX);
+          toolTipContainer.css( 'top', toolTipY);
+        });
+
+        // Remove tooltip when mouse is out from node's area
+        image.mouseout(function() {
+          toolTipContainer.empty();
+          toolTipContainer.css({'opacity': '0'});
+          toolTipContainer.css('left', 0);
+          toolTipContainer.css( 'top', 0);
+        });
+
+        // Draw text below the node
+        var label = g.g();
+        g.multitext(
+          currentXpos + (pWIDTH / 2),
+          currentYpos + pHEIGHT - TXT_ALLIGN,
+          this.get('image_text'),
+          150,
+          {"font-size": TXT_SIZE ,"text-anchor":"middle"}
+        );
+
+        // Draw Arrow to parent only its not the first node
+        if (!_.isUndefined(pYpos)) {
+            var startx = currentXpos + pWIDTH;
+            var starty = currentYpos + (pHEIGHT / 2);
+            var endx = pXpos - ARROW_WIDTH;
+            var endy = pYpos + (pHEIGHT / 2);
+            var start_cost = this.get("Startup Cost"),
+                total_cost = this.get("Total Cost");
+            var arrow_size = DEFAULT_ARROW_SIZE;
+            // Calculate arrow width according to cost of a particular plan
+            if(start_cost != undefined && total_cost != undefined) {
+              var arrow_size = Math.round(Math.log((start_cost+total_cost)/2 + start_cost));
+              arrow_size = arrow_size < 1 ? 1 : arrow_size > 10 ? 10 : arrow_size;
+            }
+
+
+            var arrow_view_box = [0, 0, 2*ARROW_WIDTH, 2*ARROW_HEIGHT];
+            var opts = {stroke: "#000000", strokeWidth: arrow_size + 1},
+                subplanOpts = {stroke: "#866486", strokeWidth: arrow_size + 1},
+                arrowOpts = {viewBox: arrow_view_box.join(" ")};
+
+            // Draw an arrow from current node to its parent
+            this.drawPolyLine(
+              g, startx, starty, endx, endy,
+              isSubPlan ? subplanOpts : opts, arrowOpts
+            );
+        }
+
+        var plans = this.get('Plans');
+
+        // Draw nodes for current plan's children
+        _.each(plans, function(p) {
+            p.draw(s, xpos, ypos, currentXpos, currentYpos, graphContainer, toolTipContainer);
+        });
+    }
+});
+
+// Main backbone model to store JSON object
+var MainPlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plan": undefined,
+        xpos: 0,
+        ypos: 0,
+    },
+    initialize: function() {
+        this.set("Plan", new PlanModel());
+    },
+
+    // Parse the JSON data and fetch its children plans
+    parse: function(data) {
+        if (data && 'Plan' in data) {
+           var plan = this.get("Plan");
+           plan.set(
+             plan.parse(
+               _.extend(
+                 data['Plan'], {
+                   xpos: 0,
+                   ypos: 0
+                 })));
+
+           data['xpos'] = 0;
+           data['ypos'] = 0;
+           data['width'] = plan.get('width') + (xMargin * 2);
+           data['height'] = plan.get('height') + (yMargin * 2);
+
+           delete data['Plan'];
+        }
+
+      return data;
+    },
+    toJSON: function() {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (res.Plan) {
+        res.Plan = res.Plan.toJSON();
+      }
+
+      return res;
+    },
+    draw: function(s, xpos, ypos, graphContainer, toolTipContainer) {
+        var g = s.g();
+
+        //draw the border
+        g.rect(
+	        0, 0, this.get('width') - 10, this.get('height') - 10, 5
+	    ).attr({
+            stroke: '#FFEBCD', 'strokeWidth': 1.2,
+            fill: '#FFF8DC', fillOpacity: 0.5
+        });
+
+        //Fetch total width, height
+        TOTAL_WIDTH = this.get('width');
+        TOTAL_HEIGHT = this.get('height');
+        var plan = this.get('Plan');
+
+        //Draw explain graph
+        plan.draw(g, xpos, ypos, undefined, undefined, graphContainer, toolTipContainer);
+    }
+});
+
+// Parse and draw full graphical explain
+_.extend(
+    pgExplain, {
+        // Assumption container is a jQuery object
+        DrawJSONPlan: function(container, plan) {
+          var my_plans = [];
+          container.empty();
+          var curr_zoom_factor = 1.0;
+
+          var zoomArea =$('<div></div>', {
+                class: 'pg-explain-zoom-area btn-group',
+                role: 'group'
+                }).appendTo(container),
+              zoomInBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom in'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-search-plus'
+                  })),
+              zoomToNormal = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom to original'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-arrows-alt'
+                  }))
+              zoomOutBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom out'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>', {
+                    class: 'fa fa-search-minus'
+                  }));
+
+          // Main div to be drawn all images on
+          var planDiv = $('<div></div>',
+                           {class: "pgadmin-explain-container"}
+                         ).appendTo(container),
+              // Div to draw tool-tip on
+              toolTip = $('<div></div>',
+                           {id: "toolTip",
+                           class: "pgadmin-explain-tooltip"
+                           }
+                         ).appendTo(container);
+          toolTip.empty();
+          planDiv.data('zoom-factor', curr_zoom_factor);
+
+          var w = 0, h = 0,
+              x = xMargin, h = yMargin;
+
+          _.each(plan, function(p) {
+            var main_plan = new MainPlanModel();
+
+            // Parse JSON data to backbone model
+            main_plan.set(main_plan.parse(p));
+            w = main_plan.get('width');
+            h = main_plan.get('height');
+
+            var s = Snap(w, h),
+                $svg = $(s.node).detach();
+            planDiv.append($svg);
+
+            main_plan.draw(s, w - xMargin, yMargin, planDiv, toolTip);
+
+            var initPanelWidth = planDiv.width(),
+                initPanelHeight = planDiv.height();
+
+             /*
+              * Scale graph in case its width is bigger than panel width
+              * in which the graph is displayed
+              */
+            if(initPanelWidth < w) {
+              var width_ratio = initPanelWidth / w;
+
+              curr_zoom_factor = width_ratio;
+              curr_zoom_factor = curr_zoom_factor < MIN_ZOOM_FACTOR ? MIN_ZOOM_FACTOR : curr_zoom_factor;
+              curr_zoom_factor = curr_zoom_factor > INIT_ZOOM_FACTOR ? INIT_ZOOM_FACTOR : curr_zoom_factor;
+
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+            }
+
+            zoomInBtn.on('click', function(e){
+              curr_zoom_factor = ((curr_zoom_factor + ZOOM_RATIO) > MAX_ZOOM_FACTOR) ? MAX_ZOOM_FACTOR : (curr_zoom_factor + ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomInBtn.blur();
+            });
+
+            zoomOutBtn.on('click', function(e) {
+              curr_zoom_factor = ((curr_zoom_factor - ZOOM_RATIO) < MIN_ZOOM_FACTOR) ? MIN_ZOOM_FACTOR : (curr_zoom_factor - ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomOutBtn.blur();
+            });
+
+            zoomToNormal.on('click', function(e) {
+              curr_zoom_factor = INIT_ZOOM_FACTOR;
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomToNormal.blur();
+            });
+          });
+        }
+    });
+
+    return pgExplain;
+});
diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
index ddb9d8f..1366017 100644
--- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html
+++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
@@ -66,10 +66,53 @@ body {
             </button>
             <ul class="dropdown-menu dropdown-menu">
               <li>
+                <a id="btn-explain" href="#">
+                  <span>{{ _('Explain') }}</span>
+                </a>
+              </li>
+              <li>
+                <a id="btn-explain-analyze" href="#">
+                    <span>{{ _('Explain analyze') }}</span>
+                </a>
+              </li>
+              <li class="divider"></li>
+              <li class="dropdown-submenu dropdown-submenu">
+                <a href="#">{{ _('Explain Options') }}</a>
+                <ul class="dropdown-menu">
+                  <li>
+                    <a id="btn-explain-verbose" href="#" class="noclose">
+                      <i class="explain-verbose fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Verbose') }} </span>
+                    </a>
+                  </li>
+                  <li>
+                    <a id="btn-explain-costs" href="#" class="noclose">
+                      <i class="explain-costs fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Costs') }} </span>
+                    </a>
+                  </li>
+                  <li>
+                    <a id="btn-explain-buffers" href="#" class="noclose">
+                      <i class="explain-buffers fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Buffers') }} </span>
+                    </a>
+                  </li>
+                  <li>
+                    <a id="btn-explain-timing" href="#" class="noclose">
+                      <i class="explain-timing fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Timing') }} </span>
+                    </a>
+                  </li>
+                </ul>
+              </li>
+              <li class="divider"></li>
+              <li>
                 <a id="btn-auto-commit" href="#">
                     <i class="auto-commit fa fa-check" aria-hidden="true"></i>
                     <span> {{ _('Auto-Commit') }} </span>
                 </a>
+              </li>
+              <li>
                 <a id="btn-auto-rollback" href="#">
                     <i class="auto-rollback fa fa-check visibility-hidden" aria-hidden="true"></i>
                     <span> {{ _('Auto-Rollback') }} </span>
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index dee3ea1..6db04cb 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -236,3 +236,10 @@
 .CodeMirror-foldgutter-folded:after {
   content: "\25B6";
 }
+
+
+.sql-editor-explain {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index ee533a1..aeb1fdd 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -1,9 +1,8 @@
 define(
-  ['jquery', 'underscore', 'alertify', 'pgadmin', 'backbone', 'backgrid', 'codemirror',
-   'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection', 'codemirror/addon/selection/active-line',
-   'codemirror/addon/fold/foldgutter', 'codemirror/addon/fold/foldcode', 'codemirror/addon/fold/pgadmin-sqlfoldcode',
-   'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator', 'backgrid.filter',
-   'bootstrap', 'pgadmin.browser', 'wcdocker'],
+  ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', 'pgadmin.misc.explain',
+   'backbone', 'backgrid', 'codemirror', 'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
+   'codemirror/addon/selection/active-line', 'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator',
+   'backgrid.filter', 'bootstrap', 'pgadmin.browser', 'wcdocker'],
   function($, _, alertify, pgAdmin, Backbone, Backgrid, CodeMirror) {
 
     // Some scripts do export their object in the window only.
@@ -161,6 +160,12 @@ define(
         "click #btn-auto-rollback": "on_auto_rollback",
         "click #btn-clear-history": "on_clear_history",
         "click .noclose": 'do_not_close_menu',
+        "click #btn-explain": "on_explain",
+        "click #btn-explain-analyze": "on_explain_analyze",
+        "click #btn-explain-verbose": "on_explain_verbose",
+        "click #btn-explain-costs": "on_explain_costs",
+        "click #btn-explain-buffers": "on_explain_buffers",
+        "click #btn-explain-timing": "on_explain_timing",
         "change .limit": "on_limit_change"
       },
 
@@ -247,7 +252,7 @@ define(
           height:'100%',
           isCloseable: false,
           isPrivate: true,
-          content: '<div class="sql-editor-explian"></div>'
+          content: '<div class="sql-editor-explain"></div>'
         })
 
         var messages = new pgAdmin.Browser.Panel({
@@ -639,6 +644,79 @@ define(
             self.handler
         );
       },
+
+      // Callback function for explain button click.
+      on_explain: function() {
+        var self = this;
+
+        // Trigger the explain signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain analyze button click.
+      on_explain_analyze: function() {
+        var self = this;
+
+        // Trigger the explain analyze signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-analyze',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "verbose" button click
+      on_explain_verbose: function() {
+        var self = this;
+
+        // Trigger the explain "verbose" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-verbose',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "costs" button click
+      on_explain_costs: function() {
+        var self = this;
+
+        // Trigger the explain "costs" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-costs',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "buffers" button click
+      on_explain_buffers: function() {
+        var self = this;
+
+        // Trigger the explain "buffers" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-buffers',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "timing" button click
+      on_explain_timing: function() {
+        var self = this;
+
+        // Trigger the explain "timing" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-timing',
+            self,
+            self.handler
+        );
+      },
+
       do_not_close_menu: function(ev) {
         ev.stopPropagation();
       }
@@ -703,6 +781,12 @@ define(
           self.on('pgadmin-sqleditor:button:download', self._download, self);
           self.on('pgadmin-sqleditor:button:auto_rollback', self._auto_rollback, self);
           self.on('pgadmin-sqleditor:button:auto_commit', self._auto_commit, self);
+          self.on('pgadmin-sqleditor:button:explain', self._explain, self);
+          self.on('pgadmin-sqleditor:button:explain-analyze', self._explain_analyze, self);
+          self.on('pgadmin-sqleditor:button:explain-verbose', self._explain_verbose, self);
+          self.on('pgadmin-sqleditor:button:explain-costs', self._explain_costs, self);
+          self.on('pgadmin-sqleditor:button:explain-buffers', self._explain_buffers, self);
+          self.on('pgadmin-sqleditor:button:explain-timing', self._explain_timing, self);
 
           if (self.is_query_tool) {
             self.gridView.query_tool_obj.refresh();
@@ -948,10 +1032,24 @@ define(
           var message = 'Total query runtime: ' + self.total_time + '\n' + self.rows_affected + ' rows retrieved.';
           $('.sql-editor-message').text(message);
 
-          // Add the data to the collection and render the grid.
-          self.collection.add(data.result, {parse: true});
-          self.gridView.render_grid(self.collection, self.columns);
-          self.gridView.data_output_panel.focus();
+          /* Add the data to the collection and render the grid.
+           * In case of Explain draw the graph on explain panel
+           * and add json formatted data to collection and render.
+           */
+          var explain_data_array = [];
+          if('QUERY PLAN' in data.result[0] && _.isObject(data.result[0]['QUERY PLAN'])) {
+              var explain_data = {'QUERY PLAN' : JSON.stringify(data.result[0]['QUERY PLAN'], null, 2)};
+              explain_data_array.push(explain_data);
+              self.gridView.explain_panel.focus();
+              pgExplain.DrawJSONPlan($('.sql-editor-explain'), data.result[0]['QUERY PLAN']);
+              self.collection.add(explain_data_array, {parse: true});
+              self.gridView.render_grid(self.collection, self.columns);
+          }
+          else {
+            self.collection.add(data.result, {parse: true});
+            self.gridView.render_grid(self.collection, self.columns);
+            self.gridView.data_output_panel.focus();
+          }
 
           // Hide the loading icon
           self.trigger('pgadmin-sqleditor:loading-icon:hide');
@@ -1700,7 +1798,7 @@ define(
 
         // This function will fetch the sql query from the text box
         // and execute the query.
-        _execute: function () {
+        _execute: function (explain_prefix) {
           var self = this,
               sql = '',
               history_msg = '';
@@ -1719,6 +1817,17 @@ define(
           else
             sql = self.gridView.query_tool_obj.getValue();
 
+          // If it is an empty query, do nothing.
+          if (sql.length <= 0) return;
+
+          self.trigger(
+            'pgadmin-sqleditor:loading-icon:show',
+            '{{ _('Initializing the query execution!') }}'
+          );
+
+          if (explain_prefix != undefined)
+            sql = explain_prefix + ' ' + sql;
+
           self.query_start_time = new Date();
           self.query = sql;
           self.rows_affected = 0;
@@ -2037,6 +2146,66 @@ define(
               alertify.alert('Auto Commit Error', msg);
             }
           });
+        },
+
+        // This function will
+        _explain: function() {
+          var self = this;
+          var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          // No need to check for buffers and timing option value in explain
+          var explain_query = 'EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE %s, COSTS %s, BUFFERS OFF, TIMING OFF) ';
+          explain_query = S(explain_query).sprintf(verbose, costs).value();
+          self._execute(explain_query);
+        },
+
+        // This function will
+        _explain_analyze: function() {
+          var self = this;var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var buffers = $('.explain-buffers').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var timing = $('.explain-timing').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          var explain_query = 'Explain (FORMAT JSON, ANALYZE ON, VERBOSE %s, COSTS %s, BUFFERS %s, TIMING %s) ';
+          explain_query = S(explain_query).sprintf(verbose, costs, buffers, timing).value();
+          self._execute(explain_query);
+        },
+
+        // This function will toggle "verbose" option in explain
+        _explain_verbose: function() {
+          if ($('.explain-verbose').hasClass('visibility-hidden') === true)
+            $('.explain-verbose').removeClass('visibility-hidden');
+          else {
+            $('.explain-verbose').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "costs" option in explain
+        _explain_costs: function() {
+          if ($('.explain-costs').hasClass('visibility-hidden') === true)
+            $('.explain-costs').removeClass('visibility-hidden');
+          else {
+            $('.explain-costs').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "buffers" option in explain
+        _explain_buffers: function() {
+          if ($('.explain-buffers').hasClass('visibility-hidden') === true)
+            $('.explain-buffers').removeClass('visibility-hidden');
+          else {
+            $('.explain-buffers').addClass('visibility-hidden');
+          }
+        },
+
+        // This function will toggle "timing" option in explain
+        _explain_timing: function() {
+          if ($('.explain-timing').hasClass('visibility-hidden') === true)
+            $('.explain-timing').removeClass('visibility-hidden');
+          else {
+            $('.explain-timing').addClass('visibility-hidden');
+          }
         }
       }
     );


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  2016-05-09 15:19     ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-09 18:26       ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-10 06:51         ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
@ 2016-05-10 09:24           ` Akshay Joshi <[email protected]>
  2016-05-13 10:25             ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Akshay Joshi @ 2016-05-10 09:24 UTC (permalink / raw)
  To: Sanket Mehta <[email protected]>; +Cc: pgadmin-hackers

Hi Sanket

On Tue, May 10, 2016 at 12:21 PM, Sanket Mehta <sanket.mehta@enterprisedb
.com> wrote:

> Hi,
>
> As previous patch was not applicable to latest pgadmin4 source code, here
> is the new patch accommodating latest code.
> Please do review it and send comments.
>

    Following is my review comments

   - Remove Demo and sample code which you have used for testing before
   integration.
   - Facing issue to open QueryTool as there is some problem in require
   module.
   - Please add 'codemirror/addon/fold/foldgutter',
'codemirror/addon/fold/foldcode',
   'codemirror/addon/fold/pgadmin-sqlfoldcode' files which has been removed
   from your patch.
   - Remove below code from sqleditor.js which is duplicated in _execute
   function
   -
      - self.trigger(
      -            'pgadmin-sqleditor:loading-icon:show',
      -             '{{ _('Initializing the query execution!') }}'
      -           )
   - Clear the existing contents of the Explain tab when execute the query
   without explain.
   - If schema name is exists then please prefix the schema name to the
   node.
   - Please check the data is available before working on it in sqleditor.js at
   line no 1043 inside _render().  In case of "View Data" it throws an error.



>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Mon, May 9, 2016 at 11:56 PM, Sanket Mehta <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please ignore previous patch as there was an error in it.
>>
>> Error:
>> Tooltip was not getting disappear when user moves cursor out of image.
>>
>> I have attached a proper patch with this mail.
>> Please consider it for testing.
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>> On Mon, May 9, 2016 at 8:49 PM, Sanket Mehta <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> PFA revised patch according to Ashesh's comments.
>>> Please find my response inline.
>>>
>>> I am currently adding minimap feature in graphical explain.
>>> I will send a new patch for the same.
>>>
>>> Regards,
>>> Sanket Mehta
>>> Sr Software engineer
>>> Enterprisedb
>>>
>>> On Mon, Apr 25, 2016 at 4:36 PM, Ashesh Vashi <
>>> [email protected]> wrote:
>>>
>>>> Hi Sanket,
>>>>
>>>> Please find the review comments.
>>>> - Please add the missing 'explain.css'.
>>>>
>>> Done
>>>
>>>> - The application should be smart enough to handle conflict in options.
>>>>    i.e.
>>>>    Buffer is not a valid options without EXPLAIN ANALYZE.
>>>>
>>> Done
>>>
>>>> - A statement having EXPLAIN keywords with different format should at
>>>> least render the output in the data-grid.
>>>>   i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
>>>>
>>> Done
>>>
>>>> - Please use the keywords used in the EXPLAIN statement in capital.
>>>>
>>> Done
>>>
>>>> - Explain should not work with empty string.
>>>>
>>> Done
>>>
>>>> - Font size in the tooltip is very small.
>>>>
>>> Done
>>>
>>>>
>>>>
>>> - Smoothing the zoom functionality.
>>>>
>>> Minimap will be added and zoom functionality will be removed. So it is
>>> ignored.
>>>
>>> - Arrow marker is hardly visible.
>>>>
>>> Done.
>>>
>>>>
>>>>
>>>> --
>>>>
>>>> Thanks & Regards,
>>>>
>>>> Ashesh Vashi
>>>> EnterpriseDB INDIA: Enterprise PostgreSQL Company
>>>> <http://www.enterprisedb.com;
>>>>
>>>>
>>>> *http://www.linkedin.com/in/asheshvashi*
>>>> <http://www.linkedin.com/in/asheshvashi;
>>>>
>>>> On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> This patch includes the patch sent earlier for stand alone graphical
>>>>> explain.
>>>>>
>>>>> And also "horizontal lines are not proper" bug is fixed in the same
>>>>> which was reported by Dave in previous patch.
>>>>>
>>>>> Regards,
>>>>> Sanket Mehta
>>>>> Sr Software engineer
>>>>> Enterprisedb
>>>>>
>>>>> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Team,
>>>>>>
>>>>>> PFA the first patch for graphical explain integrated in sql editor.
>>>>>>
>>>>>> Below are the few things which are different from previous patch
>>>>>> which was sent for stand alone graphical explain.
>>>>>>
>>>>>>  -  Now user can select Explain/Explain Analyze with four optional
>>>>>> properties (Verbose, costs, timing and buffers)
>>>>>>
>>>>>>  - Initially graph will be scale (according to only its width not
>>>>>> height) to fit to screen so no blank space will be there in case of very
>>>>>> large graph.
>>>>>>
>>>>>> - Along with zoom in/out button, "zoom to original" button is also
>>>>>> provided, by clicking on which graph will be scale to its original size
>>>>>> (not same as initial one which is according to screen size).
>>>>>>
>>>>>> Please do review this patch and let me know in case you have any
>>>>>> comments.
>>>>>>
>>>>>>
>>>>>> Regards,
>>>>>> Sanket Mehta
>>>>>> Sr Software engineer
>>>>>> Enterprisedb
>>>>>>
>>>>>
>>>>>
>>>>
>>>
>>
>
>
> --
> Sent via pgadmin-hackers mailing list ([email protected])
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgadmin-hackers
>
>


-- 
*Akshay Joshi*
*Principal Software Engineer *



*Phone: +91 20-3058-9517Mobile: +91 976-788-8246*


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  2016-05-09 15:19     ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-09 18:26       ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-10 06:51         ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-10 09:24           ` Re: PATCH: Graphincal explain integrated in sql editor Akshay Joshi <[email protected]>
@ 2016-05-13 10:25             ` Sanket Mehta <[email protected]>
  2016-05-13 10:31               ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Sanket Mehta @ 2016-05-13 10:25 UTC (permalink / raw)
  To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers

Hi,

Revised patch is attached
Response in inline.


Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Tue, May 10, 2016 at 2:54 PM, Akshay Joshi <[email protected]
> wrote:

> Hi Sanket
>
> On Tue, May 10, 2016 at 12:21 PM, Sanket Mehta <sanket.mehta@enterprisedb
> .com> wrote:
>
>> Hi,
>>
>> As previous patch was not applicable to latest pgadmin4 source code, here
>> is the new patch accommodating latest code.
>> Please do review it and send comments.
>>
>
>     Following is my review comments
>
>    - Remove Demo and sample code which you have used for testing before
>    integration.
>
> Fixes

>
>    - Facing issue to open QueryTool as there is some problem in require
>    module.
>
> Fixed

>
>    - Please add 'codemirror/addon/fold/foldgutter', 'codemirror/addon/fold/foldcode',
>    'codemirror/addon/fold/pgadmin-sqlfoldcode' files which has been
>    removed from your patch.
>
> Fixed

>
>    - Remove below code from sqleditor.js which is duplicated in _execute
>    function
>    -
>       - self.trigger(
>       -            'pgadmin-sqleditor:loading-icon:show',
>       -             '{{ _('Initializing the query execution!') }}'
>       -           )
>
> Fixed

>
>    - Clear the existing contents of the Explain tab when execute the
>    query without explain.
>
> Fixed

>
>    - If schema name is exists then please prefix the schema name to the
>    node.
>
> Fixed

>
>    - Please check the data is available before working on it in sqleditor.
>    js at line no 1043 inside _render().  In case of "View Data" it throws
>    an error.
>
> Fixed

>
>
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>> On Mon, May 9, 2016 at 11:56 PM, Sanket Mehta <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> Please ignore previous patch as there was an error in it.
>>>
>>> Error:
>>> Tooltip was not getting disappear when user moves cursor out of image.
>>>
>>> I have attached a proper patch with this mail.
>>> Please consider it for testing.
>>>
>>> Regards,
>>> Sanket Mehta
>>> Sr Software engineer
>>> Enterprisedb
>>>
>>> On Mon, May 9, 2016 at 8:49 PM, Sanket Mehta <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> PFA revised patch according to Ashesh's comments.
>>>> Please find my response inline.
>>>>
>>>> I am currently adding minimap feature in graphical explain.
>>>> I will send a new patch for the same.
>>>>
>>>> Regards,
>>>> Sanket Mehta
>>>> Sr Software engineer
>>>> Enterprisedb
>>>>
>>>> On Mon, Apr 25, 2016 at 4:36 PM, Ashesh Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Sanket,
>>>>>
>>>>> Please find the review comments.
>>>>> - Please add the missing 'explain.css'.
>>>>>
>>>> Done
>>>>
>>>>> - The application should be smart enough to handle conflict in options.
>>>>>    i.e.
>>>>>    Buffer is not a valid options without EXPLAIN ANALYZE.
>>>>>
>>>> Done
>>>>
>>>>> - A statement having EXPLAIN keywords with different format should at
>>>>> least render the output in the data-grid.
>>>>>   i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
>>>>>
>>>> Done
>>>>
>>>>> - Please use the keywords used in the EXPLAIN statement in capital.
>>>>>
>>>> Done
>>>>
>>>>> - Explain should not work with empty string.
>>>>>
>>>> Done
>>>>
>>>>> - Font size in the tooltip is very small.
>>>>>
>>>> Done
>>>>
>>>>>
>>>>>
>>>> - Smoothing the zoom functionality.
>>>>>
>>>> Minimap will be added and zoom functionality will be removed. So it is
>>>> ignored.
>>>>
>>>> - Arrow marker is hardly visible.
>>>>>
>>>> Done.
>>>>
>>>>>
>>>>>
>>>>> --
>>>>>
>>>>> Thanks & Regards,
>>>>>
>>>>> Ashesh Vashi
>>>>> EnterpriseDB INDIA: Enterprise PostgreSQL Company
>>>>> <http://www.enterprisedb.com;
>>>>>
>>>>>
>>>>> *http://www.linkedin.com/in/asheshvashi*
>>>>> <http://www.linkedin.com/in/asheshvashi;
>>>>>
>>>>> On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> This patch includes the patch sent earlier for stand alone graphical
>>>>>> explain.
>>>>>>
>>>>>> And also "horizontal lines are not proper" bug is fixed in the same
>>>>>> which was reported by Dave in previous patch.
>>>>>>
>>>>>> Regards,
>>>>>> Sanket Mehta
>>>>>> Sr Software engineer
>>>>>> Enterprisedb
>>>>>>
>>>>>> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Team,
>>>>>>>
>>>>>>> PFA the first patch for graphical explain integrated in sql editor.
>>>>>>>
>>>>>>> Below are the few things which are different from previous patch
>>>>>>> which was sent for stand alone graphical explain.
>>>>>>>
>>>>>>>  -  Now user can select Explain/Explain Analyze with four optional
>>>>>>> properties (Verbose, costs, timing and buffers)
>>>>>>>
>>>>>>>  - Initially graph will be scale (according to only its width not
>>>>>>> height) to fit to screen so no blank space will be there in case of very
>>>>>>> large graph.
>>>>>>>
>>>>>>> - Along with zoom in/out button, "zoom to original" button is also
>>>>>>> provided, by clicking on which graph will be scale to its original size
>>>>>>> (not same as initial one which is according to screen size).
>>>>>>>
>>>>>>> Please do review this patch and let me know in case you have any
>>>>>>> comments.
>>>>>>>
>>>>>>>
>>>>>>> Regards,
>>>>>>> Sanket Mehta
>>>>>>> Sr Software engineer
>>>>>>> Enterprisedb
>>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>>
>> --
>> Sent via pgadmin-hackers mailing list ([email protected])
>> To make changes to your subscription:
>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>
>>
>
>
> --
> *Akshay Joshi*
> *Principal Software Engineer *
>
>
>
> *Phone: +91 20-3058-9517Mobile: +91 976-788-8246*
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/x-patch] integrated_graphical_explainV6.patch (496.6K, 3-integrated_graphical_explainV6.patch)
  download | inline diff:
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index f461cbe..125c44c 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -6,28 +6,52 @@
 # This software is released under the PostgreSQL Licence
 #
 ##########################################################################
-
 """A blueprint module providing utility functions for the application."""
 
-import datetime
-from flask import session, current_app
+from flask import url_for, render_template
 from pgadmin.utils import PgAdminModule
 import pgadmin.utils.driver as driver
+import config
 
 MODULE_NAME = 'misc'
 
-# Initialise the module
-blueprint = PgAdminModule(MODULE_NAME, __name__,
-                          url_prefix='')
-
-##########################################################################
-# A special URL used to "ping" the server
-##########################################################################
-
+class MiscModule(PgAdminModule):
+
+    def get_own_javascripts(self):
+        scripts = [{
+                'name': 'pgadmin.misc.explain',
+                'path': url_for('misc.index') + 'explain/explain',
+                'preloaded': False
+                },{
+                'name': 'snap.svg',
+                'path': url_for(
+                    'misc.static', filename='explain/js/' + (
+                        'snap.svg' if config.DEBUG else 'snap.svg-min'
+                        )),
+                'preloaded': False
+                 }]
+        return scripts
+
+    def get_own_stylesheets(self):
+        stylesheets = []
+        stylesheets.append(url_for('misc.static', filename='explain/css/explain.css'))
+        return stylesheets
+
+ # Initialise the module
+blueprint = MiscModule(MODULE_NAME, __name__, static_url_path="/static")
+
+ ##########################################################################
+ # A special URL used to "ping" the server
+ ##########################################################################
+
[email protected]("/")
+def index():
+    return ''
 
 @blueprint.route("/ping", methods=('get', 'post'))
 def ping():
-    """Generate a "PING" response to indicate that the server is alive."""
     driver.ping()
 
-    return "PING"
[email protected]("/explain/explain.js")
+def explain_js():
+    return render_template("explain/js/explain.js")
diff --git a/web/pgadmin/misc/static/explain/css/explain.css b/web/pgadmin/misc/static/explain/css/explain.css
new file mode 100644
index 0000000..49a4bc4
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/css/explain.css
@@ -0,0 +1,56 @@
+.pg-explain-zoom-area {
+    position: absolute;
+    top: 5px;
+    left: 5px;
+    opacity: 0.5;
+    cursor: pointer;
+}
+
+.pg-explain-zoom-btn {
+    top: 5px;
+    min-width: 25px;
+    cursor: pointer;
+    border: 1px solid transparent;
+}
+
+.pg-explain-zoom-area:hover {
+   opacity: 1;
+}
+
+.explain-tooltip {
+  display: table-cell;
+  text-align: left;
+  white-space: nowrap;
+  line-height: 10px !important;
+  padding: 2px !important;
+  font-size: small;
+}
+
+td.explain-tooltip-val {
+  display: table-cell;
+  text-align: left;
+  white-space: pre-wrap;
+  font-size: smaller;
+}
+
+.pgadmin-explain-tooltip {
+  position: absolute;
+  padding:5px;
+  border: 1px solid white;
+  opacity:0;
+  color: cornsilk;
+  background-color: #010125;
+}
+
+.pgadmin-tooltip-table {
+  border-collapse: collapse;
+  border-spacing: 1px;
+  top: auto;
+  left: auto;
+}
+
+.pgadmin-explain-container {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
\ No newline at end of file
diff --git a/web/pgadmin/misc/static/explain/img/ex_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..6bfe75d909f5092c4d3ba8978c75fbc57d7f606d
GIT binary patch
literal 574
zcmV-E0>S->P)<h;3K|Lk000e1NJLTq001%o001%w1^@s69zTe&00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10lP^=K~!jg?U_w#!!Qs=pNrR76nc`JAiEyfyPUu$F?5lg!9`tE
zrVdorjI6O#*B=N1Q4~GTk7uODImZ$7QhEcqbb{2T!+^AsNlnvqz}0v!OZCpVcg+u)
zSl03oH-ylcGy!)Fj09u=UN>$mMIX+&H|b<ajP!gzp{f<N2t38e1(}OYz(X)^Z9SDm
zaL$Pb&;cXx85twc3D+9}YYwWtX(n61tgLAZVhA&A0ZDox`m}f_o&;Lp=3^|TZAm4?
zB8D-uw2Hk&77xL~GD+H8Yh{L+-D~onRU64N$mC{z9Z`Z<4$%uyDn(tUuBBqiTE>@*
zne6>YDVVIT^|Y|u&2%+YKxQ4H!ZKN8+Uk0kwJKPjW&<(>@$PjAe4RCOnSn%Nr0(=P
zYi|fJ04V_hnL$cH0K3&%;sz_V=BgQD)cm$)2vvhs6@*_isdrBfc8kD{yg=7gktITF
z+PGFu2!0OeLWgu>5LFp3D9xourL!bQu%a?wd{rRqFIvi++{=Q!&>aaV%KTa{dS;2c
z$5o3IhEOTyT37x61pK30-JX4KbAS7Pk<5;R_SRus>jbGyCrEAj0!#X?PWurOy8r+H
M07*qoM6N<$f>VU-$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_append.png b/web/pgadmin/misc/static/explain/img/ex_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..017a2068b735f13cb89a3bcb2832e5880d17df56
GIT binary patch
literal 1162
zcmV;51a<p~P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(LcQrnzUg(&?|IPgRl@0p)bM7=>}tvEZ_4YI+3|bM?Sj$mrrz?7)b5hj
z@2}zW1*hRn!RZ^W<6Fh+V#n(^x8+d2=WEC4a+aaNk2du1$nfm9@9MSh>9q3g#qH&>
z?c}iR<FN7S!|>?B>));G-mL1~tMBB$>*k;D>$d6On&{t|?&h=S+m`0ol<eWI<I<1f
z(2eQZsp{jL=-rs*){^1Oi|N^?<kgYn(~#oMjOf><@94Ab;k?JHb@1lE=hUR;)1&O&
zx!}r)-o=I9!-U<zg67eo-^hpM&!OhgqTIiL<j$Y(=D_CCqwCqW<jtPr%%0=Rp4+{D
z+PZy)sbo@(8Kui)sL*Ju)oiZXakAicwdHz}mC<Hv(aX&7yuR$dzwOS>^3l=q!NTst
z#P8G8^VQY#$jI=?$??j|@!8t--ro1u*Y)Ay_q@IAr>W+ss^`DI?yaxsud(UH#qY<+
z@3pq;x47%Lx$M-`^vB2WiI(>{00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0zpYcK~zY`?U3tJ5>Xh&?SzGeqJ$LLD=fGO%SB*ek((>YO)i2W3vOk#CSYi4
znRfSIZ+9mfuyZIJXZldjhj-?kd4K0Q&oeUeU#e~(MZ}3i&`phI^cK3U)oRDk*qyt&
zaWp=m*H5C!DTCo2!Xg@@(Kw2xO(xTQ^uTO3>(InQtyYhwW@dB-G{|N8&s`p=e<n^G
z0<#v2W%goPDar;m`yB0nd8dnU0~WD(JRXZWy+HYV3x2Q%f<YFXp-`9`j6@<<8Ch7g
z109S;oxp^{vG^dw8;L|H@Gk}eG_cV`k^ych7US{Aj}#QOtfZjT6pWn09q0KJ7I_Sc
zh!gMP^;&1a=J(qj;9yzj3b;8go`O_5oyp=qCa3T%U!+JRLvo5(EXPASzgj5bk-lP+
zO0n@=u9Sw%YN1djA(xBgOQn1U)(VwM6_42b_BpvF*6CW8Q^eI2nT;%D%hhV_+8T4v
zEISV?48yr0#q(+T{k3Ab2DQz)fOi2p8cn$56iaG~e0~Fpl}e)u^=7jv;1M>FxKwPp
z(r9dgKt_How%TaO#{-achU4Ux__UIuSXNThl@v8W5U#DUt7}W#B5ow&NzYaPMkJm-
z`+3#B5v1H~KNqnZ(M8-Adt7=qvQOWu;_p1vqZcA^BYthzl84euNfB}45NYSt?ruwJ
zclP#POWpm0H;2+;tKB}5j=J6Bw-Oe4cXIOnRO+0aefTJS`uyeVj?_BsblTFl^ZkS4
zzljW=<qD1clll(R-{b2g;$(6F001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@z
zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUh
cIxsdXFf}?bFv^!ztN;K207*qoM6N<$g1#AbUjP6A

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_and.png b/web/pgadmin/misc/static/explain/img/ex_bmp_and.png
new file mode 100644
index 0000000000000000000000000000000000000000..64d5869dcfcf9d94d031fa068cff93ea929180b2
GIT binary patch
literal 1006
zcmV<K0}=d*P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004)P)t-smD2kF
z003rd(R6flb#--ic6N7ncY1nyHi*G{dwYC*e13j@fPjF4f`WsCgM@^HhlhuVh=_}e
zi;azqj*gCGmC%omkC2d%k&%&&j=z$UlEIHQm6er`lHGcqshOFXoSdAVo}QbW#Gjv^
zrlzK+r>D86a;T`NPQK>5s&lKWtGldpyRCMut*u(Y=dP}<ys&q#udltad8@4GzOs6W
zx!ba`vSh{Rx3{;mwCZuo?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?Zd;v-NJ*w!S2S!
z#*5VM-^PaD$cK{E@5aaP;L3^0%F2}3@Z-y#<IJAq&7R`WjpWXs%+2x5&hq8Zq2|$|
z)6>(Y-ty(vljhW<)z#JK)upZ9^VHPy=+~y`*Qe^(w&~ia>DsB<+S=*ds_NXT>)W{8
z+}z#W-Rs`0?A^KE-rnrst?b~g?cclJ-uCR`ui@e0?BlTF;^Ob*zvboS@aDkq=fUUa
z=jrL`@$1Cu>gw?D@bU5S^78WY^Yiuf_4fAm`1ttw`T70*{r~^}C9&%N00001bW%=J
z06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0q;pfK~zY`?UY$l6G0S(b8}-Di3kcP
z+Oo)^h$4bwltoqvL?DD{Mjf_nBqD~O2?A~ymixDzo*9a*Gm})MC?7Zv{c`K8Gv{>Q
zvDq$ko~o9(s?QF(wL!N4k*52GJqwax@WJEtiUu~Rr}eShD?&VOcaK*pj!Vugb=sg#
zfY{%@e)DeKes;iqrqk&yCPD_D!r-C^Nk4<GWMRlx_z0E==`xVDE_fnFj<OJC>PwuJ
zQ#!=9lF84TBBa*NRjVOi9LP1IA^nW2-@@fKw*1L<;8opaGaip`h_l>+MlB1G6SG9S
z=+u$;BX}4UBk&Ro<O=>E`h+(O1ZE*><<f@Dv{HsEI<ou#?nSH``|ZN(SURV-%r>Ht
zNXoo1qP+&hYdgl><kJ{stMHBkrzooyKsJ^Ng_cqlS=#YAjp3zO3@bPinuH0(v@xPG
z-th~(T!n2MS(<=x#ngpg%P%#>ef9pot6A9mcrN5H3(-xi$?R{pAeQfQB&8_Is%c|H
zG5v{QDc(Jx{2HTgO)iJ4;r-tV>{PR?%CelW*cUn`^~2;*7z$c$rLkbz!Q>%$6)bF#
zgDMhW1^r<X!9XBkv6Uy4*H)f(3HbeC^EY-H@%KXSjQLiI5MN;~GdYo*S;9V_FI=R?
cF7zMiA0?@qIju>9W&i*H07*qoM6N<$f<qfLO8@`>

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png b/web/pgadmin/misc/static/explain/img/ex_bmp_heap.png
new file mode 100644
index 0000000000000000000000000000000000000000..2657d8c39328bfc80751c60db9d3ab4341c0de35
GIT binary patch
literal 1106
zcmV-Y1g-mtP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s00000
z003rd(Nc{WHi*Gol_FY@Wn`8)38~@|tKwvp&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPGu5^~NHbTAWN5AP#zUE86=}y7vTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q^_*?AyJGyuIvu(eH!N?S#?pz`^c>
z)9|d;ZHv_Ik=5>!)$gv_amL5*l-Tf>+3~XAcCFUw%+2wj+wtexE9lxP&Cc@b+bZna
zD(>4V@7yZ!+$!?iD)ihcx!dfw=Y00vDy`r1`Q0h{-6_=6^RD3Y``#)1-YNdxDZbwA
zx$A$x-|oWT?z`@S?c-qY<6p($@9^Ybyzqqb<X`jTU&rI{$m8(y<zMvWU&`a~_2pmp
z<zM*aU(4k2-rn~3=3n~eU(Mz6{N`W&=3mk0^3&+^^6H1w>GRa-^z`e8*6H;2>xcO3
zhu7-$`s|1O?1ujAhT-q`mEcQQ00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94
zoEQKA0&7V`K~zY`?Ud_N(?Ar(NfQ+@uBZsUQR*A(3k7^uiA_xlwb7<XX$T2Wl46zw
z3Z<omf1TZggd}Y;GR`=D;C$H3?B=&;&b@cGr{_ffM12%iU&sC(Yu~pzN7RRBuO_}z
z9SHSg=<4;C)SHp}k3Ldg#wI3zy8lp*uid=+whJ=U+k5r~HGFAc;3+jWJaTW6`U{t^
zEElbP-|8I2KEHnd^>^&(-vmu3&<E7Y<#G+wL{Y4Rx+F<;;36;16PJH@5PE-~#z=W;
ziYWt;VNw#1SeBJN2=S2cA-lUa!Z3^o#8j#a5_H+w@gP!)Wi2G_L4sKv#G9fn%W}g-
z`eRt9$y)*BIl%I*K9?(GoOF@xZQI4ZUy^0CDl3(yC(CW(0vV5!j_A!z8h-}~mT*$6
za2%)l5JX@-7#$sd_le%vpcj!ymO#PfbULko3dd#C2p5DnE;2^GRe;K6G8zcaf)Jd=
zPSH>*C`D7%v}T}UXUKCdvcCQ&74!AQMsP2b2D)EWk&C8PTM^wqM7$}qY%Zrq%-GtR
zg(zuUST%!@YA%<95iYB%7Gn28&1ADxp!<=IEX&Il;!V+l5Vj&_Y-#D(Gq$jB=z@FE
z)OFo<X@_?I6h_E^1__=pLT79oBQrC<=I0lWMDj$zh?ucbsf$a&>(Y@0AyyV$hfa#N
zVHoX*8Jn9s_Kewxb3cRzb}`Mpu<oOWZ+aR(egNk4c?ck1K*3ExD4Jn2MsLj~le1g2
zh42s<1wlYavFJkrE~R9$WV`UZ0SrM9%pelTlE5>D9%WyOJ=2@Tu2_G^GagZ~6a9ZW
Y0Mz-{B6#`Cr~m)}07*qoM6N<$f){{JGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_index.png b/web/pgadmin/misc/static/explain/img/ex_bmp_index.png
new file mode 100644
index 0000000000000000000000000000000000000000..23b9733b56792533e4db25ca9890a0a0140c6ff9
GIT binary patch
literal 1172
zcmV;F1Z(?=P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004@P)t-s000{R
z003rd(Nc{WHi*Gol_FY@Wn`8)45{LDla^$a&~c(UW|q;7j=#Z=Hh!USkCNSbo~bRg
z<eQwtI=JPPu{MmZbjzYxL%irozv)iC=1#%sTEXX;ym+gu=v~C>iMiWk#pq(j>Y>D5
zYsctl$n2uVV6(L9Zp-X(%j|E>?xxITcFpXi!Q|V#etORA+r5d~zJR>F?0eDg-NAx`
z(d~rM?%l(Kz`^c>)9|d;ZQjL&i`4Gl$A^*C?%>FXlGX37+HuCm@8HUbl-Tg$%!-%U
z@#4;m<IJA2;C8Ln>CDaX<<6kv(T?ZZE6vXG=Fy>}-SXtrk>=B+^4u!t)TFuF?6>E9
zt>5$1)bp<3^XA!==+~zI-YMzWr@r3px$A%D+?T-L?&;d7!r<=c-k9s!x9Z%g?c-qY
z<6p($@9N#Gyzqqc<X`FGo9x}X$K&wG<M7Jk@a*8N%jEIi-uC(CU(Mz6?BlTi=3nmO
zz3t_((dY8+=Caf1^Y7)q^6H1w>GRa-^w#P0@aMtr>9p7C_3`P${_KYG?Z)Bn_g>Mo
zbpQYW0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_00N9jL_t(Y$L*BsQxZ`a
zhh26nvhB^vO1oLo-JmFv5|N5XBA|_nRxB8D7XcMR7F@v#0{+$QVK1_a?Bq1l^uhPT
z*_pHZ%x|7^ezOAuC-YBckU%rovwx?vFI&Y|#D{0EroRzA2=QcObo3?hX8iu6kHpu>
z>6stWH^k%XH}AeZ0vXY2wKs^dOT)uYiOI3?do#per1Wz++u7&Wi~K6S(tLjX{>v}T
z;kSB{)N>E0r_<>=v>S~^8`><(wn0K(oX0MI??T9f0}>%=uh*M~Mn0c!0Gmiet6d28
z5R)PM`wD~wHX4nVRZ@0$Wk@2yLNyu+bs<U@5fNNE7R_?GyeA<;8Z@WzTMFbFpyAi&
z{3=ViitBI*+1Zh$RI5#B7K_EbE|=Tb1ze}Y#UZ!0Nc6mdd9k!$QS|wISsB6+XdX;V
zOuhR=Zf=sx+~8h})31g?q2dvUJcUEVlnj&w#N$ape-{qnT{4-vU{TAaQZ>biox#sZ
z$i~K>oS5uhcm(RXT&@m##cZ|)wNxxtQMr8q$pr#|oL~`iI2^P}$JW;+qy$+HkJ#<B
zO3K=rgfNiu%+AIjVz=9ZDji#0?I5jEsnmj63|UlYnl7kxY-Q!x9a~xwQW&NMl}LoH
zKp`0P7y91*DTMI1AI2EY!p2zyEfzD?w_{TXnV<i;jEAu>1GLR%4T9fnRv_|@#p7Km
zwAE@sh{;5$bc{nfE(~(vEeaGxB~?1MxOnW2@ran>r>FuX-EMcX-|cfhzPUN{^8+Rv
z=Ja_Bx6xp5_UjnAz2I^!Y?C5FnM@`(xD9edkrH>g;)f}e$!P3B6fSzyF}>u%TO^%M
mr}D&xdVb?7Cw4Ob-~0w&gyX$CR{j?N0000<MNUMnLSTYde~VB6

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_bmp_or.png b/web/pgadmin/misc/static/explain/img/ex_bmp_or.png
new file mode 100644
index 0000000000000000000000000000000000000000..c22fc31eee32e53abf634278f9d57751181f15c0
GIT binary patch
literal 685
zcmV;e0#f~nP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700030P)t-s00000
z003B6SY~U{Hi*GwmC%ijzrl|-kCNSbo~fFenwy-&xu<eYzUI5CbGxi`yRCLw!RNfN
zcfGNBtE}k0vU-WR+hoP)v$X2Ey1H@8?Ap72dd}?Iy@}kufV{ozyuIz+!GeR)?cKtI
zz`^c|)b8KLhTq7ClGX3V$M4|EiImvz<IA4o%%0@Up5oAr<j$YW&GF67^3l=J<<Oz#
z(W0i_^5xc(=G3I;)upZ9^VHPy=+~y`*Qe^(w&~ia>DsC3+^Xu_s_Wah>)x#F-MQ@G
zt?b~g?cclJ-uCR`uk7Qn@8rMa<mB+?!0_k6@$1Cw?CkIF@AUNa|NsAqxAFG?0004W
zQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkPM@d9MR7l6|)NN0KU=#*mj$o+0
zXGv;SRwxw`6%>^SK@x>8z5V~+lCW40^I>$hY<=+l#CCS=?A!_rGtXZp&xOfP4=T~1
zLLuDg&VhK#Q3h9{B+&*8S6f~eBpML~p(b&^vn6@U$0T2m#b{8Z5cd4&_~MEE7O~-9
zf*=_4G_tn|`*$&UE0u;Z3AUi@Ws_kpcNvpsxCSJ7EW-w!ByJ(e*z+DnG*V#06sAdo
z57R(x899zKpx?3pi_}}3HCVOj1h#=r;0$csmirZ0vT%(JY|HXz-k5KiT_1Ogc>-+%
z*I2g=Ed#gZrj<t0Z!rv`Kl8@=x~{vp_eDR1riLU<*hLa;LR4I1uBNJPc4P0=>MO1>
z^3%t=s-pBVfBn$JPrOoxdMEQgMkXTi54I4blS&e|kfbNeaxb$nGU<)Y^N;cg@O4qi
TF88uo00000NkvXXu0mjfLbGwY

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png b/web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..e99f57478842f61cf0582433b659d37f0c532d5c
GIT binary patch
literal 334
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_@3?!2S6O@1yXMj(LE06{PXIHn!e64$cEXI-`
zzhDN3XE)M-9L@rd$YLPv0mg18v+aP48c!F;5RLO&CtC9zP~dS+JaMn}cL58V&ewnH
znS#1o7niJ>^>*TX*`QzgMdD7POzPaTo+w<9uwpLL)1F%W#!IqdhoMSxhwp~0@cf02
zhkMjmROfZc`EZ)w`S;|i<*e@Qr8gQ^@9R11|6Rc8|AQ9IRLjSmUOFeWtM)%%ZozPh
zRpl$&!*g?h?ocgpjVMV;EJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0
rRpn4L<mRVjrd2{T7+8WefK*!<m_an0njX3asDZ)L)z4*}Q$iB}@40B!

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_cte_scan.png b/web/pgadmin/misc/static/explain/img/ex_cte_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e1d779372f7fd212d2096e7701b0f1af05a5297
GIT binary patch
literal 1955
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_delete.png b/web/pgadmin/misc/static/explain/img/ex_delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca051cd5d01ee2f3ac17779b362999d82a9285b5
GIT binary patch
literal 1129
zcmV-v1eW`WP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003^P)t-sKLD^>
zTU+Qs2lh=8Hi*I5b0^MyED5RN(19;<mZ6f8lEIHQ*N;guwdHlD$Lf$-LA>b8qFCae
zX-dH9$EtPXqi$Wq>SM<0Ysctl$n0**>~+oT+Pi++y?@)iiQK+`+`oW((eK>Bf`rlT
z-NS=~)9~KJgx<x4-o}QE)b8KMhu_GEk=5?t$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc
z&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+mlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#
zr{>z0>DZ^~*{J8-m+9K6>e;sG+p6o@x9HxP>fNgB+_>rBn(N-I?A^NS->vN4y6oVs
z>f@a4-@EMLuI=Ev>g1j5;;-%DyzJw!?d7rW<i73ZvhL=y@8!Vo=D_dhv+wD(@aV$u
z>B8{rw(;x4^6ka*?Z)%)$Yy(c-~a#s0d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkV
znw%H_00Om1L_t(Y$L*8bdl^v_#f8bG-PLL%nhMc~q#`mQ2qBD7)FomPgc62MQMdg6
zk3DnFFeXNthkjrCko~Zp{O0Vn_M*|e%s-g7lsHRO4WY~igJFl-+ccRDsDn>t^C@+@
zx4(Z*ogcZ~Uny0@u)e--pf-`3sE>NR-c0Qve4jen-Zs0bzx2v0yz^33L*QXJ96n67
z-|vrMEC|8~Mm*0Cfcyr)IFAhP?`wTY`?t5ZJrIhyTpRdgGF}BjOnw=S3Z#aS8bOKz
z2)SI2hh{Q9MZQla-3-V`zlMK|wO1@6q@DYg<e?sq#VQh#$r!5>kiFpjXD)tV7tH|-
zs+Ce#@?tf9p@%c(J&3b~b$3yv?^<i0Wd?NOY>az?FosdP&5vMEuU5Oz<v6Z0{jh=o
zomQ*WZOAT~qKnHCa6!L6FYiCcNQh7@l3<f{sKzcYIN<z#Z+<Q&%wTg<1P9CGU@I5F
zAO{7nm(GGl$rxFfd0BMP$!D`Im_IsL#iN4e&4xICNNma=3fVzdXx0akb}J=xi%ub*
z??5Y^PBlQ;M@VO0uV|$Gl`d%HB7Jh~V&PGT0ag%FkH=9lh;qT8zW{lfrqhr*b~zs-
z5-3Jml9U9SrB6;$kZ?Mkj5>n$=YC2mx@aL6sYC-BEW?yl5z?^_b4}Y?mYDns#}ztj
z_&TfPMvVl^-a$DU3E!wMhWB5?9atqzKrRj$h&b%_kUD~kp+H#Yp(6@myWO?~!I3Hu
z=Fs5?L&$Ek1=NvFHk)oKyp$u6@HJeA0)gu%65GN}d$-s()mK`n1iAvzJBEpt;U*Lc
zp;rPv-<A4eq!>gQ2X<i-UHB{(%b7ZoaEZkr8pXlNh({e^(G{YSM(iRIu-Pm=I9ra~
vF;TQX(RmkF9*^hhiNtoF^RIlF|7m^$H0#Fus?kFU00000NkvXXu0mjfhwW63

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png b/web/pgadmin/misc/static/explain/img/ex_foreign_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..acba49c4fbfd9b871444c9e8d528deda3a37dda4
GIT binary patch
literal 1607
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
FV1m**ONRge

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_gather_motion.png b/web/pgadmin/misc/static/explain/img/ex_gather_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_group.png b/web/pgadmin/misc/static/explain/img/ex_group.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d5de31bc129a9abcf4a73e19a48739139f66271
GIT binary patch
literal 1228
zcmV;-1T*`IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=(EA5f2CVejTwfiWOA0Fgt11U#9qOVHuUhw@a(tm>9z9h#qH&>
z?c}iR<FN7S!|>?B?BK2I-mL1~tMBB$bEG!w=AY@{n&{q`?&h=R*_GtekmAse>DsC4
z<DBHxlHtsY>Di~^&Wz~SrlQAS@94Ab;k>5IWyh*@@aDkh)THLqqwL+ecBwj^wTIx!
ziQdJ9-ou35!Gh+|q2I`d<<Fq3)@|IrfaJ}di@s3r=D_ROwzcJZ<IJAp%bvUMgW9`&
zd$dE9%UpG!OlO!lj)kjcYthWj@w~q5zQ66y&+^gH^1{OI!^Q8@)brKV^vKBY$;t7`
z%kkOT_1@n1*Vpyo;rG0~?5C;bsH*3`zwW@o?yaxsud(UH#qY<+@3pq;x47%Lx$Mi#
z@y^cjz`^d+)bz*4@RF6$yGLoX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0)t6JK~zY`?UZR(+CUVB(*d`NQfXa6^~z!}1Oy^sF^V-vQ6PniB6gWpu~n<K
z*4lmj>zxTpNrZFE={Y_9;C#4z;o;8n<_!#Ueg5^if#~TvJ0ZwowOV^nZ(o1^02;Vy
zu?(WYUYl(Q4Gr7vw@^pKIx;e1MSTv3<2Jf8Iy!1WJp{L*;juA`9sTu|@d=i7GIzB*
zcbx`ea%zfYVA|z!O*g08?e>7jJHz>4miG$*&>@5fk}cqxo11erraKf0N5CtJA`kxg
zXjIZ8>{QT6K3YgD<|aru>;+DY$NeBgqlu&uVVO20Mv+K3EONld<MRSY3yFkmKo;vr
z%VLRIaMZ#t5Q{)kmQw~~SwrptJzZxz;S5eXn_Jw|;E6=MKJf88zXAbCN~R#CDCt$u
z5zCqTb(dHyv_vdKafVpTQ;S3*D}%DOp38&oS{ZE#LxT<JgH2|$1qIUUxtyxol6lbB
z@+Vln5v*J&tfe8J%N0vHeP`xj-Nh5}&d_Jg`!|S1QnN@Ci&a%El?{n)noX>UfRE$&
zSy&MSDF7*1R><$?^J*E&rLBr_%XS^%;`MqgXclXlMUhx+RjbvC5!ul?)~H8Yuxhbb
zEkWfnGVy48hc@|gu%#mXatCCbFywBqAxIKQNmeuqm0E03i*lu6bQyolvg{M)DW<3E
zG`wPq%cPn3+4J4qJzU%0fAJC@93H(o#>acL+6g{6I5>Tc4doBgcOSnwJ9~SM-(6h1
z|A0S!`uyc9{&xO-zlMMOJUluzB3ffiAkFe$9ch*S>MpJF|I(7_Iy;r$L}JbIn@VhR
z$7Es@G5cI-#kS2oefq~wwp=~2>+`>z-(ue+J2o$;LI3~&C3HntbYx+4WjbSWWnpw>
z05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppn
qF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTX@`Go)g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash.png b/web/pgadmin/misc/static/explain/img/ex_hash.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f35c76538c3a41920a7b93b94bdf97443df7aa7
GIT binary patch
literal 1169
zcmV;C1aA9@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001n
zsbo@(8Fi+|Hi*HY#9nXB?oYw#V#exs&h4h&@<F}n38~^Nv*by<=84npY=q5bYthWj
z@xa0E#>el?&hp;g_M^vPyuIzKtmw0}>eSTprp#qVzv+I_?y1pfi`DRv*YI-8?0nAc
zpxg0|)b5kj@2}wVOTX!?)@^0T>qEQdP`>AE$LX%xagvqMa+aa8;C8`}HuUky@a(s=
z<azV%$M5K~?&h-b>%{Eiu<YTk?BK2N=fUgdp10_I@9DMZ-I(RolH}8n>fNlm?Sban
zmF(iL<I#@k+^W3rgyPSQ>Ds94;H~fF!0zF_pG#z^7Z=B>b%2f~b#^4RQ$FKkW936b
z-9bUQ>wu4ZIPc`Y=+~#`)}`#;yM|jA(>_1o$cW#@hUV0yu-$XX5E0BgJmJiW-o%9F
z(xSc&4#hh=;K_*H!-U<zg67eo%w1jR)~4*;x#rQL<<Oz)+qdxO!sgSY<j$b#*|yug
zf7-fz<IJAizk%e<p5x1&-i$@K00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0!T?jK~zY`?UU<U5<wWn@pV8EQt5^jN;fSkZ!Z^txZea>#3ciXMG^ue5lb*`
zBLBL(BnC@M_{lzaKFl*S`~03W=gh1~bf??Z7>o&i8z-E`2MDn+LnITE(kU`Ph=my<
zli!=3;UOHsGB<N!4<_g`tKc-t-v&Vskw|g>!NW(76$6NJ2r~EN>D;qx<f=ddDj?lC
zzfvATB(nK=DOmtDJSQ(+N?*MuQ;UmBZ^#ftCYQ_Kk_phjJ0f{M{b7O3OfD}k^&$OP
zSQlBxWkuL!wbR&Yce&O%`5J+BAl-Rq6m~(aPN&nN7W801!-mmlLX-KU#frjNP-nB*
zbf^P6IMC>HI?ZTyx!j(ABM$Jw28;N7n`m*n{ee-CEij_l=W|)m<M#(C9G=C&reF~^
z#ik`>#q9t^g?o`5n$u2q89~HPuuHQD{TVq$evdr}9gW5IqV%<k!Qp^7d|{C&ZS{Bp
z2@Eq#a=+Jwj-`OFxEFRI^_BMcgKP@sAf3+iBOq2Pl`555t<kJ}Ti5ID_PBQove_IR
z2<Gz)Cew$9g+65OuP&+V_amCh=Zi_q93GcW`jBWYcl6^=7L(b`W$A2&nV)Qm)AC8>
zY=p)*2{p#QmtU5UXB!g~#e{J`Q!Sk0S*=#954-)4+s{4^DqlQEV)eLGs$i|rXr2#4
zu8J*k#g<F0Rt+y2&1QQT(pzk$)oRr6yvav+SrQb(rKlFlofH>!thd|kzAU`IGSb`9
zXpM~`UY7p|xp@{|V|TiBT>>)4pzcQhBc}iW03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfr*~9W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2a4e9369fa78c6fa19bf5c7814f8a586aed5565
GIT binary patch
literal 1571
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
VFfhuORjdF2002ovPDHLkV1kgxQ(pi8

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..0051f996783873a4a27db4b971135e95cac8d744
GIT binary patch
literal 1452
zcmY+CSx}P)6on&<ilC9wDWY}j1EW==VG&s^MyMc>VE_dIWsf2%OOQ>VP^3a>#ezbi
zQ$WfpN-;oW2^b&=k^l*WB?JiB_w`TyKVY?Ov`?Km=bn3K?)P@ef&wq>vfO70gTZ$B
z`}qW$(($7$%uVxaUUo4IW>yn~4)ryiwS?^8pZAsxthkhpJ|kUEnO0vS%3>*s%xPsS
z2k<7yY&!WTdW3!iaT!ycQNDmlo}HT35l7XO3C;8jz~MlDOvDm~m7Oo7J-u?muxfr@
zpW4Z+8|Svr5=g76*J7xhadTK|9F-Zn1z?W=#EU?ha)Ykg7?VO|88jh>7B!m<YlhB+
z+ihIKOFme=tgB-K^{c?37$i!dE}kC8*Y^qZlX7DhJFTT%m|NeG{<NMuQHSGZ&NB!^
z;v%k3I!Km}R=5~+lUn8L%?)9nD0kB+*n|W|{W4?{z}f(LBJsvL7vc+yV)2Gr{Z1in
zFoNuUS%J~8tXEIz)ik||2I-d8IN-v9etFrj#(`wAO|24dG_VI%MS2w#P*Oqdl0sB3
z7yho1^%ArX0Ocg0m;^P88U<b>@6$+o03~@7;%^vuMm@JJ<aWoNJuAayg^%p>Gmnsy
zuG1*Xb4;BYgOU392vO(P&!Kp6@p=_SEyk&Y-2;lk%6vCay<jj&HE-0dOV0b0N7*Ii
z>@A6~WuE;;C#7g56s?$SP%?QaR1+n-c`~kT8uNVa*2|@o?lo)=KN~N}8`2cd*w{>1
zTTgj<YDFZyg1O@D{UE}YyxV#N4ySl{7?kr`5m_T5X@ui?`P^!3EZxhC>gh@L@R+)G
zO(PhQvf3nzE#>#mKw1u{SyOTb#j{N!#xvnegJKyEY1bgl8X%hmqzq8Kx;j$0LMUJl
z<qK%_8X-v|7+3Q~)m)Q^fP}#wDqs)hYxrZTbs`|9t4#~%jZRFLv+$sL4Uo=)sui7h
z#-L;~yHiyh($Tl5Qd2dzWCRC>z`VsQjOo~s>UR}uO21G3b~5>_sl;f1pL3yp6Y(*2
zTg|QPYVFdf4n2i1ryt-~R&&EyEH~GIaAZa|e{p<ldYm><(Ym$Sy#1kbWFdS!sl4#8
zg+-&46R)?p=v<IrxKAkB`h2<TL4-|Z@SI~R;xX*W)A-68l_8JKw;U~tw3<);5q%L&
zO+WVhmV94&T?E^GFg?51+bpQUzhTmQhVhm~3Z0o+9h<ZCwR#@S#fJ;<<Zb)oT##J|
z)~QSg4M#(dpu=3@PP3E!chc@*v$C?VU<{A|;Ea$C)Mvphc3Q&ID5^bSMg}7&YYEw!
z+G=rs{cXF{PQvQnc;JJIsEn$r4gNJx`JpL<IafAY`AhBA`O0bGjYCz{Rlhy-wHk~D
ztSrl;%8Mf2s{-cqz(t1-!B^mBiO9Ppot??{A(!2P0&-5fl5P>dx>DZULg*TK_Nrt5
z-2iOB@sKY=Jc0s3a*{6G2s_h?5P1!wrHzP^j*5=&P&pEke^pz|yOz9TpDUboGQjBx
zMo!1otB@1eyV;Rc&(=*V+v==K^@N)awupf0xSm8APIR0a))g^~LD(V`iz%%B9lV<9
zNW9qx+^!S-`8sx=M=9LFn)pSWRENVZslD!B8wv}&N}w0~Q)Iq<_h*6eL%R>T<xbtB
zHVKX_vJbiD$PIhPc3|ksSjnS-lh?8Q_gnrdZaWp#?-x#cg*}W&rboNyF#et@q1F6p
z9T*>0alj*U0b~~`wx_Vx`_n~Y1o};*=weXmz#wPa!-WHU)K7MW;+k{KB^hrTOISzk
zQem*}7uv?l*3EgQg4t*IhG!(iW+b}Drze^M=7@AaA{-nMNQY2Iq`R||yR-9g<Y{*#
t(zVw9;QxR-DG5oKoc{o%+b7`Tw>#41KY&_u`GpC9`TGX?bfIFt{})E(L^%Ke

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png
new file mode 100644
index 0000000000000000000000000000000000000000..76c546a4dadd7fdae310dafcec2ecdef9649d4c5
GIT binary patch
literal 1377
zcmV-n1)lneP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006>P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZJydFPPaY
zjqWG6vC4&kzm|uj!nL^a^ZV4*@~N!lj*;EL!|c+nY?{_9i1sFWzH5(?-O$nT?A*cg
z?Z)orvhC!s?BcKR=fQ#6N$usa=Gm3y)|2GZkm}s3cd&fiz>e(VuH@B`<I<1f&W!2U
zr|sXy;?RxZ&5Pj5iRaa&`u6Vc<G$$Fr{~tC?A^KS-n!+`q2$h=>)E#4zJS}je%iZz
z<IA4fyMEfbeQRKz9{>OV0d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U00K`*
zL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U6fJ!G0)j%qd?aZR
z5fu}c7>rs-_lqPS2uKb<h(XgGuGEK33lp=Hw2Um1oV<df5)(cx$|@?VY7i|P>Kd9_
zOxilSdivP3!2P0PU|<N*L%<e9h$*WW0rh~yjKRJz!Ik=uLrmGoKm|mbnweWzTAAVs
zOBC;`SVOd!+t}LKo8n3rs9GE#S{$96U0jLO;s(*;?&0a>jav({i+z0kltEhjT>}Dx
zaQX!$jRpIKgaY-1nSww#aj8!^Gy>=})5xgkm{?PyQeSXrL>$l-)A)qMq-30aK}mc*
zDRE%Gq^6~3Wa89<QYfipK|(A$CpRx2w-)50Po)5&rLZW!xCEzPP;Ds%YatL~kR)gf
z39qtp6+&Uk08WDD=4PfK4Ju{j6{^H&F|VwwG&cpi3aCSkSS=P1TU3bB0n$=sQ*DYQ
zQd3)3UtibI*wozI)Y6L8FP3fY?MPZWI=i}idV2f%Crq3;Y4Vh*m|7qK8ivp@ZTgHE
zGiJ`3J!e`6kUM)8rXJI|^X4yD2)CuC6QpC&l-Udnb2@84dZu8w&2;gSrOTGXwbXV2
zb<9{X8N^;$3nC}CU}#yjdd=E(a4mJ+AT3RkK<xE(AaYU@hL*q$8#ir+YpL%6X=$Da
zVsEJjkrSISv~1nBJ$na9TF^p_D2MG#-nDxVY2M$pci;X4qy_TsgNF_wv=9}Q2M(cV
zAtJ$;mK->6poFAkiJr!&3jm~}-@X0I9(w=)03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfh#BXQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..ba24ed16aeb0d93bd133902b0b2218e674f84528
GIT binary patch
literal 1402
zcmV-=1%>*FP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006^P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YN)&~
znAt0h?kBgg%7uZymWQOmwYc*0`_$F)sjTIWk=?<=?9#1ln$|0b_9lA1YmbuM(9!Yi
z+`;qh#_r~_?c}iR;;-=M!GYRI?d7rN*_GwiljPHo>fEY#uzcLWj_l&D<kgYm(vRZK
zjOo~??cc`Y(2e2Ei{Q$M=hdb9_U`ZFzUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyHkc~n00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~
z0%b`=K~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4
zBxw;56%&^jj9OrRk(88VkYoTMLO_ZS1f)P}iPb`eEpWepZ2==#y2F+Fuz8<}Sz1O`
zj!9lYQAwEzpB5EWRW)^p77h(fEo~+pT|IpRY+B&4uWD#$1kppl7I=sOZ7~Mw0g0J_
zePN0#^&zJ*6=OqH5N&2|VQFP;hAZ`<cwf~9qQ%12&fdWcSGqve;t0{=<m}?=Mx+*Z
zh!zh|FK-{*T994r>*ucm(h}en7!-`tFNhEWf)M}EFrc1rGZ2VCmcb2F!XklAGmDCj
ziH$QOD!+z=MaBbdF-u5HN>0J)7nH>3n;H-HOImtHW)@B@D20-0HYCJya`W;FaBD#>
z`cw-cT8fGbN=kA11=W@^uoeO#21$Y@knk$6P$d+m4B#YaVPS3t(x6&iQK?3Z7K^H?
zDho5PtAIMxiPd5Wu|<_A9Uv{$wl!u*BDHn(4Gr~;P0cMW&8=-%{bJSb(Sf9;v#YzO
zx3{l<!o*3FCQq3<4O0swK*JF_rq7r;bLOnsbLLL(1ajxh#?)grZ~lUXi{Q4@c7b#(
zo;rttVQyC~NY7LZx0x+jx@`FhxR$zZppKa<r-0b2>Oka_Rtznx*Q{N)9<HUn2c)HW
zGKjsQ9z;%V#?TV9ant54a4ikJAT2GEK<upzAaYU*hL&yHcjW9ONefzt5#_L5DZBUV
zCC&T0_w7G$khDPFbLjA4gchQr^59_<Ekq<3v(keH50;XYEYZ^#bpZet8{@o}32r3-
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f<D*lWB>pF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb536b11b68fca6c2feda14a8011003678204d62
GIT binary patch
literal 1389
zcmV-z1(N!SP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70006~P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j^61v!$C}WqXvLRA$F!xasnm>++;Mf(vbE^o;r8$9wesx6=F+awtZLlAj_u#Z
z`u6U(vC4OVr-z4&!nL^a^ZV4*@~N!lj*;EL!|c+nZ0y{@kCNSe%Uhb(E6~yL^X<m&
z=CbYNu<YWm@aMsZ_9pG+vF6#8<<^tr(~#=is&%M_sJt)i;;!V?k>k>j;?9ie*r$Ha
zR+!l<;?RxZ&5Pj5iRaa&h3hnp?kDf#zUbJe=hmg{-MQ@Ey5-QJ<j$Y#*|yxifZM!&
z+Pi(@%bwc1e%iWyg@M18hosTq4gLTC00DGTPE!Ct=GbNc000SaNLh0L002k;002k;
zM#*bF0006~Nkl<ZNXKJfAR90;fdC^ZT9}wwSlQT_NYcW=$;Hh>nikS+;RXAJmlQ30
z`~reP!h9rY5fK#=ml%v%NcW2*9|%YeK!`!p9j?@eO$!sVl(dX2lbpPQq7oB6Ey^k?
zs%j7|9O@dHT1?tHx_bK9w7~tMVqjnh(L=x%M2IP?7y<Qw#EikdFu|4jkV8z_$Up@|
zo0^$hSX!Cl3QH94t5`#{nA_Oe*_+}@7pPhsAX*%qoLyXr)Zzxw;_l(;<&9elvWtCu
z{ggpk{9OYAgK+u<C5;99g@gk2gqeasIB}^@IWz+3G}Fka=$KejqEcUQXha;)7Ss5I
z#H3`LenClmJ}Gfvzoe$6XJq2kf>J1{WI;kKJ0~|UAGa3dqEDp&qNT7XzqkaaUr=o+
z1#2M?Vvr<g3<<BYauq^h$^cG+=H_OmAPp*I<rS*LXfdy>tTZ<Ty9%g7jaV%f5L;A;
z(gD&^Wm9d6BvMmbS6^S((Ad=6+|<&F)i0K9?(IlgIy$?$dwP2N`X@}BIBD{fshC<I
z0UCzTF>N}KoH}FXtZ5xU#_SoGdQ9icoi~30+?JY7kdD?VGZ!wL)ma15GX=wKri&IY
zS-K3arM3%1wk%%=1S@Jm?8z+{T2`)Fy=E<3OI<gJY+47>vc3+)p45b)C2+&WO`G9b
z>U%(B^A?bnt@R-G#AXaF+qUn>-bs=cv=Af8VY`xd@7YV5_jm8xf8Zc#fxPF?;ll_m
zL`CJn!zfx{3FgRAoC(IX<lw=BB_t(F^fX3Y007N*+;lfqX*B=<03~!qSaf7zbY(hY
za%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0
vW_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfuI2JN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png
new file mode 100644
index 0000000000000000000000000000000000000000..0018157f64a5ae601a2db08fb1e4a553385c33b9
GIT binary patch
literal 1417
zcmV;41$O$0P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700072P)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS6;005<^<VC*eOu*?=!s><8@RZo_dd}>F(d~=W?ycYR
znY?&XjTw-(Y}n<?2dLr@tKu52<6Xt;am(yZzUErN=VZm`lGX2_#9oB3MRKDxdY-9j
zkEX$oHuUky@a(tn?6&Xgw)5}DqQ_wB<elo{oax}2?&h<k%46^9w&~%U=--*=-Iwg+
zu&2#t@94AW-k9gxmgd-$?BK1JyLE=PNp`9`@$1Cw;I8Z5tncK%y3wPq+HrWUKHI&C
zwV8gj<9YDr!0Fto>Ds91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35
z!Gh(_ptk3H=G3I!!h_tvf!n=*<jtPB>woLpxaH2E<IJAw*tWXvf_$_?yzqpU%w6l|
zpX=qG@9DJi=+@xJn$W9g#g|0Kw56=6)Qphaadp(PwdmmC_V4Po^6bUt(yr00YTUq%
z?cc`w_U^Z_%6EXLhlh*8wYc*0`_$F)sjTIWk=?<=?9#1l?A*bRlHGmFTbkA@(9!Yp
z?Z)orvhC!s?BcKR=fQ~fChg_1=Gm3y)|2GZkm}s3b*P4@yf5tHuH@B`<I<1f&W!2U
zr+&{?nAt1h(2e2Ei{Q$M=hdZ!>okq-C-39F=-8*{)}`#-x$NG$<<Ozz&Y$bqw%ope
z+q{0-yM5!!p4z*9+PZy(fxniAqyuE0`2YX_0d!JMQvg8b*k%9#010qNS#tmY07w7;
z07w8v$!k6U00L`CL_t(Y$75h18!$3~03#_{n3!2u+1Qy#(!#;X#mz&S7Se6u1^b1U
z6fJ!G0)j%qd?aZR5fu}c7>rtAevy=vWRPS4Awock4+Nw@YKhfChAnWvfNcRISh~ZN
z`mlMQiCJ1kR*p$tK~YJW37-}fRaG^0h!zeFO)YIE9bG+r18iF0v9D@qXavziz!rFj
z0c|k`>H&$FfPG<#EA=6#F%@G&RS<1vZeeL<ZH6oLp?F`_2BO8n*3RC+3|G29)#3=z
z;^ge&>PDm%cZe1bPcLsD+**)b?Ca;R0@4!T78n$a(=Uh+1A-9$&@iB$a5E5yK$gJ`
zRKg;GPBV*&j){#kBPzd!ghj>!Z81woOiE6{=@*p5=bIW2_DfoNMrIaHEhvSOYBnUq
za&q(X3vg>eF8WjpAzF%x3rb3H`UTaNGO!i`AqGi;CXnzduTUivrVQXDXklS)2GXEf
zUQww=j24TkswxXJu&aPN)QQz%39&_$C><az)wVTeNFud$^$iX6jZMuhEzPZMSp8zv
z?$LpyrL(KMr?<DSf5OB`lO|7@It^0`BtXLvI;PJ6lGA3+nmxS}$e1$|Q;*r)dGi-6
zgxgZv1=7(rb=IOqv%6|RdZuEy&1~_KrOTGXwbXTk$kr8$fM8`Eh&`nhL(8hwYu2uV
zYpL%6k<IHtS~k>!*pr(vv;=M3w0R3$OG7V+Y}pFZvaJEcp45V&W&4huIlD;Gf)-*#
zIc#^zp1u39dcPZ&_xJ2SaPSamfxP$dks}B#L`CJHBPd#63FhcAoC(IP^w6P0r6eUw
z^fX3Y000U{;7!~3Lt6j<03~!qSaf7zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4Fh
zG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bN
XH99ab%9mBF00000NkvXXu0mjfCZzUl

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png b/web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a78fa6a1d35d8b2666ee3ffa5583db9662a67e4
GIT binary patch
literal 1490
zcmV;@1ugoCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70007fP)t-s0001q
zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP<Vd{ciPP?6mC$Bu(ag>9z`^du$M4O~
z^4{L|yuIwJtmw0}>b$+})YS8(sN_Yy=}f@sQ^M+n)bNzp@OsYdgVF7a)b6d{^O?MO
zQjHmqwrtqt%Ll095Ub)EuH#+B>v7BMPQK<^!RKVf=#tg%p~PN<utjpCHF}<@YLBMD
zk2du2$?)vA@a(ql>$dan$D+qz>g1j3<DBW>n(pSaq{?IO>bB|Oo9N$}=iQg=<FKdA
zX7A{;=-!y;+m`0ol<eTGm%DX_wn=uXJMrtp?BK5J-mLHBzq-+*uG(>Uu0GqniM5%2
zwBvd3=D_LPs_ELO=+~$1-@AITLFwA5;LD2N$cNs=h33(sjloms*QVdbhTg-3-NAz8
z&!D#FeCE`o-NJ+1z=7Mnf8@=cx$A%H+qmV<pySM*>e#lr?t*-@L%i^Wmdsu2=AY~3
zp6}_j_U_>5)wA5djnuJo)yKTSzu&2=<&>D;#mDaG==ksIwesx6?A^lL!H?z8tMcd6
z`}p$9v%Rr~oVtpQ(zUVm_Wa-A^}oUFs;%YG((&EGk@4lw_t?(XP%76v4Bcit{POSI
z-Sn%j=JV~w?&h-X<go1Gukh!=<GQ)mQY+$cP1;Eu-)lwf<+0}3mF3ox<kOJq+^Wd6
z(8ivy+*T~?;;!V?k>k>j;?9ie*r(rPJ=a7P;?RxZ&5Pj5iRaa&@3EfMdPDEyzUbJe
z=hmg{-MRG3zUrWd?4*wR<=^byy5-QJ<j$Y#*|yxifZM!&+Pi(@%bwc1e%iWy%Ued<
zo^8}&Lerv`vZEDM00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3Yd!z~0*y&T
zK~zY`V_+Z~FfxGvBPm*#m|0la*qKPu!okVK%|n_N(rw`d`-PVjEqwd}f<nT4Bxw;5
z6%&^jj9N(dizFWiNDe@VLDL<s)Q3$A6SI`Gj4YF!yn><<6Fx1<Dk`dK5G@?)8k$;6
z+B&*=`q;F<{i0%EU<lDez!pS^DXSO(^?<~T!M-rTmHLoFOxegl1w@;gnOj&|nc@md
z6z{88L$sLN*xK2f;z}2&S{xu+9G#q9T#3};2GQc~;pyd#TMM#_eSH0tL0bG>0|J9^
z`UNG81^b1B0`-KMf<QQNsZTjH0_Zf;$f)R;SW}`>UvOwd9MBfi_=LoyWSo9MNqjyj
zabUlsrln_O;?#mtD5+#YLM%HcH!mNz7UZH&r2wL(uqeN{1gBq6Z7Bt7ArNAaBxno?
zud;F#LSf1PPJ-s<W~LwwDrMyrs>En9udJ*zHwC*2s6&ldEfx@4REW|6(o$tpZHgpP
zQ(ISGU)Rvs)ZE<E(u&nDmTm6sNLo5NySjUNdi(k(Oq@7r@|3BVS|9-$hR`u>Is?Ou
znX`ak_8bO=x$`jfn9g6YaM5D8Ej67$9SlomEnT*J)~ppk=G-Y5ZZloEYW146a4of6
zAadP$28KBsX3YYzC%0f|*|=%*maT9tb=@GcX%djwHfuYGJ*f#pOW=;3yLQ91)c1hM
z=7|gpduGkr3t~@f#?Z2F|AFj-7+MZ(n>FijJ$5Z<AqI2Uky*2jcGux>*s<i}Cr+Yy
zzhyE|3qw~e4(}g7b^6R%v_Nj1GM9m2dS?xeKt6Hq{CR{HNK`URy)bLm#SR7>QF-<}
ziWX>sxio9mWn2lywB+pBvn3=YOY}5GT>t<le%ij3CAg3P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f^-`z-2eap

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_only_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..7764b74f5e5e5af1a205b2ecee9ed184727e1fe0
GIT binary patch
literal 498
zcmV<O0S*3%P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70001NP)t-s|NsAV
zbaXa|!Dg1x!H+h2o~cd1>4~}9VaDrk&F*&1?7h9crNQLey@{>X>89TD^V}-D-R=M0
zDZt<E!r<=3;qS-e@W|uv%H#0M<nj6DU(Mz6|K?xO=kn9&^VI3|*6H>3>xcgAhT-q`
z!N@d;00001bW%=J06^y0W&i*H32;bRa{vGf6951U69E94oEQKA0Ut?3K~zY`?bO>2
zf-npQ;7O;V0?Nb_&J(=<J35L^UDqjzCdSySWyu#<`)@-0>*s42VGTQwXf%l9@iclL
zNdOXeuiySypCM4!;O@y&X{wSOIUXq~XQs-}6QvYS4hKrA@L4=0l~^f9_IpA^Zz9B`
z$BhkkJMf8s@sL*VhLWpb4Q#i-2+rIQ+9-eP?noQ0lYBNH*ld8!i7ZVDly=E_-I=Ub
z0iwNRxdislWu;A#vn&>!J9Z<@Ox#J(okv2`=*=e-1+Z!K=krEr8ku~;d=B=YX>j2X
z3`^5Z>~y=h^w%!z!<XmH<A-BxricT_g}zMDF0MB}&$8Uyetc9c(E*Y`k!1ybMZHLj
oF&RsOM{v$!pYbjo``5SC8S!5pi4Z+;&j0`b07*qoM6N<$g5#I=_5c6?

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_index_scan.png b/web/pgadmin/misc/static/explain/img/ex_index_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..d44eff429fa9a3409776ea88a11421582f6f4598
GIT binary patch
literal 1298
zcmV+t1?~EYP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001q
zx!X2~!Eep(M7`;C&hL59?^VL-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUOPl=ya2odY-AJ!Q^I^(ZP>4@#=@|<6!gf$nfm8
z@9DMj?Zxiqv+?W1?BlTT>B8af_vhLx>g1j3<DBW>n(yed=-!#<+?Va;vF6#7<<^tz
z;H}o__3YxW=GvC*;jZM>lI!2C?dG!U-mLKEz|`sV%c5A@y@~4Gs_*2!)9CZ$)RE)T
zkK)jc>DsB$=kny!kmAma@8e(T+p6Kris{*?_Uni3;k?b|^7G_h=+~#{*QV{@yYAa6
z^V}-n$%)^{h~LMD=G3Ii<nj6DU*5)s%H#0o)urasqsZg%{_KYD<-qLSy2s=2#o_P&
z=3ngIy5`cN=Fy_&(V^_zxc}ZM-o%96!-L$xg5}Vm+`fRq;O^wkpX=JUz~AoV&7SJn
zw!Gc#+Pi+^%$}{*>Ep|u5BdaA00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0?A24K~zY`?UdPD5>Xh3ZKAN;Q_C#*KwubLStTT}1_fLIkws7h8A(MzWmN>>
zU-!(C!g1n>o1QNGE)F-m&yV-_9Zp~0%lzwkMX>Y7Qw$+qOC*vv#9OIU+E4Vqdp|Hp
z3=Rzsj}RlHV`Co(QA9F6K0ZNAOioUIBtA_|O$`u3pQX}aVsv_XV2tRNGV%8*U%21Q
zpI@J!9p9ai$!5?8$Q25O9A=eD<t)sqR4O&7Ns`onhN37fbRe?1IT?#6=I7@Xpj=p3
zRDo(~X=xcqonCJM%4js1F=S<>16kw|9SNjfucyFbG<8Q*i!8FN(;>v5|7N7XYFb^j
zi6er`Z;S7og+&Wmv(4_nkhL`y`2p<k=g;S-7$T&omq}6$8iRq-fYxd@JHSEHv=d8N
zUvG1Hvaqlg8+M>wZnp<RHaFW4Qm5mKHCn;Ey0JwA?RIZ>L3V^<DXv)CmdizhcYE9C
z7f0lZot<3~3#Z5L4`9gNUeAs32*`uMAWESuRxLPecC-tp*W+<QAQTEmL{lKRzaI@^
z7CvMViX9xrG34k-K%&tED%OOq2kmmXz2Ncrd?5%Q942w(_?SncEW#FR7mD@!VzDr!
zlF1~4AtxssvYSXG(yRsji?awIi*PCekvPMgV#wK9&yAg*b1CUWCX;4&;YGXf`$GYU
zq~h@lIL+qrmmrE{uCA^!xWzRCxk9m6>W1*wBNU4rq#&NWE@UBJES7PkQfWgTi$%L&
zt_y`cl#1nYRUAoYSeFiqTrPhDH>GOz219Oddv1(Jgp@!yg?4elFxgYMER{+qrCO`q
zwWpwmuU=;#Kf#5+9!!C=D6<x|Mx$}xj>y*P&1N0_`voL*$X<^uSL_qg>P)FLo0X@O
zINC)Pazz%YmdlMA+~Wvh&>fq3bh%)%n8jVI)w=6!OnAn7Y3ybGuXzBr6b33x=w6}#
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f@j>KSpWb4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_insert.png b/web/pgadmin/misc/static/explain/img/ex_insert.png
new file mode 100644
index 0000000000000000000000000000000000000000..862d837277c99e17d2b66232de79fce010752e7c
GIT binary patch
literal 1065
zcmV+^1lIeBP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003yP)t-sBmgK|
zTU$1W!3nA2a+aZzl9IuXHmaF8ou4@}wdHlD$3eX4%c58k;}QztMoPfx$EtN*#Oh<l
z>TAd7XvplT#d&Va>__Y%vc!A9!GLwm?AyJ6+r5e0zJT1nfP2yJ+`)o`(eB;DgM`!Y
z-o%97#f9F+hK$ti-^YjF$cT~E?%>IZ;mnGc+417ejN;IZ<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp;FQ>)N;I-kIv%s_fjj>EN2{-mL81y6fMq?B2TU;H~Q8obBJc?BTBM;JfPNo$TVT
z?cu!a<FM`JvG3%*?dG!X=CkkR!0_h4@94Ad>9z3a!tm+B@a(qn>%{Wy#q;gP^YF+M
z!Lnrl0004WQchC<K<3zH00009a7bBm000XU000XU0RWnu7ytkQhe<?1R7l6|m0NSt
zP!vXK&`lBB2CJaOTA)a|q(Gri2tkO1TjWv@3IQV^xBmZM;7QKm5)32GIHM2U59@)+
zO7{1iGlRkKH2+{;P|ED2o<i9y7RwoRabmSzQkSo7wrlG8!sWW5Zmt4>Pn51=F&d2)
z>IBhBy)v84HtG!NF6!#^)E1!r(pR3TJMW~P0*}+_^l{1Ycsz%>EXy;PNs=@H{0soS
zsZ1XpT4Ta-wOSp4TqzX#AZ4@ZCSYUngQhh~OI2D}smLG~3Iz$e+4NHRx>yYJuvW$`
z^<kBxN(CwX!so06?Q|;DAWA+@Hy5zZqVB&+@l8$9oxr5os0~RL2mO(muAG;k%==v=
z?{&03tR0w-WO+_F>-VJ@Oxn%n5Qd^C4wf(0IAs5IimsC4t_ET>94@Z+-*8Z+6;vrE
zDmL4OX6o)%1Th>A6*p>99W@-O6jM@*ZQMqqB9uZQMgW7>p*i#m+5guRgQ6hxVDr$y
zp&Tmg*VxiQ7K(#Tq1&Di4jVOe7K2i;IDj6{=Q^OhkHF`j9~j8~+7t{*!3X_54z}(v
zzyVU)=`^|+m|`><E<urD7#?zdpLbg!qEcy%5Em;j!C($DUayzcSJ3|AcS0pa52eUu
zI?&-*wyvxEL1XK$h+=6#I1mKB2b~PZ-9tT@h=0*v4DY{{!oxzs?R3WU6?`!;`C!nW
z0nY1jCn4^1p6^g_rK@7mS+E|rI}S1D`FTWN!TW1cBogs5lw*-d`L~U|Gby8??pJ!b
z3_1msrXytzkCh?RRq(}7X*#mo?MOj9Ce&3jKILMl+*jeyDI_uXRqokjvW~fDmd(cA
j94hm6lTUy1|Lgn&xg@`Em^A9100000NkvXXu0mjfS$;eB

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_join.png b/web/pgadmin/misc/static/explain/img/ex_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c391233c449bdc5a0d52d50f522bd3e35c649c36
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_7<@W^2*R&GEgz?Z3h8!ou#;)brTc^v=)n(b4kM)b!xs_i~n@
zTZg#3z3iu{=c=sduCM9E#_!9_@xH(9#m4Z~*7U)THuUky@a(t1!S2S#@6FEg-rn}>
z=AWyq=(DuyyuIzz)bo;+(T1sHQjHmZrCYPXrlG`Mg|bG+s&$Tqt8$|?qQ_wH?6#)N
zW$Wdh>g1j8>9p_bw(sb(^6bTgsA6`iJD#<NuG(>Uu0Heb#_r~_?d7rT;;-=M!L{Ui
z=GvC!*OTPakm}s3d$U61)sf@UkK)gb>DZ@@!Bgwrtm4p);mwQS%8BRHr0(Lq=+~y~
z-MP8zf4c61<<X(!&Y$Yow(R4uyzqqFzJS}je%iWy<IA4v<DBW?o0iO7=--*_;H~M}
zsqpE-@8-bX#f9C$g5=GfwL|Xa00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0r^QpK~zY`?UUJC(?Ar(V^BbpRmDM2l-2b%E>$$61+gq;DJ?Apx>_)8HEF30
z1qv<yI!QY65i<h=Jn+JOn3sINdveZqt{_~`MdpTR4te<yvEk;e#wM}(cFUc+BHuEx
zwXMCQQ|!8T|3SCNw~VdAT=visCNG}~>Uq@L_ZU6tfBI|y*^ohN&5&~$8Xg&a{sO&x
z)%W_19RZglNiNj$_TBprh!c_AZnuPnKYkkfd})N~g^7GY=8#tqWQ@OlJIjk*1$#W>
zgqZ+yzW-as^djR)gE?nn@}vi!H!=L<1mfjN3{M>+(=)SYCw3BX`FwNp6fe$M7+;i6
zP0Ts|#ifABKyWz}Ug1&^<UqvyH3+S)tw$+doagEfL}HdoIG#wRD2Vfr7JgcU0O^g*
zUkVLzoGXry#4@R@lB0NWs^H)F^p4ffmArN24^hdfs+Omy80GW&f-oN{gjc}IE4D=t
zwfMKBQ@nt8X1hFR1{zO;+2q*P4r#JhD$x<&c6oOh3TyF16wJRei2A*N&O&C(yQ>A*
zTTdh_V86<|uIp4UMlA~aa1gDeGGHqKGqL&y^4bu#%6@n+eE^xPqU7u<Y%fNWHY$*+
zR%<FRl1?Nyv!GOKHH{aECXMoeS}PV0|3jEwtnxul(+=~OPV9QFW{v><WVgBso0Wk8
z001R)MObuXVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000b
zbVXQnWMOn=I&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qo
IM6N<$f?exDGynhq

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_limit.png b/web/pgadmin/misc/static/explain/img/ex_limit.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc3efd59d70436349e5413f8c5d2673200cd74f0
GIT binary patch
literal 1237
zcmV;`1S<Q9P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!Eep(M7`;C&hL59?^MF-hScz8$n0v#>~G5Jmf7)p&h3KH?WW%Hj@0gw*6*+3
z^985jO~L6KuH#$9>te_2IJf0czUNiI=UKt$YscuhV`Hfo7w~d&?QwDGaB$~uaOH1r
z;cjl-ZEe|XY}0FNhN)yyjTym@HuUkx@a(tm>$dan$M5O1^X<m+?8WZpv+d@x?d7rY
z>%{Ehuk7Kj?BK2N=)$7LVC&_c>f@a6>b2?Mn&;e?=i8R-<FMt|ljYTt>))-W%w_1_
zndaG*<kXSm(~#=js_*Et=-rs+*p%bakE+va@8-bo<iD=jaL1~3vEFs<<gn}Btm)gT
z>Ds97;k~rudA8?#xaxo6&Wz#Ai|N>>x$A)A(2n5BiRjm+=hmj@)urv<yUl57?A^NN
z(xc|lqU_wb-^hpG#)jt9q}{@U-NAz8(4pSMh1|e_-NS_2zJTP;pYP?s>)N;G)1&6m
zq2$e;<IJAw*|yrdedEiX37wJX00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*pySK~zY`?UU_O5>XV!X``@E)R3}Nf|O*B$m)u`ByS4@xQIz9FPadbCWy#_
z5QxIR-d(t0;9^epp=NwO+}WKuzd83g&$%MegZ^zjB6tlvg%IMgSS)@*Je5c!L&VUt
z=h9(fctj=}B}T`_$6pYFh*&O{i-`$^Lh+J#H90vcB}QJqnUWD>)6>#%;<hdmlBqj%
znNi+NGuMdnZJ$&sm0G1%znh(%otvA}Xf*GU=I537>Y^pJA`vX;bh-upT-1Y}qNpWU
zrdJHBuxd1!Er6>!B0rD_(XFkm>CkDl+JLg#?KIF1r;`Dr%kB06uIeHmuaQNo)k-16
z>!rcq^!bcncKg>^z*Rzst%U@<-VFmVK3~uTX8-!;P2>~V_R@9{;9VHHF1A>f4FRs|
zBA?OE7vMGU#8sVnk=rDx1^uFp(!)}Krk7!LhhZ#WF?+&W5Q;>jF~C)w9^1QnUrFAD
zodO!W2)fMJMPxr7PXMm!blKhehPqHzyFCE(hQql7Owi>vLwGZpj6gIVPo)7@b#KOh
z^I4$5t~i`N223uuKMZVge?JO|R4R1<xT-gO?1Jfc5lbH&9`&dHC!2h8X_3C)(ET>K
zCEkTm$i;CclRW{Uhm5oEtZyb+ez5U09OO<<^N=|!6gXkT=yIVYXK@xOl*Qp`v6z8u
zp;Q`xnA|;C<PITUES_h9E0xL>VZ`DIvp*md{nbSlxu|lLN>vc~?IsI!LGL0Gf><J*
zK7ylszIF^J7aUiGYNdX8C48}77w26U^+u!76h_)hp3ddaU~A`Rg$v-yWrSP~M%qh`
zvMAR0EYM&p<#MBb8`9%~Yq_ZNF0Pu*=GEVt*n|Fa{RGa817HRj*Pj3Y03~!qSaf7z
zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6W
zZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjf-+GHC

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_lock_rows.png b/web/pgadmin/misc/static/explain/img/ex_lock_rows.png
new file mode 100644
index 0000000000000000000000000000000000000000..41c1148bb185c87898b3fddaa76be36a15703336
GIT binary patch
literal 1520
zcmV<M1rPd(P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700078P)t-s1prKL
zYin<9ZE<dHb#ijIWKo23T)1UXxMxwgX;FK4c)Ds)x@%E3h{3vUQGR@Ux^Yo}etx`j
zQG<Yhyme89f`YwxQHX|yzI;)Nh={*{QH+X;38~`0f>Di(jE;<qz=Tnbj*fDcp^%S{
zz=%=8h*6S~l9Q8@!H+hYm3+dEQI(aIm6n#4mzS5An8cD%#F$YswdI<go|~SYb*9Ih
zp`pT_QO2H8o};6lq@>5AQ9-=u%c5Axr%}nMQA)t+%BxY=s7}hQQOB=S%&$?Wv9Zju
zQmC`DUBv4BtpTdFwW_wZW5(*uwpDA#=xE67&bnBwy1LG~S+BgjZp-YjzP@$M?Ap72
zw8FyMy@}(!bKJgw|GhAK(eJjz#N5GxgwgKZ!-Is=@ZQ9P-o}QE)b8KMhu_GEk=5?t
z$%*02ikI2(<IA4n&Wz*Cp5)D*;?Rxc&Yz&$@#E5u<<Ozz(~#!Tq2|$|<kXSo(xT+m
zlIGK+<<^tt)THOtrLN%f=Gm0!*QV&#r{>z0>Dj2~+?VOvsp{Fb>D#L7+PCQ5nd;rD
z?A*BN;F{~+tn%Ha?A^NS->vfAr|jOk^xmoL;H~Q8ob=zR?BTBM;JfPNo$TVT?cu!i
z;i~N8u=L}t?d7rb<gf4KzU}6+?&h=a<-qpkvhe1>`Q@?i=(G0cv-an-@9DMq=d<wX
z!uaU6@a(qs>bm&qxbf@6{pz;(?7H&p#rf^N`R>2@?#1))$p7!Z`SHN{@xlA?!~gNV
z|MAlO^v(bC)c^F+|MtiK_R0VD+W+?3|M<<SyU{8D0004WQchC<K<3zH00009a7bBm
z000XU000XU0RWnu7ytkRE=fc|R7l6|lj&CyQ5eRxeH+_N%WSus*<!m=*`Bu8B8iHI
zC~mlcpojwm1gMN_hN04AG^HkyMp;_fzWiC;xpx#ij-Jk`bLxZdhi7KancsQe=eh3<
z3Yw{ZPzMOMrc^$J5DEl>R3a-OF)@e8xgr!66UABC*%d@ZUSZ)af)^1)L_`RP1gMF`
zrP$b5A(0C4Y$7i?Sy)K?L6_Y-qqanb<M+*I<|&mUcAZbVnRe~SO7sCn1_lO3#?<5S
z41pnrVSK<x)3g_0A|~ZR%)0Q%xVS|K8R+b6_i*ZIY;5cUjNNMO2DH(rb8yJniyPR-
z(tX=Gq#Ysc7_l>ev0AM(=rZcYkf@Xxj4t0ln?*eB=x3yTb~{A6t+$OdaO!GmS~0Te
z24{TE!C>SC%|Gtq5pvPx1ztyMQx8I}?^v_?^qCXK;t$OVo);YybP}+Yfi!)xvdGo<
zqo2Nf{PF$s@L+%cR@fzzEilx_FhjuC-A(&|*XeNdfF6pX+^Eatcca5>4G#A9K3UHq
zNl8hW0WP{&7u8LGQYjQRgq(YWkWm(S7&eYDb~_H%=md_M>PiYw3PmYKPQCUc4?_J2
z3YJE}wpOZC6sRjLm0J*U{FNWMzhd0WWEXCm#o|Ip{0l#FH{`bxJN%qQMiFvnG3?@_
zyPRH5ds>^|S-9(LHaBpY&8802W$)8xFF$<!`R#4*z2@cx5W(|LbRmZ>%tZzHdW7tW
z-nMaVSm>%{Axjp{p9>MEP6rnQE2UB?>VQoymz#m9AYY~jd{+!b+Qcai)*>$|G6923
zCL{R~I6w86Q`UtGx-c0!K!=_rTX>P90f}WyPLIC}Mb){v)4|$d(Cgbki&j(Bz&{wC
zAMXHG=pvT{8mUC0=11^i1R{D_u2c)EBogry2#z!mA#<hDDxj8##Y%p}ZLyeL6TGx&
zG*$JWUaeHt|Fy(iCIdVLtZgKzZ2=8xwHkI&mY3J^4~7>5D=9@T#H@>Qkw{d=j~MdV
z#lWgX!TOOBenbyXLCdK|!XiqsSX2(mrbgOGEu0@~po`j)l9JkK5=)=%R?gJ_Sib;}
WuicyymX%Qe0000<MNUMnLSTYGrAb2o

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_materialize.png b/web/pgadmin/misc/static/explain/img/ex_materialize.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd0bb90dd2ef1cc1baf7f932817d1b6bdf463b
GIT binary patch
literal 1221
zcmV;$1UmbPP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001^
zyUc^E$aSX2Hi*HV&EAa1-IK}Ps?y-H)ZnGh-mTK#zS!b$%<V(H=}o`rQ^M+n)bNzp
z@OsYdhtuw@-}7s{*>k?y5Ub*3#pqna>5|p&pRKHao1$`-p~$a;;^52R-owF<HuUhw
z@a(tV+qd1?w&2~u>*k;8<eu;8wd&)Y-PyJ2;hXR1v*6sp^6kaUy`#sfb>G{;;oZva
z=CbkX#N5}k+|{$*+Q8@Bm*&}&?BlTA*tOf#vFYHN=--*_;H}=+zU0-B;M>TCsbo@(
z8Sv)7tJG}W*1h1`$FJLRv*CB}=)&sVtLfaT?&7`L(XZLhuG!A5qQzgO%Ve<JbGPSw
zxa)u8(U0QJjOp2^+S0Jx)VsRuf#J-G=+~#%%&gqgy5P!*=hmj**v0JKy4%va+tIk@
z)THLqqu0r)-^hpF#f9C$g67eo*vzZg%d6JMsM^oB<j$bnzJT7<!|U0$+RnGy&9~&u
zp5x4(<IA4a#irW2ecQc%&=gc%00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0(?nCK~zY`?b6p*8bK5XU_ww#jGAI1rVXt0A|?yUN>Pg78bgs7LkqpPC|yAQ
z^~}hc;|y@}z&Sa2@IKs^*>CRt?k<n_Q2(eN@lkOdJ9#WX#r69K{Ds9u;S&GJ)1~ES
ze2fTIR@a`t5WIY~zW!Q(k;OL~8$t+$3(Evq6^TTff|a$mo9_g3PI><U71uHH@iqn5
z?;ilML?RJ$9V671R4U!#Iz}WinM{JZTtN`2QmK@pu2N4Wa``NxQfX!ot#$^{Xmr~+
z;j$<c`Wac&>g^qjd;+Mrj*-t4Dy|b#6bd*+Pt%5pDQfi=wPQ3IcZn=8g&xp*CKE$u
z(db|b<Nm(cf)j<-YNY{fGTBI?)*T!gAu*fn1hHE8Xuz0kHU~)#4k59a%}#=dX_Ugi
zI2^7!a^yZjWSnBxZnt~>x-s`LGP5^kU>L>)TrA6;0C@jxeL01O+O=3LUn!5*>+|C;
z#@J8_HgFbX0nh#I{QUU%`{~6GH;+r<4TZxIl0>5{V9$cV*q<pEKhYGs)9Lh5zHmIA
zz=<Oo2tX1{CR2Cu-AEu33i+r=Je|(qWSk-!OQo{J#ynmhOyN&tGP$|`u@jb!WkEKd
zFJy^N>4`)VekxZimGCKCihQ|TE)WE!sAQ;OwN|U+B$oVjnFob(`Km#XM7mNfQuSJ`
z*2GEb@)F)(xVoAknM$=<f~3_V2ujfg?M9=~CJ0PX&rzLbv)P$@W7$Fh-rw$adlOUY
zb$Ea0x<A0Da4EXO;jl-LdJCoK-`v~`aMFOJ3wl#Xvo#uBQ-i4lK0K9-M)2V&WSpYi
z>rG_o^ihh=U@(|_V-NMe)o+jt(ePu5AK?H103~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfVJ3t*

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge.png b/web/pgadmin/misc/static/explain/img/ex_merge.png
new file mode 100644
index 0000000000000000000000000000000000000000..3fd8299fdcb72604d0c97816d38ee25ec51699ff
GIT binary patch
literal 1127
zcmV-t1ep7YP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001z
zmC<Hv(ap~CyuIwd!0pk|^3&Aw$H?!;$neU_@!Q+=*4Opn;rFJf=Bcaa!NTsv#qYJY
z>$$q@!^H2!#_)!zWKxY8p~PNwrpGpj!Eep(LA>Zn!0CI@?}XFvW5()e$n0**?3dZ`
zpxg0;(e8}Y?vd5*uHf?tsp4J4>N2(Eb<ON+$LOQSVRDwC!H+ic@W}A&w(se*^X<m&
z=CbYOvGMD~>g1j2;F{;$m*?A-?BlSe%w_81oao+}=Gm3(;;-o4nC97(<=2z!;H{?6
zXYAmv@aV$Fs&%W?Z0p~x>fEaD<-p_8kK@pe>DsAbl`meHCBewL<kgaJokg0;U3jiO
zp0$U%?SbUekm1dYi>qviyiJ|ZV|uVayzqqY<iF|Ir|#mtzsI+$-f_6=fAHqO=+~#`
z)}`&=yWz}=;K+#I$A{+Bq~67a=F_9#$%)>?h26u0=Fy_;-MZ$|qU_wb=Fy?$&!Owu
zx7@#g+r59}&Y<1Fg5%7dI?;o_00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0v<_3K~zY`?bGX5(r_3DaGDn~EzQc+f-=ht?ef4F_ZT1uwF%R(Lb^=WWfKN0
zFq@$K>;1-ob697`-_$p~@OkmP`<&<Z{qD@+xE#k7!Wh%W5n^n7Vq%hTUY)u&O-wtT
zGuH_$8NV?*J4f7{pT9LmOy0gTHS@oavu8O+V;7hOa^b9myh~n)+@p*!Z5*NQyWH*v
z)WgL`OOL51%THIHQCQ;gcsy=ub#2Y{oLXL8e6fNj_O84fA+P%HH*FjNug~Z6f|sUg
zKlm9Y5Ckmog+d`3=x{j9027HuW0vIgKO}AtmSrFiO(c>wNIZ@R!?LLW#1iZ2j3wDH
zWf`OhpcKJmHj~4WH(-ouTamY;SNt@?_#qgJC3BGDIDQkbgke(=lwv)NQgHcvL9ilQ
z8{6+3!+!vnR0I{9Os9(+@P&dk*?IrrBQj*kFsRrlDmGgz?(&F~%a-KR=PzHkNs=sy
z$c&`egFQj0NLJ(<GT(ocN}?!|HXB1`XA4~s`&V+=4EGZ@c|njsl4WJz>dLS<v7aT=
zPa%}c)e6WpRjr>w290sM`GP^JRT&hu(P*AR2H%Cx8^sD$O;e!W_|<CT$uNb2QXDjY
zw-2$zW@Dx+l2DN)*ss@{2XOdD*N^ZIt7uwnm_qM#y2p4TYc*7Cv(-A$5$Se&c%rB(
zDz@D^>F99WwM7OgbR)%4uh+w8IY(ocV>y2TWnrZACmaDB0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1n&OPyYY_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9aa51fce9d775f169f70d115761e9817541c48
GIT binary patch
literal 1599
zcmV-F2Eh4=P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700093P)t-s0001`
zsN`mA(ag>9yuR$dzwN)l?a<Nk)710F$nVI>@XO2b+1mBo-1XAa^VHPz)z<Xk;rG0~
z?53#ZsH*3xtmnbP?#0FLw6*HCx9hpO?9I;d(b4k5#P7w%@W;sT*Vpx#ym(TL8KJ~p
ziMiW0h{0H#%Wuu@MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!a!0BPe>vqlTrrz>E
zyy*w1;t;Fi8m{AA#p`j)>`uPsTEXXJ#psgN?-HxxEVJZDyyl71?xMzDdY-9bilcOk
z)uYE@!H+id@yYP)xAgGH^YF;;>bCFcweRV)^X|sL!S2S#@7~__q{(9I=AY~3p6~0n
z@9MSb;+yH=o9W=1?&q_stmw0}>b$+})YS8(%Vg^0o#@}0=-!#`=CY>EX7KE`>f@d2
z;+*g3v*_KJ=iHa=<glpEX!GsH^6bU!=CbYOvF+oq@#@2=(rVkiiM5%2tJQ4t?#J=#
z#O&j*?BcHM;H~iI!M}zvzl?*mVn4J?6S!qS$(4q1X1J}_Z|L5b?BcKG*OTSdlH}Bp
z<ja`1U>nIz8rM-G;Z!G=R8sZm;b)sQqtRwwm?i4*<LBF!=Gv9y(~#rRkI$7};#4UA
z|Nq*iihHO~n#x^xu0Fcaqq*#W=GvC#*_Gwkl<VKE<I<4h(T?KKjlO6w!*D~Oz=4Ol
zOq|YPdayvc?t;7TgY4n1@8-bj+^Xr@s_ELPv`iG|@85{MPM*<ZtKM<A>wmiJfxPg9
zwO1DE-K^@}tMBB$?&H1b*{A5&r|8$F(UxcH-mK!!jp^8@;LC~N$cW#^hv34I>fNg1
z&Wz#BjNizI-^PaC#f9e6qql^BuwFj0N)yD8gyGDK=hda&#fIL*h26u1=Fy_))~4;>
zyX@Y&=Fy?++qmuEyyn!T=F_C?-MZ}DxaH8H<<Ftz&Y<hsx98NP-o%95zJS}kf8@=d
z-NJ+2!Gh$@pxeBD+Pi+^%%0r9f!x1=+PZz?%bw)UpX%ARsGOc$00001bW%=J06^y0
zW&i*H0b)x>M4bk^^05E_010qNS#tmY07w7;07w8v$!k6U00L=AL_t(Y$75g^1&mA}
zfI<?|!o<wN%Er#b!O6wV!^F$SFCfT7P>YbTh^QEoxP+vXG?R?1oV>zd(L%ak$OthC
zfTEHjsalkjl_=7pq6z{KQVqf;q(xO-LsLszRYzA(-#}H}(8$<Cm7o?=Gjj_|RV!;7
zTRT-#dk04+!di&4#TiM93)uY-(iI$8ZrHTABWdyQ^z!oZ_VDrb^AGS~4h)hG#+D?M
zLXfq1g@%TOdqhM=MaOsu#m2=aVAYa{q9rUWDZ;})Iyog3kCrrKE$K-anf@MG$=Nx%
zc(mjpYw?Nj^UwDvC@d;2!Q+=wBrRngAOIoDaVBr23S?U<vtm-KJW8r-YU}D78k?G1
zTCr(qL)H@0o}E+d(b3t}-P7CGKcT;G;v`Hhlc!9b#sJF$9;rFgXLNYfcFmkMyKl~%
zxpU|A&BN3(f5E~<Ks`!}L3%uLmn>aY>#=;r%2liT=YaI|uff!^cHR07Q<apIHf{py
z@hIuoTwCX{W$U)>O%vvV^i0@+sb%NdUAy<}-M4Ym{{2eGA$FjDE=UhHTY!4zPX^io
z)B&`mZu!AOhdquQJ$C#=AIK|M{Q?WP{b0YGoVDuIc8}9%&YnAuJH(&>8e$htUA%PK
z<M`z(SFhDyzj3qiRx1Mm;IZw}?K8(c?%ch1{{ikahN|Vr>0=Kcc|5-M;K@_mX$%Dr
zm&Q<Sd3NXdi<cg+UcY(!j!0WP-e3K2|B1(&kDoq$Ce|<4?tl6E+T+`&?>~MLtL4Gh
zU%%gaeE$C9&tGD-Jbm-_-#d^0KmY#yPrDEs1=s-qCgT-R!|=q?0000bbVXQnWMOn=
zI%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4Wjbwd
xWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1n5vrF{SZ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_append.png b/web/pgadmin/misc/static/explain/img/ex_merge_append.png
new file mode 100644
index 0000000000000000000000000000000000000000..12fc55d76f504140935d5fa422f9a5075431bbc3
GIT binary patch
literal 980
zcmV;_11tQAP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005JP)t-s00000
z003rd(Nc{WHi*FosNxW-;$)T3a-%gGuH(UvHg>8zdY-9xu0FMyetNM%gs???v_por
zNkF{mMZW2fwrozm=1joprKsemspnI|>RQ3)jlolwyLFkoc&e=DiMiWd#p`6n=&rBn
zp~POI$6%JsU2)6oq{?G&&F-hoW_r%-+rEF>y@|ZN?A*VB+`xgozwN%i?cKqGgVF8X
z!h^rT?cT(M!ou!_)bQTLg~G${i`4Gl$A*&C@5RRNy3wP>#_+D%ag^Bb;mnHS&yCB>
z@#M{(<j$YW&GF^VpySbw<<Fqw(vQy1^0ec5<kOJl(V^tjk>=8)<kgbV(emchr03M6
zw&#51*OTYfrLEud)710S)b!}rrs&tF=Gv9%*r(Rk^ttPQ>Dj2~+?VOvsp{Fb*xB^D
z?t<yus_NXT=--*^+qmiAn!NCY>)x#C;hXH;y6fMq?B2TS;+*W@t?c2h>g1i+<;(2i
zukGQy?BlQO<FM`Hu;AeL?&7`e<gxDKzU}3)?&h-Z<-hOd!0+g@@9DJg=)v&l!td&~
z@9Vbl>cjBtw(#t?^6bU)?Z)%($Mo^Z1Vq=200001bW%=J06^y0W&i*H32;bRa{vGf
z6951U69E94oEQKA0kKI$K~zY`?UPkg!$1^;v)KUULUEU1#ogWAouV!7?ykih{xC_%
z08P8uzzo9+=i$EWd~@#Ev&%65`jNspQS#J=aPdC=LHS%|Vph=rrxEdXaCDB~V*P?6
zOSoTiagtKVQ(H*3K0T*!kezPn9y(&R*|RBuD5{jL$;c}oWwnJQBj0x+hE!(ZG4|b2
z56J|uqU6ai*kJH@XNdN;E;@msASoyeeLdlI2K-Hg!O>Vfiyb6RLfk-<JSpwt1POuH
z$A{Y&cse`Wy#}=#6BO#@!i&hmHd?uav*Et3WuQFNGM5Uyz`)%Gnzz7yUq|yS4PkhF
ztFRheEux77*lcU89;dqKTir#MXUoaK4P4Akj_d*D?}vtngvyQCK3LpK>BaV2;4y5!
zFcp^>3gRlIkf(HCo=X%&Q8JG`mtuU?vnWc&GXk+=|5<z>)+K-bO1R(MTt0_mbAQjO
zbuYw{VGA{*NiY=`WVb62QpvD}s*xFtx!GwA)=08cuRN`gIaG!Ep))fzsaD}aS{Y79
zOvZIsXsWB7QXr(2iS{Zh6GdK`j1X5QioEi_{zPxhzn!H%4w>%&0000<MNUMnLSTYP
CgEgxF

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ce4839e02fd3f17b5fd8e358ff204046c38d26f
GIT binary patch
literal 1344
zcmY+CSx{347==TqEn;1Yb)kzRwvO5s8xTS1=pdpEBQS_s6{;)(R?#BJSj4Jq3RJBX
zQDkYPh!_@AR0IrxkdTEC5=cT4vWF~}EH}AX?oB|$Ort*a;rwUL%=w=FQ<a$T<6{5M
z{81>>;#h1<lGj7u%Fow(H%%9op-|{MiE#(_c;jQdE<1-e#<7zI!9lWxOtDPx9D+$F
za|9Y;+1PBmM2g7e2w&iwoP>oUczW7R8US1I0KR>uy>o^_wbJQO_e)bxulW@b<ZvB4
zo@1B>(HS=8h)pa;>PUr2w*vp69j|&N3$LV{>k!lrt7&peF2@=_WK3t6FL1zCrK1;c
zH7nuAD!5GzQ;lwx*-Zivior!Ux<t@i6v-ISq51BG4od>61lETNxJ+QH5ZZ2v>;w(c
zr$;)~&Sx6uh{@f|NG-y(<=?}pne`Kl{^u_ZM7rq_Go5lf9JaG;VDH?l&W*_Qz^qE`
z(rDcPFl)8GZeeAZfrp4=T%kmSLZ?ImE0ivyaqclYvz3#LAOEF|e_A$BCFnXQ9V7Dv
z_73408`uMxdz=u{jY#L*+Na`NlkRVmw$Z4rw*oI<I~TTdU}((ckeqDT`iPf(toCb@
zT??6djMEJURo#rD7SwjRVJU3mIH578m5tcNm+*=8qsIyfJBhqUufSr+@WP;cFeAGU
zOx1wur*5ajX&H5xSqLO*8Y-zE#x-)&i@KtYKUhz+mi9wsb~6oB6U@p^NJpMUrV*>q
zZCA}es>*=`951s&nb&8&N(4(twsIRVWR$lXrFet*iAB}#cBo*>Bn%3iW`PS*7^Q7$
z_8+Q|3MKs-z-xxVNk~tFw8K^n)nS-0h+9>R>&oGB1*KF!-sCj%Aq~|!O|hzn95ds<
zWQ&4YCLb!14P4c6a1JBSs-{>}Ll!034sgw~K0UWdJBBk&^&l3R0~ojKxv*KPW#gp8
z%b<J^8ZM=HN9j|LlyCqQ1x#go!@_geqvySjeW$&woSohR5F4}WAeMO|ZS{hWZ^o@H
zjt<~cX=Bt;2A4)*(K*93HuL+nBQc3L{_3j4ufE$?-cl2G_hx4!iJHcXCZ>+~h4>dY
zSFdYJuBo}Vk>A|xk3MZ$+vF3p@YH_cBwxfkBosto!jdAwL$+=?ya7+A@<*k6g(9w!
zvR@G^meB8Yt<;>!+s)T3aowoc;8%FDtaxd7@E|tX<MBnKJ@e?Y(o*z}vfa_c)PZNG
zs-i>z%K`%f)vG>Tv0{ZT@XO_&ueyC`AKI845G$^ZA*NQ}>28@ViTY+6jXxB0|Ihe+
zAGQbA9hF3-<mczxr>ZM7wVqPyI&zbynjb2^vgu!ZXGXE!his~s5{t;!8Lhxdd2+H$
z`a^M1P2{d350QAO=#A%f!-lxV@)Wji(}5@Phm(@(Lzd&2xj)T=+t1GfFXQUTsNU3-
z3CFt&S^TUm*_%@dF`1P<NcVl;vpy!2^m&H9U6|t$5mLzMU;Ubr=F|HHSR3Eadqx5D
zUS@KS_2<1=;NKL~a}pYy|H!jsP2}&ojk|&;3b$YI&{zkqJQ;7?@f+?)@T$!?Vu1u2
z6*|R)<?x~pmIY>P?D=^xPs_+6o5h4r&fZYnYKX{H+WIe;MNe~RudnS)#<~{;28H`~
zp;7y0eV6*_*%98?uP@khq#!fBAS?1jewNozp_mX%a7bt{Cgfl!CNgYGWLVg@n5~f*
t%(lAqYyJnE&CNWOeepej+5QfA+m6J9zXwKZ4|RG0RP3IF7(#Sf>3<opDS-e0

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested.png b/web/pgadmin/misc/static/explain/img/ex_nested.png
new file mode 100644
index 0000000000000000000000000000000000000000..15c47316d5d4163d97b95cda99b2e3a2049341c5
GIT binary patch
literal 1108
zcmV-a1grarP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002N
z!qAqP(SL){b9d2ZYtf>m<h8fzyT0th#P7ht?ZCnA!NTss!|uez@5RRN#>el+$nVI>
z@W{&W%FFS~%<;_4^3Bfj&d%}9&+^dF^3v1u)YS9U)%4fb^{cMtuCeH+s^-7I?YX<_
zv9sxtmEUxE)|i{&!NTpPsL;d2(7e9v+1d5IzwF1wx2vUWos>nDi!Y6V8N<SK)!C8X
z=A7p9r{wXW<M5#4@SfuCp5pDD;q08@?3>`~o8Rb}-shO$=$_f!mC@3Tzr1kL*o)26
zebU;8+TxSc+KR}^cFoU%&Cc<&uw0mqEU~Ov&d-F$$aBxniISDkwXlcC#=6<xjNRgy
z+TW7e-jUhekLvTc$jR@iqGZ|JkFBog*xQf2zwOuBjJLS!xVh`s*o@`wtmEsW*yMoL
z*Nf}&z3K0|)z^yM=aJRciPhDL-sP6m)QFdjE7;qU%*}nTs8!L=iO9rx!oF?G$b{0>
zhS%DTytZrH;g{Fej@H)nL!K(600001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0t-n*K~zY`?bYj3+CUHha0E^Sm0C-!f`UTD2bF{cA89RMMd}k&#45_8X`@6D
zMDcCwzpmmq!zFho^0nPBncU28_IB=WSEKnK57DI6=@B#ZI&Bj!nhh;RQ)^pWyU}cZ
z)A6?RoyGFL>qB>s)oQibdVBlq-DU@7w9Wkk{RS54bh=CqZ4J>G20jkDS^IJi4ZF;`
z8lrbO21iC&#g2{)nR@g!3&`nlGsS~(TRx#$;_)EO3xVtkGWPWA<#-<`!pC#oDzTG@
zm-7e4Cnl!^LHPV-`fD&4425UD%|;##g>e44==XU{gcqWVOA$^@mY1u)d>}fv62tcu
zi?6P&^D2@+DDn7(31MX;iI<(+h;RM?6?qPr6k<u`VluY5#Uwyj-DZ&~GD!iwmYp3E
zNvTN!?e11h8X!#~86`=e_H5Spl!p|M$tel4d9vA`ebie9IZ>Sj{rZg<Sws=27lu-c
zC{alo_Vx${Bq~Y6em=jiHXz9~0tdl-K6I!$8ITL~EO2y0s0Io}dif9vlTN=_Vvz7L
ziImF>GJZ_B{3m6ZPJE}8xcZ5m`Oke+!p|<Q#uX3w<m}?|il?`W$kJNndTTqC$=!&O
zB+G?jsp7x9xaAm4DY72;gNf4L^Q&7%Q+ieFtE0i*;iR56euwqsx`u@l8x{?Wqy`3G
z1LL)UA^xw%J#C$lG-cRqEC2uiC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$c
zGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLe
aHY+eSIxsNGmsP9)0000<MNUMnLSTY)!c1@g

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1c0763337617f9ffe8cf13ba0f47f4cbc228c5e
GIT binary patch
literal 1741
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png b/web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0e8a17d409343ea937e43ebb76e8ebce3cbee49
GIT binary patch
literal 1679
zcmV;A25|X_P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700093P)t-s0002$
z>-np$=8KNpd41PrYtf>m<hQu$y}#_m#_z+#@5IIL$H?!<$?(d{@XO5c%+2!7&hpRD
z^3l=q)6?_S*7VKI@x#RKtF7j*vgoL)=DE4*zQ66bx$D2c?1{PCHi*G+tJ9jp;KbbR
zwYTcBwCR$T-*kA@b$Qm9o8iF0?YO$@#KrD!&F)RW>0!p}cFpXj-txER_NL$Tu;TT)
z=JnF<_`1C7tgh$6#O>_u`M<#JK)mS^tKuxP<Vd{ciPP?%-Smjn@Rix|quuhv>Gr_E
z?!CY5b9mKtde+Ix@Y&h**4Fgb*Y&BOdclu2o|AU6wdkn3<nQj^z`B8`pHQ2SHI0E8
z^zq5??6>9S_}}36;^X(`=J@XJ`oh9?)!UTb=bh&Crswpj)Yy>1!FB8ApWWW|)z$Oa
z+4SAs_UY;Q)7gv8)qvC7hU4;`;_#Zx(|Xa?iNU~h&Cc=8&+*mTk<Hb7)Y^@-uw0{@
zKg!X0)76cosO04HoYBpr<>Je;uUp^il;Z25=JcbPk1g@=<KO9(;OUv$y@|-HgVNLS
z<@2KC@u1)6nBM1@<np22<(BL8x838F@a(p{wRqd%liupom%DYk(WC3-p6cYC@9DI{
z!|tuG>BYtG+TW6au0nIAH<`S6@9Vbj>b39av-9o7yuR$&-H@lxXLhSQxVh}u+>fx`
zbW)8O?&h-X<+1GIukh!=*V>J;;C6bkLFU<&<<^tr(~#=is^sgb<LRi^*o=(8QsmW<
z<I<1f&W!2Ur{U?K)!uy6+<M;RoYvQh>)x#5(2e2Ei{Q$M=hmg^@4C|3c;@M~)zyjb
z<G$w9r0m|h*x!oR+=$fFiPY1F=Fy_%(4pkcpX=GS*xQZM(uUl=fZM!&+Pi(@%bwZY
zlbDSxmyIjb)Qj4?e%iWy$HsK9saCJ2RmH+^(9MX=%ZJ0jZpp@epqD$y#d^WLYs<)l
z>e#luw`{_{ZP3qu&(3_myJ*M5c)Yf2*42*6#(>q;^w`+-+<e(}00001bW%=J06^y0
zW&i*H32;bRa{vGUNB{r;NB~C3Yd!z~0>(*1K~zY`V_+Bsj7-cdtSEqmnTZj<4mNfU
zPA+av9$r3v0YM>Q5m7O53A}ok*d(PSrIEGp$jHh`Fu@hbD}aEa5(p@RMVM8jRMpgx
z-KC+arLDsZ*P@`Sr*B}WU}S7!YNiAhVKKK*v$R6C*UD1UT95^y#m3go-a*09$=Ssf
zqJ`DX-NOpityZ31-mG|RVRd(BWk3V0KE4PsKYtJi2n2y3s1_NpbZ|&$Sa^77NHDq<
z|A@%Q$O!+a=$P2JKx8e!A@K=`Ny#axAU&vBBGb~+BK^}dGPAN#w1mXx<mTlU6c!bS
z5Y|#s3X%>jE3c@msw}Fm2_>kdmX);@NQEcYRn|AuH#Rkg6Z8udgtz3kwlTD|ws)Xw
z=>&&Z7dXVaQMB~*hJ&cSe&TJJ0MXJvF`Q_>OqvANGI26iEmM%R)Uxt=f%Hrb51$Is
zGI<(Y%k&vDp;~6mo`cX*%Q|=7y!l{zAnu#C0Ip@m!bOY0ep#|~*>Z$mSVdOMn`bQw
zcSiVvm2fRH7p+>o7&F9xK3Sv9ie$^mwFti~UcGKT(3a&JHg4LCC5^%SvUUr?mepIg
z0d4W$xP8aYU6^TXYWVIwAU)w*_9FbUZyVSzoAw_#cnCww)bPVcjvfQ*3Ez7hBg9Ue
zJazgEhL-TN6OW!-a6X(N{P+coc)57!%%#g1egXRA%2kkW!Y^DSoW`KuhlDOe__ga8
zwp_k(^VV%FAr`*!4p__GyBJz--n;+cA(mVj{%G%GVB&vr_bG;!ThE?9$EGE$=EX~3
z7JK#jO&GeChtIKV34QzS{i_eJK7RU)k&(YVeE9GS7Qcji{rK(m_a8rh{SF~E#DYV<
zetz@x&)?seg%U1X@DzPm{DP%`{fA!*wqpF>e|%c7Ru(cyl?A3>2v$j$Ax5y`!vthh
zuVhr>BNzYx`KOJp(wB}a0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3d
zIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(Rg
ZD=;-WFfhuORjdF2002ovPDHLkV1g+E(G&mx

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_recursive_union.png b/web/pgadmin/misc/static/explain/img/ex_recursive_union.png
new file mode 100644
index 0000000000000000000000000000000000000000..66952ea454e3703dcb08251bd2273193aacaeb28
GIT binary patch
literal 1224
zcmV;(1ULJMP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70005?P)t-s0001q
zx!X2~!Eep(MZW1w!0A)M>V?$sl-Tfk&g_HH?Tggzt>5!Nyy*w1;t;Fi8m{AA#p`j)
z>`uPsTEXXJ#psgN?_`zG-s;qsyLGzJqh@Q-%+2w=zwN)l?ZU(F)710W+4RoO^3l=q
z)YSCg;P-*9LUN@xnY?(sz3iu{=c=sduCM9E#_!9_@xH(9!ou#w#_-nG^mC;*r_X10
zsycS7JFwk!QjHmRt30ycc6zZujKETQu|cJ%<dC*(*yYQi#9oB3MRKDxqQ_vQ%44U^
zW`?#&cB(tB+HrWUKD6U`wV8gwk2a0LQ}pr4@a(tn?6&Xfw)5}D^6bU!<+1JKu<YWm
z@aVy|=X~nqo$BM9>EWC2=(Fb9mF3ry<kgbv-mJOnfA8zI>f)T}-<j^_vh3rp<kXSl
z(vRufs(PNOy6%GS>9pzKn&{q{=iHa<<FM@Dt>n{?<I#@d&yDHXsC=|Tyzqqc?Z)lo
zvGC}^@8-bj+^Xr?sp;6K?cuzZ%w5~PiSg>g@8!Sl;=SnCr{~qB=G3I@-MZ}IuH@B`
z;?R!h*QVdchTg@6-NJ+A(V^?#t>e*;;mwQZ)TG|TgxtV^<<6ku&Wz#Air~qK=F+3x
z!Ghesf!n@+<jtP$<G$?Py6fAx<<Fqx&Y$Ypwpcc+1poj50d!JMQvg8b*k%9#010qN
zS#tmY07w7;07w8v$!k6U00Ih0L_t(Y$75g^1>^xnCJ=y<#A#t-W?^MxXX4=G;^tu@
zQVTC1zknc<kg$lT7_nMNw?!Q63n)p*ixQGjKvG&pR!&|)T2V<^MOB)R<&tXZ8bB?Y
zTG~3g(t7#^hDOL*uzFF-*u+!=rxxs9)G#x*z@-Jd7o{w%tTk}?1-r1MjV;hGcG4gK
zB^fj<EbJY?TCiG<EyOGw9i6~>aJZKN(>M(aXP_REv;f1z+0g=bbYa!PVBrGNg6c&y
zE$GHsxMJD@@iq)#7-!*Tfnhl$q2L6P?j9KF2$E260x1tqFN|~qPAIs6hL^VmnqMFZ
z1!u;P^700z3Ljq(fRfOJf-}iV`hlc<{R0Anf_+0m!@?tc(LxL(V^~CjT<see6&(}n
z8yXj%km!r%atvE6lE7M$Q({uneADAIGPBUNWP^PHCBa&9z*=(i@(T)mi;7E1%h0u;
zIvN;W<rQEpm1$Ll)xI^gb@dGhEf7DUI$Fb`yrL15lAFL`1tk&D3{C~8j<(1F>Hy^z
z-{$b5#1`Myw)T!rge{O%fa+*?uJrBd&g`l4?d|KIFcHZw;8cL!(G0$mCYMfW^PM_v
z!t@zPwt!Osc1JV#*3O(YyWMxr+<EgCAhbYI0d_|-_%5vPow~?(@sgzrmSKb#c1Po|
z9FL>%0_={)uLY~4@dA8~9tHFV07F>Y9b_N@c>n+aC3HntbYx+4WjbSWWnpw>05UK!
zFfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GK
mIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTYtrHx7e

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png b/web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png
new file mode 100644
index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a
GIT binary patch
literal 218
zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU<QV=$!9G<yd-aT7Y4?=
zUBXL%JkA1-$YLPv0mg18v+aNkK2I0N5Q)pl2@<T$oD2+%6Brne&c5vllu<2ljVMV;
zEJ?LWE=mPb3`Pcq2D*kux(0?JMg~?U##SaK+6Jap1_l;0Rpn4L<mRVjrd2{T7+8We
bfK*!<m_an0njX3asDZ)L)z4*}Q$iB}aV9cI

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_result.png b/web/pgadmin/misc/static/explain/img/ex_result.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfd7b5904f9b304709ac6aab37e24dcc06d233c7
GIT binary patch
literal 1320
zcmV+@1=sqCP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002L
z$<UU#(67SKeXh~4!_bAX(Osq0Zn@x(wb8-K&}gpOjmqed%IIXO*vQS$YP8;l)bMe>
z<bu!aVX4@+#?YR;&}^sCf579B&FEmJ)}GYufxh8%tI?>y&|Rq4YRc_yz2s%c>~px_
zSfSIA(d>o8<yxZCm(uK6qSJ1{=25}wjkVEryyK13?q#joj?L+~#?YVI@LHwSTcFW`
z#^!Uh-Idtykjm$8yW%;y=Uv3<cDmqo&Fy`{<dN0xWTeqv!RRu#=5@;IZp-X#u-cN-
z?P09iK)mRBz~p+$>VwejipJ$*z2zga<UG3PRl(?g&g_)c?q;&xVZi4cujDnj=ViCx
zTEpph#prd#=v=(zEVboDyXJq$=zz%RWX9`=(d|CD<!s68Q=QRq$?2xQ&}6XOaJJrj
zx!-lf=SjZld&TESz2|<->y*ywR-w~O!0LI->S@2`Yq#KKzve-^=xegvb++DV$m?jj
z<BZ7Wg3arCyWyqU@K3knEo+Lw00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0^dnQK~zY`?N#Yh+CUT!A%sLMsaGh-LM_OZl!8#apdwKLmBRyy7FwkURz=05
z^{DN?-f#qVLv*HJI{o0AVTQN6zxUqnUBR$t`S+s2aTTPh2@R$Ze@_Uhr6?^eX#=!w
zkidu+dYpM2@p6b79vK~LYpmgw(Ik4&kDGzz5roD1dV(B&W5e4B`=q1wly#1~+^jML
zcsL#Z*85J~R5W&<&+Tu6-BT{7A|X|P;{qx^*y6$v?}zk~X*x7LLn!g#XXoaFl0t_g
zAIP46<ARx1fJ7H#9?<#k*9uEIb&ut;cg5uICm&DDQMi=5<os$BHaEGpPRal*X-K6r
zalBmum3AqcgrAV>8=EE-ir5<7j^*+nDazi&@X2h!2ImzwkBpdc1Oj$;KNWK&&*v{K
zHc_Fp2T>6_XL(<6MG=Sv0DC5#0-#mAvC+Nq*9v^kIK2l8vdFM%Et8knvxnJo1c6|c
z&4c}KvKHW}Vy0H@L~i3sM*z8wP6~1|m|;789O!rkC-wRX3IT({=^1*&oHufd5<hTM
z>Vx<$E{y1rNF{wq{1pVhD#p6C2Rn5oh%7L0oleB1N|4Fp`jMQ=#pw)z990@QuA5do
z%zYPv$i2ZQ&zL%%$z6BtR(}-AOZ|1ZsiIV3#aX_aKKpI?Rtwj<S$$bw@^{6#TF;1K
zr{3RVX9kpAD%Y_0MwLA*6vA-iVkfW3ieG7@w;>~pUC1Wk{1EpWn@(i<$E=y+6>NkK
z&qpAySa4-uHvH^XzT03OH><a{o{ehAyMj8w$h-WttQ&NZpV96kV4My?L0ABbMGMDq
z*$yScYY)=&)1fH`v<WoU-7VJ2U7}9J&`8I$z8yYm`gKnZK)LWFQpDw+wQo`T$%m=d
zzQj%A4ia8s<V50kDpFI}g$%88-LN9{Um<<~F?K%Tp#n<Vv$^~qaIuPR*#L_H0A7VD
z3nOkq=7|r|v-~&m2P92<JOAsn6#xJLC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@u
zGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<
eG&DLeHY+eSIxsNGmsP9)0000<MNUMnLSTXtOrt>n

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_scan.png b/web/pgadmin/misc/static/explain/img/ex_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..396dfb4feabdd85e081bf8336304129058633c5c
GIT binary patch
literal 1320
zcmY+CX;4!K6or#E43U(&OiX7G0Tmtd5DXYmgR&F@acWZxiv&wwL>3hgrBoq~lr@it
zLJ^duAZWq5fw&ZkC=h`X77>I3W&snjFJvdI(x|^WbI&<*@7(X#z04qTpskIa4Gade
zJw!T4v82aGA-}XZR*;$vgTXU`4u=peEwhE|na4_cJ>&OMPHdC^GN?%#(geLygmo&i
zN3_g;wJ~hf>0VV*pQ>a+SI*I098_lwt84kXyOa8Y(V1&wnnwbI)t>@VyL>~#^s#Q`
z-ez%7n>4gTN@YnI!|M1RSrS`*eOyDod9RKoioG$+D)iJTAoVnDW}2o~Kn4Y*nFci?
z(4d5jN=PdLbt2HDg!CeCRtX6-W+}%-_OSp37f^A587`pV0tPWSt}w|tfRYQSEfXG~
z<pD-9I3_n5EVvjnE1@|h#C^#el^bWp;E2qi;{kdeVBi5}F*q#M8+pK-7&KX20?sR;
z1r;<X(GQCC^Ad1T#TXFlOnku12j=+L1qs+M(#`ULMG4q9t)1s%*%ntpODaaswDupN
zW=R4rs~Ft^&Eh0>X%f3E0nHYQkNqG4U%j7Mp2U7oF*>GZTDYnYlh_prI4U=^O{(k1
z<RH8(z;Xk43WXdD^Oa1*TAEd7Nz|V$N&2LK-gs{q?2D;G2mL}wJ?au}Fvk}SCEl-Z
zn!Q~=7IE=$y}e(yGh*kDXdI~~U{jVK(F*Yxuqnx~>1&_l=Y&sWTE`Nqyi*-+eA9Md
zK3zI6YurED-VlgxD2VgQq4T)o<{Uc52dBM4_ns4qT@<Dvt#*S#&6df$?z_JA^7Nd&
zsc>|3T=BIfA3pq|mf@Ygu#{_8Ub(bzb>X|nwg*!@UBbMkgoVpZr*Tf=$$yMQS7cHv
zJl|yI9uG%_?<;ufhl(nz-b3s9yRngm7!eBB**@c+iOV>**WUgtxir4qH!Q58sBY^X
z+t^lqCrqiOuN6j!FCm^Z-Pw}A4cEdyv#acY!)jFJxKh;d#_(duq1<!#u|_inUhRb7
zZuWs4?RmPM3Lb48C_72VNCv(-0Y~+p3u~M&y#z-hvq<OPO#~HPL|HeT-JBnDa|-Q?
z%<Wv4nUHu7scz!F?nr$`t8(+1oBKW|Pe5|!z>mbS2==EN8$unCzu)=|NpXJS+8k(y
zsf-o0Hs=vz(jedS7h1^)Nk6Egj#I}j#|fGRi^DszZaa6=0t<S~OgC$5;PxXZf91sE
zOWU(6NzTED<doV?9Qs3{X(;P@)9MkAuHxQkhHpV(D8foPn!Wy&AP<pDsLJyP%3oh&
z_cAYs?`!v|IYy!w<2r@-;@--2?03|0W>xir$OP}|H5oNdm)-MvQ!!rm(9c)!#r=_M
zBHea|)}|e>!%!27`lYDuS>)H4O$vGq?bkY|V%O_yDBIEKFR-P%ozXV=^u|^833oyO
zaZUL45Q3B6k&=}B<!$*W;*kcVXyLiC3y+ARgakaz>j~!U7+UU*(%Cey-DHB+7FHqH
zPj|}S-*ndrRToh|R<X8I9r-f-LcEN{x??i2OZ4-b(JqNaSpSxvc22t`6N*Rz>U)~O
z3XiIz)B{0V)^^ow^e^-{bxVdh`d7qN(nH;q3&j)u%b;lk?cGMHsP*lX<>$Ai5W`cV
zkEg^CP9(=z0_K5p$GN(DxZ>PHJa7d3E&?9!g4<2N;r5nqb@(5Um=t{~HtjQj+xH3h
axJ|%$eg^tVLmyfI*dZeMV8gextp5Q0yRj$$

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_seek.png b/web/pgadmin/misc/static/explain/img/ex_seek.png
new file mode 100644
index 0000000000000000000000000000000000000000..130fbd8f53ff5c2c757c8173eb62a1b604555f34
GIT binary patch
literal 1326
zcmV+}1=0G6P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0001W
zrpGpj!EVazM7`;D&hC59?oYtzRl@0p)bM7=?3UT_g3;}!-tvyr?vmE;ui^6rr{No}
z<6Fh+V#n(^x8-Zc=yH~!hN)yyjTym@HuUhw@a(tm>$dan$M5O1^6ka$=CkqY#O&j+
zp~PP6<(}%}obT$j>EN30=(FhFndjV>?d7rN*p%$xt?c5j<kgbw;jZi7t?lNr@aV$t
z=D^3Qb@1lE>fEaD<G$n4kK)jc>Dj2{(~#oMjN#0S=-8+2;k;g#B*DnK#F|j0%w%+@
zIG(kKuGw+%>WA&)VD95x^XP-`;92wMfbif}@ZVJQ=6#RAQ?cH30002z+A8STDCyWG
z>eeFb)Es@XL#xzmv*LH^)gSEB81mgwaGgbAl`r+=dg#}t=hUR^+_~n_qTtDi-^YgD
z#D(V5q}{`W+`xk0$cWs(faK1gsoHLy(PX^vg!0`^^V>}K<977gNQ<j%pw(yY&=l^^
z5bw?o^V&-A%?rQBx1!f+zBxuV00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0_90WK~zY`?b6v(+E5e+U>k6&Tea1i1BihN$R<*($RcX1MKFRGK$JxVf+$KW
zhy}F%>%F-wiB95-Go4N!ybt#&-#z)Ab9#DS&%Zx!2<}MUVuX;%<?>#luYX`*kQjU`
zlMN9=eZ#{e#K`E_*gHa$$j8UW<wXC)#Ke2z!{p?ojOaytm>8X!l8q66sme$E&ysih
zt7&qYC!kO&l?v`wDPTseR?osLMNt~iXti1mw7()hkpiNeo10UDQm51DL2WP?=7BPr
zOlHtpEEel?M1_c&BSs1+lgXro1&hV@3{im|lX;^N5wpo;(ZZt5X0wYEg;J%O;ZuCZ
zDHa^yu-ffQqT~y3NAec0qSvcYm3fLnRm^78CC#GM>VRdZ)43x4V<?4zPhsh%aJt-X
zk2q25T8%Zf8@mWro6YM4r`x?MMbIodjcv`s?sd5kS@roO$Sh8w!6_Vq6e}Lj+B#1*
zzIONK8}5wc-65M>-;p8xoPq5?Ah6xNgTWoxp=o*tc0-}?Ubo6-WNU*YN&h}F5MZKA
z00PlyGyuU^EFJ{<;NXCUP$H2C^W-qHdF1!+CzB*1Q6`E>EC|7PJWeB$N`)YtNT)MA
zA&*Z`6(sm7u~>_s_vus$y&p~s$r0`tnG|LTK25Qg$z)ESkQa5#FPw|RYPB!JQhOF_
z*>zaY<#Kt*vn-njQ6gLqkFQY3Lhh_sJO}ooRAReiUr6j;FJ6yTv>w?);W7ugV)23n
zwp6+*OAwdK?L_bUd~1-sJUh$5dGP|1D*;IgiByVwxm@mMIr;J89XtMch<EH7&xM=C
zF6@TGtyxq~;j~(<-oQ<*R=ep`xsCif<aX?uGe9#bCe)sFre3dCpxS6Q@1S;ne_!j6
z-?x#6<TXBM$LJK(U5`q=-l)P|v)QaYCdl9&YgGv`2__7gbiIy9qk#$c@nh0%Tq{c_
zMWvPEu9M<XmiBo`$6n9>cm4nf0rwvzb1L2d001R)MObuXVRU6WV{&C-bY%cCFflMK
zF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<GB7bPEigGP
kFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f_kQ{fB*mh

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_setop.png b/web/pgadmin/misc/static/explain/img/ex_setop.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3a9b1983b5b47c96ae910e73ab4260ae9c28b73
GIT binary patch
literal 1143
zcmV--1c>{IP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002E
zxzL`V(1?rCc6!ljZ_&-ct;w>B#-wM%nNGrzJd>5te}mbYo#C;w>b<`0$jI=)!tS)S
z>Yk$FfrZ(}s&>qySJ}LM<k6by-Kg#6v*XQ`qNLELspq@A?YOz?x47%Bu<78)j_l>D
z>*J&8;hW6M%aoVjwzuoBvgxw3>bkq^zQOKonbT;Cw%F6E=-!y;+?VRzrRLP5*SC1B
zujqQE*JQ2NWs|?=+Lq|snys$rs;uaQsoGkq)?dWwX2$7un78KHmFC!#<=2zt){@q>
zb)U22NtVh}z2-Hy=1RThb;#&boz14Or{vX=<kXSo)|jNH<fy9WTeRLOv*bFs<#o5*
zUyi-x(~#rRkKMwEAh6_t&FbUOj<dAt7_H;t&Wz#Ai>j^WVVTb!u;hfp;!=vh;meBO
z#*ekO>Wt0lK!(EL%8BLApgM`e53A#l)9m2Mh~LPG-^YjE#f9R_n%=~P-olRE!h_qr
zgxtV_;K!5Guy4}DpWelaCqTqQ00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0xn5JK~zY`?bK^i+CUTrVD7hwTp9u~3K&4efQ155Dpjm4f~c)k&`L{%-n1>)
z8Vm%m{&hD%k|mfKr!(y*&NpUw=DfQ(dlZUEP3k|EQl-{twHmcbIry#98;mBC(V*AK
z0c6TNZLwNy_D3iJkj{ZQUHHrlPB<~gy=WGlv$#E8ug7AuTIbPOJx2O`et#ek@cVEQ
z(~p4#WO2zm9bC3Ac_=qPq43IMANT)2Boc{6p2Xsg1qngSR4_5mlTc_inS9EFXf&02
zhC3aSREyMFdQBwH*EcfBO%4E&da?EL)fS!|$)-f@i8MsEbNQXZ?%w{O1t(s=IdEa{
z9UyO)4`C8M+9{SY$0rd1ygNNTC_~YdQ=T+TwsVE|#Zvym-aY_BQK?j7P#cx`;~Y#@
z*NbPxQepRaGbMm(wdRV8r%``OjF8VqUpSKa`fZqr1GRJF!XOaM_w)$K<<I)$$4{|E
z?Yv%V7zLtrAHsyXEd1ig#_vn9Mg5AVV<tJ`7D*pe`=HruLPQdb$`$xve5NY^u4%^j
zfaCL-0Jv_EZNmdDS!~f_Sppgv`A=CNwrBy-V&I993`~e0m^z<{1x0q?S(Y2i<{%R%
zy5-);Sjr4PEwt!%S>{IaFeOjS2A<dne{2A{WhN_mVq*{45?=X3(8Ek!_M&$)_K+p(
zBEhYTPJ<wC!A;%vn9e=xqQ8_-b8vJi#oJ7y!L+k7OG)t;t8Y@1`X_Y<dEniZp!V7a
z0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFU
zC3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ov
JPDHLkV1j^qN`L?W

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_sort.png b/web/pgadmin/misc/static/explain/img/ex_sort.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d46fd34beee098f997529bf0b7714c52232a2df
GIT binary patch
literal 1157
zcmY+Ce>l?#9LKkGD0JGb!<|bOQ+Me`PIBs@X131ubm*x$mvXaab;*yACb5J^<yUCU
z=?G6fnfz!u<i}*hkYy7xW3|nXjiOR_@wspHSATpy@8|t_KCjR7c|Ol4eIL!oZ1p#*
zQ7Dudg-rBA)cHdg8z6b}L>w1|LZ92`8{m!DQ{|nmX`=An**#5DzO7UKZPUMtXP6Il
z5fWW|pN=clXUJv+$_1mFs&#^Aj(62w4Qg7mCQzhhh$nO8bCLPQz+!<`3u9yCLG%bb
z?15U4$&kq)mBQ#~6BITAuO9LqLIsSB)IkpP_6i{rRI10&2s?LDwdh7Z8fJ@NCJm;!
zFd4t3ITfD?Q+k-tfKm+#6{x2Ho*jVkUqDF&&9OlR$ex0982Sc5B7<QifWi&bjP3!r
zaEsx0$?8cB`nK#iITTstVily{c^X+e5er>i<q%g4k%ho6fRJ1W5&*YE99$-0RE<W}
zX*u<}b08K&QxnwJgHU)0{BOZo5Q%sYYAS}-7f?P0MUzmdg?qD*qJqS6;3y$x4DvM)
zseqCx$W=qd3>dXTM?bXpLR$}rBv8e3)XBr99y8x4qpF)h&;TP34lIsGOB-m{TFz%8
zmqJJT(fm=Knjt1)R-Gi%qYx#33{uyzT`1HF4+_yMfZVAcm^py)vTU8m)18%`sKnhj
z*<uksYUi1eVOfJKPs>_|AHuUzvW>}&>XtFnD#12=5jSD-l}O2Xy6AEAsv_w~<-a~=
zR<a9*e9KC+;FgGmLH*CMbBgT*1FGqRFG(blkIk<5_s=(bzrrV0ONLT{JvvNXj&wR)
z>7XZDI#BoKl9GL|_MWroq;CqTDQRU3U&c7L*{b^OT#I7a=4*Z|__`WB7ft(h<#lmL
zwVU<juuHW=)EJqm7n3feC-+#%HCxw=uD|m{yYOyl`E(|KL();vVdHgcQY}`jeA$Kz
zd^GU|aTDfZM~Hc9Sy{U?g_H0ClY?C)4vFx^B<8H|$3?8%P+fzuPdEAthoRteGj^qv
zp2y-%Cb5#6EEi(d{<;x1@Ul9Xj(^`H-CwfeY>d+eFuzNvy3((g8#?Rm4)+uXRZ{0S
zek&Lj2&|vqTX>h8z4p+Yu5=*Wb*%X-%fX;6ZCeB97F{VJdvHeYIL!geO(qzY#fd~4
zgP|L%u1gYzNTmVY-#^*ix*}V#)w7h0IfJh~v<cg-uAtgBZSQdJ-+WSx^D$TOOB4TV
z&ougHZ<*gtEH4SK60NlsIlMIuW@DxNaj!)u>tp7<>2HN#d3gq@q^0Jug@L!_)33u3
zSD40j=<&?;yenlDmRu9KMFjKSyr<uxY0vOCm{D>(4e9y6VRvwCmgKfwx9qwK@bjUR
zis9q0OCHVF-XIovFSc|R*2(*Lx0j!tMXN^#U$e&f2a$)ekM=$o%{USr>i$P$C?Y6l
zf)l~f$=Q+M6yQv7ciHCd;_?IGCwBtDjc339GvLo~Mi}$-CxEcyBk-Zyo#6Tj=q}h_
PfdD9qH;q`i`*8CAb@5|F

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_subplan.png b/web/pgadmin/misc/static/explain/img/ex_subplan.png
new file mode 100644
index 0000000000000000000000000000000000000000..e13d7794e91fc0842702b0b34c1a9b61607dcf92
GIT binary patch
literal 1283
zcmV+e1^oJnP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002O
z!_bzQ(SL){ba>NdYtf>m<hHo#y}#_l#qYwz?#9UP$jI=?%J9p~@y*Wi&(QMG(el&N
z^Viq)tgh&<vFNF*=D4}*zQ66j!R@lN>5`V;n496cyz8c@(AnDc$Hlj+rEHy)MVE~(
zje!}GmC?n;b=BCB+u@bv?y2JJpyBGA(b0>*y>Y_8Zq(O`&Cz|**oW5HjmgV)&CP_q
zyl}CsSnBe)wXlcC#=6+uj;W$#*yDlM;eY7uxXQ|X(bI*zwrqx}WKxY8b*9HQh{34O
zXl={vL%r!uzvx%P>V?$sl-Tfk&g_TN?ycYR5Ub*n)$gj(YI2sLtk-VAk2du2$?)v9
z@9DJj?Z)lovFzcl@ae+p;+*H(mgLov>)x!v$huvZB(B<V=--*+(T?ies-Crnc&$CS
z>3-?jshY`Moz7yq?t<^;!0Fhh?&7_NyG?JLL&vIhi>qwE$G7O#rsmV5>)g5C#f9C%
zgW=4J+`xh5&Y;`Af8@=c4PV1400001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0=h{=K~zY`&6Md=+CUh_IT8h`QXz`eqXw2kLy;n)g4XJmdbG8avTB3Kp(v<$
z)p}e1_3p-?5Zu7nnNFV%o0)f?-@M1}Mx*)X((3dKs}!TxX;p}pR)f)GG8$jBwRd!w
z%`ZDUEmpIMqcggeuI?_I0%_~9**P5z(YJQL>Qz?8^x17DJq=;{x!wU~4cdV|%WIZ~
zu$^Xx5QDtK35c8yeo&)jELJMwVp*3&xINyX;bB4W`rcrMwbBt;yniI{)+-1?FfbY#
z<5dwki^CHb36sj4;gCDzP(xg7TRUOE_;`3?5(g$H!Vxb}L0qhf<BXVz0ua3qO*$hH
zHH5VoIL=<X3)B(ZiOt5=WWh)fsJ71<dhlKp3=LHn5NrmCg5jxYL1vmmz_>6q?R^LC
z+aGBz3y6${r7U>JZlAM>UNjEhXh=TF8Nd2bRuFzH;GTIO2?j%Mzk8N%fEdW$AV22w
zL@?s<g=QOEOV-}=<mI>0-wW-D+06_Mp*`e&BlRIa<9G3lpVBjaeSfJrI7i?75F?V;
zhba6A>5ka^!s61W*yk_H%U@$pB6W_~LOdQ{ip?)B&3~&x5><%OLCKO($?{#QQC=UB
z<Ren&Kr5@OYb)r7D5loYlSrKdt)|oIH6(6iGFem^Z!W)?&s9dPbVz|M5s8^hVH*(_
zo>gwAm@Mw(G_XV%g#!oHwzf7zl+6?hyND182=m3g{rzGd!crMCwGM*d8pU;Vu)TYD
zgz$MJ63OJ|-to!teyLO{m&;J$!1{@Z<P~R0Bq4++r@u~5@V88|)H+C(^;9Z*fR4`2
z&M%~1s%8btQZKa*97stu9$sEvoj**h=5i_>8k8**uFuf<<<-sY<GEC;&VjbCuSwwQ
z=Jy>1Q997kA*u1=_V(_Af+!tz^Qco}$N!-}nbr1E9_U2o0000bbVXQnWMOn=I%9HW
zVRU5xGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu3
t05UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1oA>oooOA

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_tid_scan.png b/web/pgadmin/misc/static/explain/img/ex_tid_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a6063a1af031c626802911fdca598983aff7d52
GIT binary patch
literal 1074
zcmV-21kL-2P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002d
z;PI2T-@Mf8{{H{L*6o?Q;ibal>hbyT^!nN5^vBrl+3WSI#^#K%-OAhV<Lvi^t=n~>
z)~3be%jEIR<?+4P?Qx#e)#LN7$>)Ek+0f?l*6Q@d;qbxV@5$rvYnjrc!{ox+?t7-#
zwb1FX%;=%P<Hg|a#oq4t`u(HD<awmm>Ezqh$F9h;kiMjKx|(3PkxRstJB*5mfq{XC
zhlh@ij+T~|prD|vtgO7eyv@zc+}zyo@bKZ`;laPVtDlO=oI}p1PuR6w-oa?$%4zD;
zYVFo~;L^J5>E!0-=GocV($dn%$jHFJz_hfqs;a7)nVF-bqn?|W>)wU&>3;X;Y4PG!
z_2EYK-Z;soXy4!8*4Eb0(9p`t%EiUS?d|R1)xgxbiOHZ?)2mYZ=33Can9k15#>U3O
z!^66|x{;BQ>gwv<&a=<QxxT)>xVX5tx3}Kg*UYkn%e$bxy}jh*<eZ$G=;-L1o13t(
zu%DlwudlDArKN?1g_M+(;^{T(00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0qIFZK~zY`?UY$l+E5sVm1q!Z0s&d05EXH$OJzw&f>9JvV-~<xtzpr&mJ)3#
zt^4}dJ;CwH5a3Q{crP>OnfJ;0@};Mzn|G656V%rsV(N#@1DyaC>j%>yg4)_VZtpi^
z4$R~na=AT7>*IZ1AL@sFZhUwc9|;5piB`Y>L|~&3j^krablejP42`#Fv4k9O$mI$;
zF%N;*>=WT2Y&Irr9@rh6L@)yJTD0r+VpD$OYqp`G5qH=Twobo1f&7qh2|N*)`RMCt
z5>^5=+dBb3rrVuclg!S|FML{Dj6`FxSe&F1G{YvB49;`QE2-7B^m-<<5!u}0xots|
z<ZO=2OPoHy%R7<P^ye?Td;4GaGY3ktP$(AJk|>G{P073tp-|<+t)p+>BR|ra<Kx}6
zp9P^(`c;(}RaK?3e4@NYvh3lhmf7D8Y$NrD70Xgh{acr1xh^r;=Ey0}bN}Z4ADn5#
z`7_Z35rLQn@fV!3T@cu3sYXRZOw?(q(U+=l1rYG!LIqTS4wb1%)v`)c*EjlN#riFI
zORLpVMG%CdCdSn7?sHM{I%^=7WY6#Nxs&Jv80AAeN<QWi_cvX!_a&yZ5Yzei!HOA(
z>8=<EC*^g;nl5r9OG%cY6BHS5^LNqB+sG4UmhG=%BJ%?P001R)MObuXVRU6WV{&C-
zbY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=I&E)cX=Zr<
sGB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$g8msoqyPW_

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unique.png b/web/pgadmin/misc/static/explain/img/ex_unique.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb70480be8a4f49369d9a974ce9461284d1a4654
GIT binary patch
literal 1238
zcmV;{1S$K8P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004iP)t-s0002Z
z#KgUKcgI3Ps~Q@~u&}o&Ddc5l(9X`ss&&DSHnvqK%tl7dN=mayB!;PEQjHmZrCYPX
zru6a2@a(tm>b0~uF5<tRp~YT>vPSFWp6cS9>EWB(wt%)SEA8a5a-%iq-I&LARP5lb
z-DPF(<-exPW%BF9vLz+pz?JIUs&=Y7>DZ_2+_~o1l-8qo)LdNR&yAk7hpyUjc&<L=
z)RExHh~LMC=Fy_RTtmV?KjF-Z=+~yT<a*t~g5%7duOT6OvqIz1kK4U}xG^!fML3PY
zQ|HvA<j$bIJ3G^6X1VKs(35S+RaLw1gT_uymdstaH8rt4A-G~H%wuG_M@zb8Ws;TA
zW^2)iL34~eXwA;?y}#|h!R^Ar?#0IN$jR`_%<--<Hq^DqyuItEspqP!=dQ2mv9s#7
zx9hx}zpN}W)z$R0W_!S=%C9#);NbVg#O%qp*3r@OwTrE~yX?Nd?W<8{vvZEQtk|zV
zN|H5LgGPX=ey69T;D1YtlFETR00001bW%=J06^y0W&i*H32;bRa{vGUNB{r;NB~C3
zYd!z~0*y&TK~zY`&6N9F(oh)3hs+U!xs^aDm&vUZae{%Mz>t6d6NpNgVwgoYFIh(3
zy7;emwsT-WPoD8Z&*S?eXP>?A`{nFI5QC)~(-9-qn4TPw8K(^79c_;qX;>yRGhrkM
zmgDZ;!yxpe#VVK0K;5Sag0tJFa13pkb~v0)7iDnswA^I|Fc`!t6CSVG?Df&|5A2Mc
z!yy3hc-(%<1rabC4w>YwJny3XHeUcC4=@N!Y=Y67XxiA1bfc8ZIN0SO&+|UgKRXvh
zUD&C47Dj2YfB}5L&;mV(@PZ&Ly2G|eBm_^E<{w2_CCX`sM~Fq1<B1`}C&Xea%&Iq;
zOraAtn&uS5sSu)=8Af8U2<cyQC6^Jh6isJYB}$L*I2jkk+%OUd1{d?;LMoL)BePGk
zEL;+-QB0IFnW0^bN?v7?8Vr}lB^C%K$n~|kVljbU#y!RTm7(NDzETiDp<uWw4oSsV
zYGtpCl;9o9;t`QtCtYGLmn$hZIa_^(yU7b1^-3X8DwULAxGEqp-;0rcUaP<81*7Gw
zBy{A<kv%cHxiuOAn5}W8+b#20LxQz!W_B9d5|f&{uUdPr_um+(w~{uGN$q#<KYaXj
zKp75?ByAo;$g$acEF)SxhLA%)^%){~kHK9IDF4aP7lhzE1{2$1^xKlu=tsIy%GLP0
z%U9sB({2~!v2K*jZ{34|T{`aYNox=7f;@(n{C?Iu7(mXM)eH3mdJKaAk6o%=pvP`N
zn!Bw|K76H)j6C-5UF2YX)XG2AV35adO6;r+Ja($S4C^s4@~?d5s&8bh#{hC(D_^Jx
z-eXexH}KeH7t!J|X}^=NZ1#fD;;{zEtA5=J<`#PF|I{BM#JXLtE~^v(001R)MObuX
zVRU6WV{&C-bY%cCFflMKF)}SMF;p@zIy5snG&CzPH99abIGH6L0000bbVXQnWMOn=
zI&E)cX=Zr<GB7bPEigGPFfmjzGdeUhIxsdXFf}?bFv^!ztN;K207*qoM6N<$f(A@n
A@Bjb+

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_unknown.png b/web/pgadmin/misc/static/explain/img/ex_unknown.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5d54f5c7b253978b96993ea260828a144395095
GIT binary patch
literal 1088
zcmV-G1i$-<P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-s00025
z!qAqu(2ljybgR*YvC*c#(8$fuxW>>^ozY{g+H$$zaJ}Pq#prd!=Xk~Eea7c~!sL0n
z;BT|sS)tRx%FtS*)o#A!TgB^2zUV@{=s>&YKf2~ayXQu|=uW=pS;FaL#_4Uz>V?ql
zgTmx#uG*ix&}^sCZn)r0!09iw<{+@-AFtyivE(VU<S(@3G`8hBxaB~(<wd*ZOTFe$
zzUOPl=~<%FSE18r!{{2W;}xvqRlw(3!RL9*>ypmuTB6fvyyGgg<`JvnU&H8?(CcKb
z+6}7WW5noy&+LiA<Yc7LR>0^9sp2xV<VL*aXU6Do$?BHZ?`E*vFt_Gl#_Mpx=3d3>
zQNZYfzTthY(P6#i1*qX$pwV}|;(pHSdCBU($<S58=^e1-60GB4s@Sl@&}p>ZUZ&P@
z%j|o{>5S9ub++Dj%<5;W*ml6=ipuD1!|15M&|t;sdd}^2y5K>(=xE66YQW}z$LM~@
z=wip~XUObp%Itj4?KV3{{{R300d!JMQvg8b*k%9#010qNS#tmY07w7;07w8v$!k6U
z00H_*L_t(Y$L-VoSJF@z2k>#Zm$&&66HySyHefJJgw$z(fRdI186mP%OejOc)GU0-
z>Thp&cTUGnWA_LBjqi^;_jC5#=Xowg_45ER0W(yCfW=C5-iGWBdSGy9_=%GlaUqi-
z7)9=}agTSxH_7@rUI+w3XtcKw!x1Su^>jKm6Hh#wotx+6KyX3qS=8e5Xfl=jOVUex
zCY$SJ(DF(?f1kYIGpjlM+Q3>O|MFk*N?zYUx};D{l{E6&w>hhkH|4iEy=IG*tr8}&
ziR$)Hxu#$u2i^f4va_4qyCc)=fD6L<KCfW%UbnKkRKg@awqsLAR5&PNlF7pVi-x=T
zA@#A_G0)+r?gu!`8IsoWZc$>r-g5o!YDdRuOx8EdI)ya=f(84cqtfc00Its*zP`{t
zvGm*-tI5)xw)yaY^JDU5HB1@DiXyUq6VF9xpNhu9hUnU)HhsCxm`TK0GodYv9AJ>K
zpwO8V$2+zpw@(TFp)QUR#cP=s&KF1o75*B>n>;TB;RT6Mw;+beHz%@@2+C4n@q3xe
zxX1(rB0fx-PLf_>Qk9e@SL44kfRHrYjx{x*Q0S;ZNGK6#A=nd5v?4T3cRC&Bgw()6
z&n4?oZ*z4HNy+sL?wE4ZCuHQu?RfR}=da`6SyFNV?OMnlOFxg0KS=w20#>Z+^Z)<=
zC3HntbYx+4WjbSWWnpw>05UK!FfB1MEif@uGB7$cGdeUhD=;-WFfcfoB_03(03~!q
zSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL<G&DLeHY+eSIxsNGmsP9)0000<MNUMn
GLSTY1nJ@bQ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_update.png b/web/pgadmin/misc/static/explain/img/ex_update.png
new file mode 100644
index 0000000000000000000000000000000000000000..a45c53f83a0593704855bb8488871e8e8c9be701
GIT binary patch
literal 1090
zcmV-I1ikx-P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70003#P)t-s00016
zTU$1W!3nA2a+aZzl9IuXHZry4b*9Hbyy(lKSW3X@*Qid%s&!q&>iw+&W5()h$LMIt
z>~72Kb<OPByMEiff7`u@<Gpj-zJT1nfd9QPd(rRQ!GeU*?%l(Kgwyce#Dw0(h2F-7
zjMVPm$A{m@h>_Lq;K_;M%!-%U@#D*$;?9iY%%0@Up5oAr<j$X<+wtSlkLA#z<kOJm
z(V^ziqU6+(=F+0%)sp7Zqvh6<=G3I;)upcB^XA!<=+~y`*Qe&%mg(51>Dj2~+?VOv
zsp{Fb>D#L7+PCQ5nd;rD?A*BN;F{~+tnA&o>));H-n#7It?J{P?cclX;jZoAyXxef
z?BcKO;k@kQu<hls@8rJi=CbbQv+w1=@aDkp=(F$XweaY|@ae+v?6&di#PaRM^X<m-
z@W|M;qjdlP00DGTPE!Ct=GbNc000SaNLh0L01FcU01FcV0GgZ_0007=Nkl<ZNXPAy
z-B#LA5QQ<eL#ah;5tS;UMbwA_5<v)|fIxsm0YgCuG)2mv@BaclIp+XDLzfq>)m}Ik
z`zl|u_nwKx@;3iqJ}~+$R5OHe*le~9W_M+Eb)VV);&7ZYr@MQ57tF=s@$q-Y6tOKY
zFWZ<Eq^rzltJUgYHW0qY9ImfBj+s~b$~)|Np_(D^I2a5bC)(@vMljMeZ3shERr^4m
z0k9j9!To)$N40l*d;0*Il+U+8O{EeF5Mun>Zl6PH7^x9N(m>1S^C~n`i3##!Jnm*;
zhV*LqVXP05gphXrI;BEA5sz0XlFizcG0d`H|I-vdaf)Ui`bxFjrCB!Z-`K&F`2_lG
zW8HeL^u3$4uQLXA^nuZXrj20OZmUD+*A=A;U0IeplNZl1u(P(dwqr=q)KgrQK@JCl
z;>^E+p@=3)q}Ws)l=#)94014dCK_tO3=TIzaIq>bwt5*3%TPQ!V{>4cXY2w@vpi4H
z5p%f~7?%#4`cmO#jZGLtp*Uy@&3d0|w^~NK=oCe<1FcLZ-GKRfgiQARl7;%8Pr;%T
znUf=*08efhU<o1hL;@WKQ8^qA7C~fLHUsG+pZ6(3LA}y4RZ?t@Jvm83((Co|<_PjH
z|Dvj=XrUD8WCI!k&sR*5kA}v!pSNfJ6<IEJ=yr%Ul7estl~^=#V{VN2CpSP8r8wXr
z>T$a{bA%j*5fQuxo)|>jZr1{YRBA-{1CJ*HoZICJnIoNCF4LOJQi(<*B`9&BQ0dhg
zYo(KD3q<dDK30Jnj^of*LV-ZR+!#3wqPz#EaOo)mPN(zS97+20!yp>N#m*4FIU=Af
z#HdEyIudfZoB=prjNI`t<e#W9MZxd)7yi=N)=U1%xA~vu4`}JQAOw$Lx&QzG07*qo
IM6N<$g4z^C1ONa4

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_values_scan.png b/web/pgadmin/misc/static/explain/img/ex_values_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..15b5ab4079cb8e2c015b4f5f10a3dbf8aa6410d3
GIT binary patch
literal 913
zcmV;C18)3@P)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj70004fP)t-siMiW0
zh{12o?m@ljMZW2E&hL59?@+?(R>SIp)9{Ja@MXyCYRT+x%IuWb@tNB3d(Q2G(e0$&
z@u=SNj@0gw*6*+3^985jNWbYDuH#$9>o~XNP`>9?z~@=P=Z4YkYscts$mx~W?|Po8
zrNQK8meIkFHu36*?c-qX<6!dYhx6)(@8e(Z<X`jahxF@*@Z?|d<X`pchw|iK_Unh?
z@Av20E9lxQ=-Mjk+bZnaD(>4V@7pT#<zLt8_37Fw>)R^r+bZweD)8JY^yOdH>GbpD
zU-ji*_v?rF>xbLDiPY)z_T^vG==1U1D)QVa^V}-;<zLa~^7Py)_~l>q+$#9yU-|2Y
z`s|0!=JL+v^7-ap`|O6y<?;31D)-$g_}wY`=3mR?@%G&+`{rND<M8?2DahmS`Rs@M
z?1snV@W$fr{N`W%?1shR@B7{<{N5@3=3n>SDf-<h{^no8;O_n2DgNw+z~An<+wA_{
zDgWkQ)amoS-tDc{=`>ap0ssI20d!JMQvg8b*k%9#010qNS#tmY3ljhU3ljkVnw%H_
z00FH@L_t(Y$L-b0a)Lk*24Ek?nFomRW_gGUK~X^z7hFIQqoPPyTuOQNU}g{;#Yj~u
zhm?Jq%hywM|1~5M&-$-bf~P9QA@C)YD!#&4B$dhJ^6-HRlK><UDZb@$)Hi|e6h+aI
z7lL#eAd=5jtC&#LT8)Xk5M;BM1g#-eV_6OzX@=ukmluLU0gw}e6wC6MaC~zhM3C?#
zXpU=qVA33nA0Xiih4FYO5~N%P_eS3qM6pMZN(IEs1gO_R%)p!pq$!4J!B~W0dA{*T
zU_hti6P|CPal)1$2<_;=bi0@Y8wgGI=I8hOm;|k%FdTl$=-8HJWkw8nG`bFqIFuv>
z5m{ALg&>p3bzpku)=*JRlO)sW-M}cOX=((S&+i6irfAxdAd5xpz^qoW1LG6e7Dc)D
zP+)8u6H)Rf`%_@fM3(#wc;Boj%jm#tw?0u-yn)kXbuBtDXA-pbh^`yxxHJr711}%G
z-3~o|;r(g)dX0&(b{s2Az~Sh+#{;pMQ)_F3i9?ViuwB>2PwdUWSk8WbK|JGSEO{p|
n8_Tjb@b)wQYk!?*{c(K(wSJimSdJ6(00000NkvXXu0mjfih%ky

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f858be1cee3f5d8f2b67f55d730e6d3f43e9b42
GIT binary patch
literal 2021
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png b/web/pgadmin/misc/static/explain/img/ex_worktable_scan.png
new file mode 100644
index 0000000000000000000000000000000000000000..b51e0b3f4c61ba166b69bd7f32cb23dc8428429e
GIT binary patch
literal 1965
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

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exclude.png b/web/pgadmin/misc/static/explain/img/exclude.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd62eef6410d9315857d2e6d246770e8b65b8f47
GIT binary patch
literal 725
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47y|-)LR^8|wi}<ymTvVw_QrY1
zLC3?d^*UDhFWLA1|9`)wM`KREbvXQ5A-=(6%9fR9ZhU_6^4^pwS28lLWMw^=HS5>=
z_isLZP1<@%wQ`Qjk=K%*nNgG09JqAnY+~Zm($d$dsZT2^E~KQ~e)?Lsd6{J1L`ko#
z8QYH^XzhAZSorVPuP+-md|tBT-`~HFYif>6nkJPuNuqp?YIOD56Ib^8`rV6+d^K<0
zzkmPUtXQ!pGFqZ!hGg3swe*g4XK!wIaz5qe_HfFSrwbQu^AC`0S|K@Qk7Uk-*~hPM
zEhsq<82I=1?;BI6UhV1m_vg>S@bIm*jgk|0NG?2Of9R!T^@7Cpr%znEwc5~Vm%sly
zUEQ5NKC5l*c3rqFx8Q{2nrn7@9?A4<w%>5&&c`3`Z{FOOoV;9Hdxfs<!JM4Ox9?6m
zenWfBW$A4XY?hzYUUH^<`Bv54PXhWDq@H_cwegx-ao6F7A05CjWh@Eu3ubV5b|VeM
zN%D4g;b^-zwF=1LEbxdd2GSm2>~=ES4#-&V>Eak7aXC5R07H*Y2Gbdx45l?XZ+LiQ
zWMmY~)Ws(}c=qt=V{riyAu&Nw;pq&V9$ucOPn<e=Qd>hyv$&ZhB;@K9Q<JS*N=v?e
zImpD;=5|bN*|M}_&%~xBFluK@M_UI6S4XqEt8Zx7+`W4C?)K%=xA(7?_c&m$V4z{4
zVq&6Wqh!RTA|NX)Ek1w3j45*_&6>t1bmBw`i*--4qPDWM%7n?><t2V{=CjN{U1V)w
zV3-jtUHUai{xs0(swJ)wB`Jv|saDBFsX&Us$iUE0*8qr2Lkumf3@ojTjkFEStPBi}
hcbK)IXvob^$xN%ntzp~MJ}aOG22WQ%mvv4FO#q89Jv#sZ

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension-sm.png b/web/pgadmin/misc/static/explain/img/extension-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..432d2f44231c1f88e787091060efc5125d80ef64
GIT binary patch
literal 423
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}QGic~E0BJDv+cp5$Qv6qzrVk7
zW3&9@vzBik`G5Ho|K-)uTicc2-1GkNGyU0x%oo?3fBedPak1gnHlr`^;(z|izO~Kb
z%lm|1zjI$+bN~H2|Mm{mcMpR9{3*J*S>f3^`;Si}e|^pQ`={jETDdE$#Qyy)dv><$
z|KAGswyocQwlbCk`2{mLJiCzw<Zu>vL>2>S4={E+nQaGT<aoL`hDcoQ?f2wsP!M2o
z7l{eDQ+~(c|9@BE84^1Kx361bY|Bu`qQb-J62DBxZ_<p5BE`pwna}6wxpa1%>e!Vz
zahdU=*X&ESFOpJE*&;e)`qd3*DpaT5u9JDLS%3P((k)@<ZbaLzV+h!Of4=?`KK92q
z=3fE@l4^--L`h0wNvc(HQ7VvPFfuSS&^0vDH82b@GO#i+wlXo*HZZj^FqrpFZxxD$
o-29Zxv`UBu152<5plTB<12c$*Q`1A&05vdpy85}Sb4q9e01}k2!2kdN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extension.png b/web/pgadmin/misc/static/explain/img/extension.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3c533347883fc1dec73bcb21b32fc54e3c31732
GIT binary patch
literal 996
zcmV<A0~`E_P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%`cO<%MQ2zLf_6I6&9|zdd99>-@a*2m!Junh7N)|bRf|-bx|zVboSKha_w(lG
z;mc-L4XVVcQjb)R!IoN>UTm6cdTlP+)48aeY_F(+!qvh)fIq$I$ak@QM1Demb2p-y
zZNIpg%*U?Z+|0eUmR?Q)@#@yLtA3u9XFhv8mdv0tb~C!Ok)4xckc3Wsa4?*aUD?pH
z@afX-<;s|gP-<HecxWLITMn+{ye)Dq{QLL&`11Mo?Dg>A^X%E{;lk+My5`xk<JGC)
z(V?J^Pa0qr9%URUZYXJ}aH`|EXsd7=V;MkvKHbcisFznYY9*e@qsi^kq0p+c;k~!#
z!?5MPqv5li->#6?p?1NAale3n)0NuDkgb|nMQ$vEwur;)&X(P%klLe(*qwsbn0(Ze
z&%lqBg+*3S1ZY?dnutTImr{GNfpyZ2etkM9XCNqTCtr9wihVOAV;iN_u6D(TK#xcf
zTn`v!7(bOtw4`b=XCQ>Lht=`it<|(#mtSF?W<QQcm$;T>m1BZ;G0*SUx8%WGt7&4B
zV3D+tb8RIwgF3Luu#bN^7G4v==*yVRp-_@kjmeoSdoaG!zSzWv!K`UHUKC!MVuZSj
zaIAJVdpAyF9vxyClh&g+jY8GGe$u;klYuouT@*iVD?w)>Y;7EsekVC?DRR1gZ_tQZ
zyKv99ZOyY~z@}84hBK{_JHn(!l6fLVbTnhFZfMSfV$6Lul0qhGC2*Z?NP<TvhBL~q
zUB|0c#ivccpg)Ro98G#WT*`P>$a6}(W;2>aI;l}Wz+t|fIeljlScFSfreaXWZ%f2#
zMZ#t~zg?k^OOAUphI1*4cr3V}Qns8&igY1@Y#D@Y7H?b$T8UCtsbn_2S%i2!rk!)h
z!>EaMCcvamyR?(W!KK8$qF<3+9BCaAUJ)8<9X6LmEss1DV->ZTL3e2=8EP9)hfaum
zLy>wXERsEdsDEo(3zmE$ex`jsyImG&7=LOOfol~TcO*%INEmJ%7i}B|YRn1%0004W
zQchC<K<3zH00001VoOIv0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW
z0~-xG4!&HBF8}}lR!KxbR2b7^U?3j2xS;VvLXbtwTz~>0W=QhVA)yH_FkL{}1;huk
zpaKCQKz@=7s`{i97i0@vl2TS8w1C7?R&G7y;)1Nm<<OZkC{A-h<6}A<CjbDaP8qDe
SR_4e60000<MNUMnLSTaX-^L37

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/extensions.png b/web/pgadmin/misc/static/explain/img/extensions.png
new file mode 100644
index 0000000000000000000000000000000000000000..eed7ca97a33ef595f448b8621168d531f86d51d5
GIT binary patch
literal 1017
zcmV<V0|xwwP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px%_fSk!MS^xZ(#^N3p?R&Od+_Yu$ibj%T^6RorB#bmn!1_5x}2JiUH9|m=i$p{
zRt>7esZx(rkHMB&m|kp}YkF-i+S9qHoNTYCfx^|oK7c>H>Bx7neMEjjfO9vZnQgzg
znaszo-`vc-ww7K_0rBeAwyS=gm1jPCJeJI$Gj=n&vyq*XV~~VSeQ+?GkzLu)v+(KC
z?&Zpui%@D?5_o7K5L*te<Gd|$E&TiU`}p$t_U!fW;PdR+>*2!a-MZ%4vg6gM;L)L=
zk53w479M3BDQ+lfsBo&|xoE3!8)F$jd_LXGn5dUmHEJcE$)m~b(xK3*v*EqB=fkk&
zzN6u@o!_pI*P(X7g>k=tf76xP$dIj?SVe9ugSLpn>&}+lsF2#DiP)Wj)|h<Mlh449
zm4!uCPy}dL4Vs8UtCv!Hv4M5cjedPPC}$ujZ6{xNJBocXBx4(;)vk8Mhd_@=5nK-#
zWf(t|O0=YEF=rrzvxn93+^yBLT$f*Ao@PIeN0+#kWR+urcQMcJ*SF-sT&rnflVFjw
zkaKM%G=n;@$*_-qITl_M!syGG&7n||RE^1*D|;}$)4tfmhrz6AIbIZAnqq{yi*T%V
zHhVWtV;&u18I#tdIE_NpzkbrYc9VfMLtPX<ZYx1&B5Z9Om3}8VZ7FiPes9o-TDx%1
zw{6X{Wx%FXorW{5k~_krMv{3VMsze|t!`+}gJR5mHj+XnY9(-;Zb*VhCx$c1uU*Hh
zRmG=Gz@R^havV*1JY33nR>*Toyk;|+MLMZbK)_+XojHAH5m<yvR;FT5#&1i+YDL0k
zJHK6_k4uhwGlp|1i+C)!pi;J+M~ZYIf@~RtZ5D4_30jF#R;gq*y;+2KJ*J&=$it|K
zbtb^1PrI~}#=)h;zM@}|T^wm05?&D+Y8^J0MJ<m!6k`>&nL&4HC>d%SP=`*4d_$3X
zCoGaZfT(|KS__tZB7UZQKD%8OXc&KL7J+LO8+Rm0f=C!{9T#mJ7UDFE00001bW%=J
z06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru+XEXB
z6Cp(Rbcp}}0B%V{K~xyiU68R2z#t3+nS(tt!3~hY^vGg42FL=`EENNm0{M!6-y1;)
zQxFazvL_cK*b<_okFg0d8Hc#V=Eh`)h+yZmplehnlNZEdtgQ@hX0CNeIWEpxw!oyN
ndc~tsp9TlidOzOC&-)*|KH)3pD2ngr00000NkvXXu0mjf<KNUz

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable-sm.png b/web/pgadmin/misc/static/explain/img/exttable-sm.png
new file mode 100644
index 0000000000000000000000000000000000000000..acd43cb8abab6ef38daf34ff6d7544153a11dacf
GIT binary patch
literal 612
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47`X#{LR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)^XY$8$A0G@_?f)(TiB-0o~u9DE`4h_
z@3s2Om(8dDdI7mh-fGNz8JxJ$(RuCl>$mUSyLbQo{YQ@;J%0Rn>9T9)mKA9e*Bv-;
z!q9k*y7s}c^63vAJOIkOd8|>@*q@f(clYkyWy_XDM0Trbo^<nAscUdhS#3sp`^5F@
z*S~u8DlvJNy7pxi^{qR09)J1r<?Gk4&!4|-WV%sNW#!y?Ya^o8sA?W7EL!yO<Hxse
z-}X<~s-}6=(71d4{Dn`SK5cATs;+%VU9<bdiL;%Z6I)tVJ$drv&6_t59=vw<+ErTC
zQw{Rh4~>~Gc%JJ_00swRNswPKgTu2MX+REVfk$L9koEv$x0Bg+K*j`57sn8Z%gG4}
zOa@Af9Sp+8+}hIC?Ck2|<}MBG)BEEc0z4vIL{>Gq^Qb83DJpS_PM9=p;?&9E0U<$c
zTq{<rTA7`ZmHE2Bfwko87hay;9$%lc3z#+)6+IK17IrPoEP&B6+Pa$ET|Heqd_}|T
z-R;Y#Z{6O%UOwc2y~2g=!)h89Dk9JL?rkX8V67z4`B96(W2)e}i60vRfNoPQag8WR
zNi0dVN-jzTQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTd
cHGouG8JIydoSGiG2B?9-)78&qol`;+0LVQH?EnA(

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttable.png b/web/pgadmin/misc/static/explain/img/exttable.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d84035feea62d53d457481178b1bcbe9622f856
GIT binary patch
literal 793
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47;6K3LR^7d#i`GGFZ`c${`aEG
zUw7Sjd-49u`;VVLefIp>vuC#+K3#L}dGf{=db3}2pa0)_=3m{3Kc$C%<?Q>BxZ_*c
zrqAAMJ~%9YXTIQ#?wnU@GhViy`J27(hvTw$W((iw&U_Y~)1HyF{qEhn_wL=hfB*i2
z2M-=TeE8_mqsNaQ8yXiV>iW%}zd>1TV^GL$BjYL^{S(WU9oVt+prXnmSNFZkm#=*C
z<jL~o%k>TF^o@>PyZ-vst5*{zPLflcU|_QU>a|B#uU=JF?bJ2cziipY_3PKae*OB&
zm8%-sEqaEB)ikG?m>;=w=lPpAZ{EIr`|{<>zP<^HDjPJkuS}Y>_5J(zA3l8e^y$;P
zckfoNT)BGn>N#`fDXPqIbUAEeo3EjBs<&_JrcIlkKYzY=?-6CSwcft_U%!6i?2@ga
zbAH+K$Dcoc{`&RnzI`WD)Hj5LAKkcd)7Gt9lT#YiwN7;R-um|K+wv9LmDM(Tcy0Rl
z@#D5_+u{=nRW-JDc3)AL{8UY|HzVudq)BroPg$ri@$mwIO;dmoz*rLG7tG-B>_!@p
z!&%@FSq!8-z}W3%wjGdh+0(@_MB;LCLV^n;4-Zd|&l#OHId5d<%!#>Uvqyo^u8z@B
zF;Otku#nSp1DBm9qhscRhMqMtE)H>yfu51Bp}w)6&cZqimabjQ@@VyoS1+4cMR<97
zd#?KUoIRtfbCAhvS=cqRZDrs1`uxr%FfOPQ4>vF8_xIM%-_X$2-@kPI{CbCkfC7#P
z2U8Or8zmzxD>XAS4xIy%;`1lWcrs_sq*>GW#5zu&ICF}R^VG?+r}Y`QMZ`qYkF!mE
zGG*G-@CFvG)vNY8H+eN(zQmoCbx`Y7z#e{vFBY0Te&TE7f!<Rsag8WRNi0dVN-jzT
zQVd20h6cKZM!E)uAw~vPCdO7KCfWw3Rt5$ZGgakKH00)|WTsU@G#FTdHGouG8JIyd
UoSGiG2B?9-)78&qol`;+0Q{<O$^ZZW

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/img/exttables.png b/web/pgadmin/misc/static/explain/img/exttables.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5dfbd454ed33727bd1deeb87c62f8671f6a5a03
GIT binary patch
literal 662
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47^MPyLR^7d#i`G`&i$Km{^#1O
zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)(1uT&ufDx|_ipp)e^tkR=O6f)yz^Vw
zrq7<MKiDpPYdG(<`plR2?%iuX{nra9vgEDdjF%cSU*5le-z{*Hjoqv(SFS&L^yu;9
z$BCh54YV)SY`ps5!GohmFPT|vP}AIAUOws6@q1>vXAHE?^>wU&`SRt%hYy!6TNV*D
zT}|t>v;9UL?W?Amt7goYwSN8jSFc`8IQ7}h=Aef5HBHT(=Pz7){rdI(-PetE&Kqf+
zTC?J?x9>_dt@Dk|J3fB=`1bAF+~nQb+SeRSH>_B(;_1_;Rn;?8HFg`BEWUQ_zJ=bg
zri$H9o;-Q;=FOu=PwX9+YwPW<2KnxXa>IqjgMEvDp~F}b<QL4~@a#q!ki%Kv5m^kR
zJ;2!QWVRiUvDwqbF+}2W?0HwQLk<G27xhdW8XmB4zY~jTVks}b`QPk?8Z+O@ht}mE
zxDUR+@ZDiy$V$_wu1ha@gBJAuXUmw$IQdLNWLm+2wv7?FW$l-rva#qG$_eeUt7l;6
zYfsQPQ#3I{XWjwDPG7CLC)J97I!!#j(8XtG(AulgXTzi?C<q+hI5%(o`R%TodS@Ig
zFq!9*{+X5YhEDkFv(xUs<#&JSY%y0q+H23fcl??hCZB62%C0l7Vc_x-dugW}eiG<>
z)e_f;l9a@fRIB8oR3OD*WMF8ZYiOivU>IU#U}a)#Wn!XjU}|MxU@=ow4n;$5eoAIq
iB}9XPC0GMUwUvPxM8m1+p=*E|7(8A5T-G@yGywoZHZsuw

literal 0
HcmV?d00001

diff --git a/web/pgadmin/misc/static/explain/js/snap.svg-min.js b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
new file mode 100644
index 0000000..6567d19
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg-min.js
@@ -0,0 +1,21 @@
+// Snap.svg 0.4.1
+//
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// build: 2015-04-13
+
+!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g=/\s*,\s*/,h="*",i=function(a,b){return a-b},j={n:{}},k=function(){for(var a=0,b=this.length;b>a;a++)if("undefined"!=typeof this[a])return this[a]},l=function(){for(var a=this.length;--a;)if("undefined"!=typeof this[a])return this[a]},m=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=m.listeners(a),j=0,n=[],o={},p=[],q=b;p.firstDefined=k,p.lastDefined=l,b=a,c=0;for(var r=0,s=h.length;s>r;r++)"zIndex"in h[r]&&(n.push(h[r].zIndex),h[r].zIndex<0&&(o[h[r].zIndex]=h[r]));for(n.sort(i);n[j]<0;)if(e=o[n[j++]],p.push(e.apply(d,g)),c)return c=f,p;for(r=0;s>r;r++)if(e=h[r],"zIndex"in e)if(e.zIndex==n[j]){if(p.push(e.apply(d,g)),c)break;do if(j++,e=o[n[j]],e&&p.push(e.apply(d,g)),c)break;while(e)}else o[e.zIndex]=e;else if(p.push(e.apply(d,g)),c)break;return c=f,b=q,p};m._events=j,m.listeners=function(a){var b,c,d,e,g,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,g=m.length;g>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[h]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},m.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(g),d=0,e=c.length;e>d;d++)!function(a){for(var c,d=a.split(f),e=j,g=0,h=d.length;h>g;g++)e=e.n,e=e.hasOwnProperty(d[g])&&e[d[g]]||(e[d[g]]={n:{}});for(e.f=e.f||[],g=0,h=e.f.length;h>g;g++)if(e.f[g]==b){c=!0;break}!c&&e.f.push(b)}(c[d]);return function(a){+a==+a&&(b.zIndex=+a)}},m.f=function(a){var b=[].slice.call(arguments,1);return function(){m.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},m.stop=function(){c=1},m.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},m.nts=function(){return b.split(f)},m.off=m.unbind=function(a,b){if(!a)return void(m._events=j={n:{}});var c=a.split(g);if(c.length>1)for(var d=0,i=c.length;i>d;d++)m.off(c[d],b);else{c=a.split(f);var k,l,n,d,i,o,p,q=[j];for(d=0,i=c.length;i>d;d++)for(o=0;o<q.length;o+=n.length-2){if(n=[o,1],k=q[o].n,c[d]!=h)k[c[d]]&&n.push(k[c[d]]);else for(l in k)k[e](l)&&n.push(k[l]);q.splice.apply(q,n)}for(d=0,i=q.length;i>d;d++)for(k=q[d];k.n;){if(b){if(k.f){for(o=0,p=k.f.length;p>o;o++)if(k.f[o]==b){k.f.splice(o,1);break}!k.f.length&&delete k.f}for(l in k.n)if(k.n[e](l)&&k.n[l].f){var r=k.n[l].f;for(o=0,p=r.length;p>o;o++)if(r[o]==b){r.splice(o,1);break}!r.length&&delete k.n[l].f}}else{delete k.f;for(l in k.n)k.n[e](l)&&k.n[l].f&&delete k.n[l].f}k=k.n}}},m.once=function(a,b){var c=function(){return m.unbind(a,c),b.apply(this,arguments)};return m.on(a,c)},m.version=d,m.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=m:"function"==typeof define&&define.amd?define("eve",[],function(){return m}):a.eve=m}(this),function(a,b){if("function"==typeof define&&define.amd)define(["eve"],function(c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("eve");module.exports=b(a,c)}else b(a,a.eve)}(window||this,function(a,b){var c=function(b){var c={},d=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},e=Array.isArray||function(a){return a instanceof Array||"[object Array]"==Object.prototype.toString.call(a)},f=0,g="M"+(+new Date).toString(36),h=function(){return g+(f++).toString(36)},i=Date.now||function(){return+new Date},j=function(a){var b=this;if(null==a)return b.s;var c=b.s-a;b.b+=b.dur*c,b.B+=b.dur*c,b.s=a},k=function(a){var b=this;return null==a?b.spd:void(b.spd=a)},l=function(a){var b=this;return null==a?b.dur:(b.s=b.s*a/b.dur,void(b.dur=a))},m=function(){var a=this;delete c[a.id],a.update(),b("mina.stop."+a.id,a)},n=function(){var a=this;a.pdif||(delete c[a.id],a.update(),a.pdif=a.get()-a.b)},o=function(){var a=this;a.pdif&&(a.b=a.get()-a.pdif,delete a.pdif,c[a.id]=a)},p=function(){var a,b=this;if(e(b.start)){a=[];for(var c=0,d=b.start.length;d>c;c++)a[c]=+b.start[c]+(b.end[c]-b.start[c])*b.easing(b.s)}else a=+b.start+(b.end-b.start)*b.easing(b.s);b.set(a)},q=function(){var a=0;for(var e in c)if(c.hasOwnProperty(e)){var f=c[e],g=f.get();a++,f.s=(g-f.b)/(f.dur/f.spd),f.s>=1&&(delete c[e],f.s=1,a--,function(a){setTimeout(function(){b("mina.finish."+a.id,a)})}(f)),f.update()}a&&d(q)},r=function(a,b,e,f,g,i,s){var t={id:h(),start:a,end:b,b:e,s:0,dur:f-e,spd:1,get:g,set:i,easing:s||r.linear,status:j,speed:k,duration:l,stop:m,pause:n,resume:o,update:p};c[t.id]=t;var u,v=0;for(u in c)if(c.hasOwnProperty(u)&&(v++,2==v))break;return 1==v&&d(q),t};return r.time=i,r.getById=function(a){return c[a]||null},r.linear=function(a){return a},r.easeout=function(a){return Math.pow(a,1.7)},r.easein=function(a){return Math.pow(a,.48)},r.easeinout=function(a){if(1==a)return 1;if(0==a)return 0;var b=.48-a/1.04,c=Math.sqrt(.1734+b*b),d=c-b,e=Math.pow(Math.abs(d),1/3)*(0>d?-1:1),f=-c-b,g=Math.pow(Math.abs(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},r.backin=function(a){if(1==a)return 1;var b=1.70158;return a*a*((b+1)*a-b)},r.backout=function(a){if(0==a)return 0;a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},r.elastic=function(a){return a==!!a?a:Math.pow(2,-10*a)*Math.sin(2*(a-.075)*Math.PI/.3)+1},r.bounce=function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b},a.mina=r,r}("undefined"==typeof b?function(){}:b),d=function(a){function c(a,b){if(a){if(a.nodeType)return w(a);if(e(a,"array")&&c.set)return c.set.apply(c,a);if(a instanceof s)return a;if(null==b)return a=y.doc.querySelector(String(a)),w(a)}return a=null==a?"100%":a,b=null==b?"100%":b,new v(a,b)}function d(a,b){if(b){if("#text"==a&&(a=y.doc.createTextNode(b.text||b["#text"]||"")),"#comment"==a&&(a=y.doc.createComment(b.text||b["#text"]||"")),"string"==typeof a&&(a=d(a)),"string"==typeof b)return 1==a.nodeType?"xlink:"==b.substring(0,6)?a.getAttributeNS(T,b.substring(6)):"xml:"==b.substring(0,4)?a.getAttributeNS(U,b.substring(4)):a.getAttribute(b):"text"==b?a.nodeValue:null;if(1==a.nodeType){for(var c in b)if(b[z](c)){var e=A(b[c]);e?"xlink:"==c.substring(0,6)?a.setAttributeNS(T,c.substring(6),e):"xml:"==c.substring(0,4)?a.setAttributeNS(U,c.substring(4),e):a.setAttribute(c,e):a.removeAttribute(c)}}else"text"in b&&(a.nodeValue=b.text)}else a=y.doc.createElementNS(U,a);return a}function e(a,b){return b=A.prototype.toLowerCase.call(b),"finite"==b?isFinite(a):"array"==b&&(a instanceof Array||Array.isArray&&Array.isArray(a))?!0:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||J.call(a).slice(8,-1).toLowerCase()==b}function f(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=f(a[c]));return b}function h(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function i(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),g=d.cache=d.cache||{},i=d.count=d.count||[];return g[z](f)?(h(i,f),c?c(g[f]):g[f]):(i.length>=1e3&&delete g[i.shift()],i.push(f),g[f]=a.apply(b,e),c?c(g[f]):g[f])}return d}function j(a,b,c,d,e,f){if(null==e){var g=a-c,h=b-d;return g||h?(180+180*D.atan2(-h,-g)/H+360)%360:0}return j(a,b,e,f)-j(c,d,e,f)}function k(a){return a%360*H/180}function l(a){return 180*a/H%360}function m(a){var b=[];return a=a.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g,function(a,c,d){return d=d.split(/\s*,\s*|\s+/),"rotate"==c&&1==d.length&&d.push(0,0),"scale"==c&&(d.length>2?d=d.slice(0,2):2==d.length&&d.push(0,0),1==d.length&&d.push(d[0],0,0)),b.push("skewX"==c?["m",1,0,D.tan(k(d[0])),1,0,0]:"skewY"==c?["m",1,D.tan(k(d[0])),0,1,0,0]:[c.charAt(0)].concat(d)),a}),b}function n(a,b){var d=ab(a),e=new c.Matrix;if(d)for(var f=0,g=d.length;g>f;f++){var h,i,j,k,l,m=d[f],n=m.length,o=A(m[0]).toLowerCase(),p=m[0]!=o,q=p?e.invert():0;"t"==o&&2==n?e.translate(m[1],0):"t"==o&&3==n?p?(h=q.x(0,0),i=q.y(0,0),j=q.x(m[1],m[2]),k=q.y(m[1],m[2]),e.translate(j-h,k-i)):e.translate(m[1],m[2]):"r"==o?2==n?(l=l||b,e.rotate(m[1],l.x+l.width/2,l.y+l.height/2)):4==n&&(p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.rotate(m[1],j,k)):e.rotate(m[1],m[2],m[3])):"s"==o?2==n||3==n?(l=l||b,e.scale(m[1],m[n-1],l.x+l.width/2,l.y+l.height/2)):4==n?p?(j=q.x(m[2],m[3]),k=q.y(m[2],m[3]),e.scale(m[1],m[1],j,k)):e.scale(m[1],m[1],m[2],m[3]):5==n&&(p?(j=q.x(m[3],m[4]),k=q.y(m[3],m[4]),e.scale(m[1],m[2],j,k)):e.scale(m[1],m[2],m[3],m[4])):"m"==o&&7==n&&e.add(m[1],m[2],m[3],m[4],m[5],m[6])}return e}function o(a){var b=a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||a.node.parentNode&&w(a.node.parentNode)||c.select("svg")||c(0,0),d=b.select("defs"),e=null==d?!1:d.node;return e||(e=u("defs",b.node).node),e}function p(a){return a.node.ownerSVGElement&&w(a.node.ownerSVGElement)||c.select("svg")}function q(a,b,c){function e(a){if(null==a)return I;if(a==+a)return a;d(j,{width:a});try{return j.getBBox().width}catch(b){return 0}}function f(a){if(null==a)return I;if(a==+a)return a;d(j,{height:a});try{return j.getBBox().height}catch(b){return 0}}function g(d,e){null==b?i[d]=e(a.attr(d)||0):d==b&&(i=e(null==c?a.attr(d)||0:c))}var h=p(a).node,i={},j=h.querySelector(".svg---mgr");switch(j||(j=d("rect"),d(j,{x:-9e9,y:-9e9,width:10,height:10,"class":"svg---mgr",fill:"none"}),h.appendChild(j)),a.type){case"rect":g("rx",e),g("ry",f);case"image":g("width",e),g("height",f);case"text":g("x",e),g("y",f);break;case"circle":g("cx",e),g("cy",f),g("r",e);break;case"ellipse":g("cx",e),g("cy",f),g("rx",e),g("ry",f);break;case"line":g("x1",e),g("x2",e),g("y1",f),g("y2",f);break;case"marker":g("refX",e),g("markerWidth",e),g("refY",f),g("markerHeight",f);break;case"radialGradient":g("fx",e),g("fy",f);break;case"tspan":g("dx",e),g("dy",f);break;default:g(b,e)}return h.removeChild(j),i}function r(a){e(a,"array")||(a=Array.prototype.slice.call(arguments,0));for(var b=0,c=0,d=this.node;this[b];)delete this[b++];for(b=0;b<a.length;b++)"set"==a[b].type?a[b].forEach(function(a){d.appendChild(a.node)}):d.appendChild(a[b].node);var f=d.childNodes;for(b=0;b<f.length;b++)this[c++]=w(f[b]);return this}function s(a){if(a.snap in V)return V[a.snap];var b;try{b=a.ownerSVGElement}catch(c){}this.node=a,b&&(this.paper=new v(b)),this.type=a.tagName||a.nodeName;var d=this.id=S(this);if(this.anims={},this._={transform:[]},a.snap=d,V[d]=this,"g"==this.type&&(this.add=r),this.type in{g:1,mask:1,pattern:1,symbol:1})for(var e in v.prototype)v.prototype[z](e)&&(this[e]=v.prototype[e])}function t(a){this.node=a}function u(a,b){var c=d(a);b.appendChild(c);var e=w(c);return e}function v(a,b){var c,e,f,g=v.prototype;if(a&&"svg"==a.tagName){if(a.snap in V)return V[a.snap];var h=a.ownerDocument;c=new s(a),e=a.getElementsByTagName("desc")[0],f=a.getElementsByTagName("defs")[0],e||(e=d("desc"),e.appendChild(h.createTextNode("Created with Snap")),c.node.appendChild(e)),f||(f=d("defs"),c.node.appendChild(f)),c.defs=f;for(var i in g)g[z](i)&&(c[i]=g[i]);c.paper=c.root=c}else c=u("svg",y.doc.body),d(c.node,{height:b,version:1.1,width:a,xmlns:U});return c}function w(a){return a?a instanceof s||a instanceof t?a:a.tagName&&"svg"==a.tagName.toLowerCase()?new v(a):a.tagName&&"object"==a.tagName.toLowerCase()&&"image/svg+xml"==a.type?new v(a.contentDocument.getElementsByTagName("svg")[0]):new s(a):a}function x(a,b){for(var c=0,d=a.length;d>c;c++){var e={type:a[c].type,attr:a[c].attr()},f=a[c].children();b.push(e),f.length&&x(f,e.childNodes=[])}}c.version="0.4.0",c.toString=function(){return"Snap v"+this.version},c._={};var y={win:a.window,doc:a.window.document};c._.glob=y;{var z="hasOwnProperty",A=String,B=parseFloat,C=parseInt,D=Math,E=D.max,F=D.min,G=D.abs,H=(D.pow,D.PI),I=(D.round,""),J=Object.prototype.toString,K=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,L=(c._.separator=/[,\s]+/,/[\s]*,[\s]*/),M={hs:1,rg:1},N=/([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,O=/([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi,P=/(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/gi,Q=0,R="S"+(+new Date).toString(36),S=function(a){return(a&&a.type?a.type:I)+R+(Q++).toString(36)},T="http://www.w3.org/1999/xlink",U="http://www.w3.org/2000/svg",V={};c.url=function(a){return"url('#"+a+"')"}}c._.$=d,c._.id=S,c.format=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return A(b).replace(a,function(a,b){return c(a,b,d)})}}(),c._.clone=f,c._.cacher=i,c.rad=k,c.deg=l,c.sin=function(a){return D.sin(c.rad(a))},c.tan=function(a){return D.tan(c.rad(a))},c.cos=function(a){return D.cos(c.rad(a))},c.asin=function(a){return c.deg(D.asin(a))},c.acos=function(a){return c.deg(D.acos(a))},c.atan=function(a){return c.deg(D.atan(a))},c.atan2=function(a){return c.deg(D.atan2(a))},c.angle=j,c.len=function(a,b,d,e){return Math.sqrt(c.len2(a,b,d,e))},c.len2=function(a,b,c,d){return(a-c)*(a-c)+(b-d)*(b-d)},c.closestPoint=function(a,b,c){function d(a){var d=a.x-b,e=a.y-c;return d*d+e*e}for(var e,f,g,h,i=a.node,j=i.getTotalLength(),k=j/i.pathSegList.numberOfItems*.125,l=1/0,m=0;j>=m;m+=k)(h=d(g=i.getPointAtLength(m)))<l&&(e=g,f=m,l=h);for(k*=.5;k>.5;){var n,o,p,q,r,s;(p=f-k)>=0&&(r=d(n=i.getPointAtLength(p)))<l?(e=n,f=p,l=r):(q=f+k)<=j&&(s=d(o=i.getPointAtLength(q)))<l?(e=o,f=q,l=s):k*=.5}return e={x:e.x,y:e.y,length:f,distance:Math.sqrt(l)}},c.is=e,c.snapTo=function(a,b,c){if(c=e(c,"finite")?c:10,e(a,"array")){for(var d=a.length;d--;)if(G(a[d]-b)<=c)return a[d]}else{a=+a;var f=b%a;if(c>f)return b-f;if(f>a-c)return b-f+a}return b},c.getRGB=i(function(a){if(!a||(a=A(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:Z};if(!(M[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=W(a)),!a)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z};var b,d,f,g,h,i,j=a.match(K);return j?(j[2]&&(f=C(j[2].substring(5),16),d=C(j[2].substring(3,5),16),b=C(j[2].substring(1,3),16)),j[3]&&(f=C((h=j[3].charAt(3))+h,16),d=C((h=j[3].charAt(2))+h,16),b=C((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=B(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),f=B(i[2]),"%"==i[2].slice(-1)&&(f*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsb2rgb(b,d,f,g)):j[6]?(i=j[6].split(L),b=B(i[0]),"%"==i[0].slice(-1)&&(b/=100),d=B(i[1]),"%"==i[1].slice(-1)&&(d/=100),f=B(i[2]),"%"==i[2].slice(-1)&&(f/=100),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=B(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),c.hsl2rgb(b,d,f,g)):(b=F(D.round(b),255),d=F(D.round(d),255),f=F(D.round(f),255),g=F(E(g,0),1),j={r:b,g:d,b:f,toString:Z},j.hex="#"+(16777216|f|d<<8|b<<16).toString(16).slice(1),j.opacity=e(g,"finite")?g:1,j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:Z}},c),c.hsb=i(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=i(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=i(function(a,b,c,d){if(e(d,"finite")){var f=D.round;return"rgba("+[f(a),f(b),f(c),+d.toFixed(2)]+")"}return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)});var W=function(a){var b=y.doc.getElementsByTagName("head")[0]||y.doc.getElementsByTagName("svg")[0],c="rgb(255, 0, 0)";return(W=i(function(a){if("red"==a.toLowerCase())return c;b.style.color=c,b.style.color=a;var d=y.doc.defaultView.getComputedStyle(b,I).getPropertyValue("color");return d==c?null:d}))(a)},X=function(){return"hsb("+[this.h,this.s,this.b]+")"},Y=function(){return"hsl("+[this.h,this.s,this.l]+")"},Z=function(){return 1==this.opacity||null==this.opacity?this.hex:"rgba("+[this.r,this.g,this.b,this.opacity]+")"},$=function(a,b,d){if(null==b&&e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&e(a,string)){var f=c.getRGB(a);a=f.r,b=f.g,d=f.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},_=function(a,b,d,f){a=D.round(255*a),b=D.round(255*b),d=D.round(255*d);var g={r:a,g:b,b:d,opacity:e(f,"finite")?f:1,hex:c.rgb(a,b,d),toString:Z};return e(f,"finite")&&(g.opacity=f),g};c.color=function(a){var b;return e(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):e(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.opacity=1,a.hex=b.hex):(e(a,"string")&&(a=c.getRGB(a)),e(a,"object")&&"r"in a&&"g"in a&&"b"in a&&!("error"in a)?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1,a.error=1)),a.toString=Z,a},c.hsb2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var f,g,h,i,j;return a=a%360/60,j=c*b,i=j*(1-G(a%2-1)),f=g=h=c-j,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.hsl2rgb=function(a,b,c,d){e(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var f,g,h,i,j;return a=a%360/60,j=2*b*(.5>c?c:1-c),i=j*(1-G(a%2-1)),f=g=h=c-j/2,a=~~a,f+=[j,i,0,0,i,j][a],g+=[i,j,j,i,0,0][a],h+=[0,0,i,j,j,i][a],_(f,g,h,d)},c.rgb2hsb=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=E(a,b,c),g=f-F(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:X}},c.rgb2hsl=function(a,b,c){c=$(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=E(a,b,c),h=F(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:Y}},c.parsePathString=function(a){if(!a)return null;var b=c.path(a);if(b.arr)return c.path.clone(b.arr);var d={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},f=[];return e(a,"array")&&e(a[0],"array")&&(f=c.path.clone(a)),f.length||A(a).replace(N,function(a,b,c){var e=[],g=b.toLowerCase();if(c.replace(P,function(a,b){b&&e.push(+b)}),"m"==g&&e.length>2&&(f.push([b].concat(e.splice(0,2))),g="l",b="m"==b?"l":"L"),"o"==g&&1==e.length&&f.push([b,e[0]]),"r"==g)f.push([b].concat(e));else for(;e.length>=d[g]&&(f.push([b].concat(e.splice(0,d[g]))),d[g]););}),f.toString=c.path.toString,b.arr=c.path.clone(f),f};var ab=c.parseTransformString=function(a){if(!a)return null;var b=[];return e(a,"array")&&e(a[0],"array")&&(b=c.path.clone(a)),b.length||A(a).replace(O,function(a,c,d){{var e=[];c.toLowerCase()}d.replace(P,function(a,b){b&&e.push(+b)}),b.push([c].concat(e))}),b.toString=c.path.toString,b};c._.svgTransform2string=m,c._.rgTransform=/^[a-z][\s]*-?\.?\d/i,c._.transform2matrix=n,c._unit2px=q;y.doc.contains||y.doc.compareDocumentPosition?function(a,b){var c=9==a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a==d||!(!d||1!=d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)for(;b;)if(b=b.parentNode,b==a)return!0;return!1};c._.getSomeDefs=o,c._.getSomeSVG=p,c.select=function(a){return a=A(a).replace(/([^\\]):/g,"$1\\:"),w(y.doc.querySelector(a))},c.selectAll=function(a){for(var b=y.doc.querySelectorAll(a),d=(c.set||Array)(),e=0;e<b.length;e++)d.push(w(b[e]));return d},setInterval(function(){for(var a in V)if(V[z](a)){var b=V[a],c=b.node;("svg"!=b.type&&!c.ownerSVGElement||"svg"==b.type&&(!c.parentNode||"ownerSVGElement"in c.parentNode&&!c.ownerSVGElement))&&delete V[a]}},1e4),s.prototype.attr=function(a,c){var d=this,f=d.node;if(!a){if(1!=f.nodeType)return{text:f.nodeValue};for(var g=f.attributes,h={},i=0,j=g.length;j>i;i++)h[g[i].nodeName]=g[i].nodeValue;return h}if(e(a,"string")){if(!(arguments.length>1))return b("snap.util.getattr."+a,d).firstDefined();var k={};k[a]=c,a=k}for(var l in a)a[z](l)&&b("snap.util.attr."+l,d,a[l]);return d},c.parse=function(a){var b=y.doc.createDocumentFragment(),c=!0,d=y.doc.createElement("div");if(a=A(a),a.match(/^\s*<\s*svg(?:\s|>)/)||(a="<svg>"+a+"</svg>",c=!1),d.innerHTML=a,a=d.getElementsByTagName("svg")[0])if(c)b=a;else for(;a.firstChild;)b.appendChild(a.firstChild);return new t(b)},c.fragment=function(){for(var a=Array.prototype.slice.call(arguments,0),b=y.doc.createDocumentFragment(),d=0,e=a.length;e>d;d++){var f=a[d];f.node&&f.node.nodeType&&b.appendChild(f.node),f.nodeType&&b.appendChild(f),"string"==typeof f&&b.appendChild(c.parse(f).node)}return new t(b)},c._.make=u,c._.wrap=w,v.prototype.el=function(a,b){var c=u(a,this.node);return b&&c.attr(b),c},s.prototype.children=function(){for(var a=[],b=this.node.childNodes,d=0,e=b.length;e>d;d++)a[d]=c(b[d]);return a},s.prototype.toJSON=function(){var a=[];return x([this],a),a[0]},b.on("snap.util.getattr",function(){var a=b.nt();a=a.substring(a.lastIndexOf(".")+1);var c=a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});return bb[z](c)?this.node.ownerDocument.defaultView.getComputedStyle(this.node,null).getPropertyValue(c):d(this.node,a)});var bb={"alignment-baseline":0,"baseline-shift":0,clip:0,"clip-path":0,"clip-rule":0,color:0,"color-interpolation":0,"color-interpolation-filters":0,"color-profile":0,"color-rendering":0,cursor:0,direction:0,display:0,"dominant-baseline":0,"enable-background":0,fill:0,"fill-opacity":0,"fill-rule":0,filter:0,"flood-color":0,"flood-opacity":0,font:0,"font-family":0,"font-size":0,"font-size-adjust":0,"font-stretch":0,"font-style":0,"font-variant":0,"font-weight":0,"glyph-orientation-horizontal":0,"glyph-orientation-vertical":0,"image-rendering":0,kerning:0,"letter-spacing":0,"lighting-color":0,marker:0,"marker-end":0,"marker-mid":0,"marker-start":0,mask:0,opacity:0,overflow:0,"pointer-events":0,"shape-rendering":0,"stop-color":0,"stop-opacity":0,stroke:0,"stroke-dasharray":0,"stroke-dashoffset":0,"stroke-linecap":0,"stroke-linejoin":0,"stroke-miterlimit":0,"stroke-opacity":0,"stroke-width":0,"text-anchor":0,"text-decoration":0,"text-rendering":0,"unicode-bidi":0,visibility:0,"word-spacing":0,"writing-mode":0};b.on("snap.util.attr",function(a){var c=b.nt(),e={};c=c.substring(c.lastIndexOf(".")+1),e[c]=a;var f=c.replace(/-(\w)/gi,function(a,b){return b.toUpperCase()}),g=c.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()});bb[z](g)?this.node.style[f]=null==a?I:a:d(this.node,e)}),function(){}(v.prototype),c.ajax=function(a,c,d,f){var g=new XMLHttpRequest,h=S();if(g){if(e(c,"function"))f=d,d=c,c=null;else if(e(c,"object")){var i=[];for(var j in c)c.hasOwnProperty(j)&&i.push(encodeURIComponent(j)+"="+encodeURIComponent(c[j]));c=i.join("&")}return g.open(c?"POST":"GET",a,!0),c&&(g.setRequestHeader("X-Requested-With","XMLHttpRequest"),g.setRequestHeader("Content-type","application/x-www-form-urlencoded")),d&&(b.once("snap.ajax."+h+".0",d),b.once("snap.ajax."+h+".200",d),b.once("snap.ajax."+h+".304",d)),g.onreadystatechange=function(){4==g.readyState&&b("snap.ajax."+h+"."+g.status,f,g)},4==g.readyState?g:(g.send(c),g)}},c.load=function(a,b,d){c.ajax(a,function(a){var e=c.parse(a.responseText);d?b.call(d,e):b(e)})};var cb=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,h=e.clientLeft||d.clientLeft||0,i=b.top+(g.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(g.win.pageXOffset||e.scrollLeft||d.scrollLeft)-h;return{y:i,x:j}};return c.getElementByPoint=function(a,b){var c=this,d=(c.canvas,y.doc.elementFromPoint(a,b));if(y.win.opera&&"svg"==d.tagName){var e=cb(d),f=d.createSVGRect();f.x=a-e.x,f.y=b-e.y,f.width=f.height=1;var g=d.getIntersectionList(f,null);g.length&&(d=g[g.length-1])}return d?w(d):null},c.plugin=function(a){a(c,s,v,y,t)},y.win.Snap=c,c}(a||this);return d.plugin(function(d,e,f,g,h){function i(a,b){if(null==b){var c=!0;if(b=a.node.getAttribute("linearGradient"==a.type||"radialGradient"==a.type?"gradientTransform":"pattern"==a.type?"patternTransform":"transform"),!b)return new d.Matrix;b=d._.svgTransform2string(b)}else b=d._.rgTransform.test(b)?o(b).replace(/\.{3}|\u2026/g,a._.transform||""):d._.svgTransform2string(b),n(b,"array")&&(b=d.path?d.path.toString.call(b):o(b)),a._.transform=b;var e=d._.transform2matrix(b,a.getBBox(1));return c?e:void(a.matrix=e)}function j(a){function b(a,b){var c=q(a.node,b);c=c&&c.match(f),c=c&&c[2],c&&"#"==c.charAt()&&(c=c.substring(1),c&&(h[c]=(h[c]||[]).concat(function(c){var d={};d[b]=URL(c),q(a.node,d)})))}function c(a){var b=q(a.node,"xlink:href");b&&"#"==b.charAt()&&(b=b.substring(1),b&&(h[b]=(h[b]||[]).concat(function(b){a.attr("xlink:href","#"+b)})))}for(var d,e=a.selectAll("*"),f=/^\s*url\(("|'|)(.*)\1\)\s*$/,g=[],h={},i=0,j=e.length;j>i;i++){d=e[i],b(d,"fill"),b(d,"stroke"),b(d,"filter"),b(d,"mask"),b(d,"clip-path"),c(d);var k=q(d.node,"id");k&&(q(d.node,{id:d.id}),g.push({old:k,id:d.id}))}for(i=0,j=g.length;j>i;i++){var l=h[g[i].old];if(l)for(var m=0,n=l.length;n>m;m++)l[m](g[i].id)}}function k(a,b,c){return function(d){var e=d.slice(a,b);return 1==e.length&&(e=e[0]),c?c(e):e}}function l(a){return function(){var b=a?"<"+this.type:"",c=this.node.attributes,d=this.node.childNodes;if(a)for(var e=0,f=c.length;f>e;e++)b+=" "+c[e].name+'="'+c[e].value.replace(/"/g,'\\"')+'"';if(d.length){for(a&&(b+=">"),e=0,f=d.length;f>e;e++)3==d[e].nodeType?b+=d[e].nodeValue:1==d[e].nodeType&&(b+=u(d[e]).toString());a&&(b+="</"+this.type+">")}else a&&(b+="/>");return b}}var m=e.prototype,n=d.is,o=String,p=d._unit2px,q=d._.$,r=d._.make,s=d._.getSomeDefs,t="hasOwnProperty",u=d._.wrap;m.getBBox=function(a){if(!d.Matrix||!d.path)return this.node.getBBox();var b=this,c=new d.Matrix;if(b.removed)return d._.box();for(;"use"==b.type;)if(a||(c=c.add(b.transform().localMatrix.translate(b.attr("x")||0,b.attr("y")||0))),b.original)b=b.original;else{var e=b.attr("xlink:href");b=b.original=b.node.ownerDocument.getElementById(e.substring(e.indexOf("#")+1))}var f=b._,g=d.path.get[b.type]||d.path.get.deflt;try{return a?(f.bboxwt=g?d.path.getBBox(b.realPath=g(b)):d._.box(b.node.getBBox()),d._.box(f.bboxwt)):(b.realPath=g(b),b.matrix=b.transform().localMatrix,f.bbox=d.path.getBBox(d.path.map(b.realPath,c.add(b.matrix))),d._.box(f.bbox))}catch(h){return d._.box()}};var v=function(){return this.string};m.transform=function(a){var b=this._;if(null==a){for(var c,e=this,f=new d.Matrix(this.node.getCTM()),g=i(this),h=[g],j=new d.Matrix,k=g.toTransformString(),l=o(g)==o(this.matrix)?o(b.transform):k;"svg"!=e.type&&(e=e.parent());)h.push(i(e));for(c=h.length;c--;)j.add(h[c]);return{string:l,globalMatrix:f,totalMatrix:j,localMatrix:g,diffMatrix:f.clone().add(g.invert()),global:f.toTransformString(),total:j.toTransformString(),local:k,toString:v}}return a instanceof d.Matrix?(this.matrix=a,this._.transform=a.toTransformString()):i(this,a),this.node&&("linearGradient"==this.type||"radialGradient"==this.type?q(this.node,{gradientTransform:this.matrix}):"pattern"==this.type?q(this.node,{patternTransform:this.matrix}):q(this.node,{transform:this.matrix})),this},m.parent=function(){return u(this.node.parentNode)},m.append=m.add=function(a){if(a){if("set"==a.type){var b=this;return a.forEach(function(a){b.add(a)}),this}a=u(a),this.node.appendChild(a.node),a.paper=this.paper}return this},m.appendTo=function(a){return a&&(a=u(a),a.append(this)),this},m.prepend=function(a){if(a){if("set"==a.type){var b,c=this;return a.forEach(function(a){b?b.after(a):c.prepend(a),b=a}),this}a=u(a);var d=a.parent();this.node.insertBefore(a.node,this.node.firstChild),this.add&&this.add(),a.paper=this.paper,this.parent()&&this.parent().add(),d&&d.add()}return this},m.prependTo=function(a){return a=u(a),a.prepend(this),this},m.before=function(a){if("set"==a.type){var b=this;return a.forEach(function(a){var c=a.parent();b.node.parentNode.insertBefore(a.node,b.node),c&&c.add()}),this.parent().add(),this}a=u(a);var c=a.parent();return this.node.parentNode.insertBefore(a.node,this.node),this.parent()&&this.parent().add(),c&&c.add(),a.paper=this.paper,this},m.after=function(a){a=u(a);var b=a.parent();return this.node.nextSibling?this.node.parentNode.insertBefore(a.node,this.node.nextSibling):this.node.parentNode.appendChild(a.node),this.parent()&&this.parent().add(),b&&b.add(),a.paper=this.paper,this},m.insertBefore=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.insertAfter=function(a){a=u(a);var b=this.parent();return a.node.parentNode.insertBefore(this.node,a.node.nextSibling),this.paper=a.paper,b&&b.add(),a.parent()&&a.parent().add(),this},m.remove=function(){var a=this.parent();return this.node.parentNode&&this.node.parentNode.removeChild(this.node),delete this.paper,this.removed=!0,a&&a.add(),this},m.select=function(a){return u(this.node.querySelector(a))},m.selectAll=function(a){for(var b=this.node.querySelectorAll(a),c=(d.set||Array)(),e=0;e<b.length;e++)c.push(u(b[e]));return c},m.asPX=function(a,b){return null==b&&(b=this.attr(a)),+p(this,a,b)},m.use=function(){var a,b=this.node.id;return b||(b=this.id,q(this.node,{id:b})),a="linearGradient"==this.type||"radialGradient"==this.type||"pattern"==this.type?r(this.type,this.node.parentNode):r("use",this.node.parentNode),q(a.node,{"xlink:href":"#"+b}),a.original=this,a},m.clone=function(){var a=u(this.node.cloneNode(!0));return q(a.node,"id")&&q(a.node,{id:a.id}),j(a),a.insertAfter(this),a},m.toDefs=function(){var a=s(this);return a.appendChild(this.node),this},m.pattern=m.toPattern=function(a,b,c,d){var e=r("pattern",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,a=a.x),q(e.node,{x:a,y:b,width:c,height:d,patternUnits:"userSpaceOnUse",id:e.id,viewBox:[a,b,c,d].join(" ")}),e.node.appendChild(this.node),e},m.marker=function(a,b,c,d,e,f){var g=r("marker",s(this));return null==a&&(a=this.getBBox()),n(a,"object")&&"x"in a&&(b=a.y,c=a.width,d=a.height,e=a.refX||a.cx,f=a.refY||a.cy,a=a.x),q(g.node,{viewBox:[a,b,c,d].join(" "),markerWidth:c,markerHeight:d,orient:"auto",refX:e||0,refY:f||0,id:g.id}),g.node.appendChild(this.node),g};var w=function(a,b,d,e){"function"!=typeof d||d.length||(e=d,d=c.linear),this.attr=a,this.dur=b,d&&(this.easing=d),e&&(this.callback=e)};d._.Animation=w,d.animation=function(a,b,c,d){return new w(a,b,c,d)},m.inAnim=function(){var a=this,b=[];for(var c in a.anims)a.anims[t](c)&&!function(a){b.push({anim:new w(a._attrs,a.dur,a.easing,a._callback),mina:a,curStatus:a.status(),status:function(b){return a.status(b)},stop:function(){a.stop()}})}(a.anims[c]);return b},d.animate=function(a,d,e,f,g,h){"function"!=typeof g||g.length||(h=g,g=c.linear);var i=c.time(),j=c(a,d,i,i+f,c.time,e,g);return h&&b.once("mina.finish."+j.id,h),j},m.stop=function(){for(var a=this.inAnim(),b=0,c=a.length;c>b;b++)a[b].stop();return this},m.animate=function(a,d,e,f){"function"!=typeof e||e.length||(f=e,e=c.linear),a instanceof w&&(f=a.callback,e=a.easing,d=a.dur,a=a.attr);var g,h,i,j,l=[],m=[],p={},q=this;for(var r in a)if(a[t](r)){q.equal?(j=q.equal(r,o(a[r])),g=j.from,h=j.to,i=j.f):(g=+q.attr(r),h=+a[r]);var s=n(g,"array")?g.length:1;p[r]=k(l.length,l.length+s,i),l=l.concat(g),m=m.concat(h)}var u=c.time(),v=c(l,m,u,u+d,c.time,function(a){var b={};for(var c in p)p[t](c)&&(b[c]=p[c](a));q.attr(b)},e);return q.anims[v.id]=v,v._attrs=a,v._callback=f,b("snap.animcreated."+q.id,v),b.once("mina.finish."+v.id,function(){delete q.anims[v.id],f&&f.call(q)}),b.once("mina.stop."+v.id,function(){delete q.anims[v.id]}),q};var x={};m.data=function(a,c){var e=x[this.id]=x[this.id]||{};if(0==arguments.length)return b("snap.data.get."+this.id,this,e,null),e;
+if(1==arguments.length){if(d.is(a,"object")){for(var f in a)a[t](f)&&this.data(f,a[f]);return this}return b("snap.data.get."+this.id,this,e[a],a),e[a]}return e[a]=c,b("snap.data.set."+this.id,this,c,a),this},m.removeData=function(a){return null==a?x[this.id]={}:x[this.id]&&delete x[this.id][a],this},m.outerSVG=m.toString=l(1),m.innerSVG=l(),m.toDataURL=function(){if(a&&a.btoa){var b=this.getBBox(),c=d.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>',{x:+b.x.toFixed(3),y:+b.y.toFixed(3),width:+b.width.toFixed(3),height:+b.height.toFixed(3),contents:this.outerSVG()});return"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(c)))}},h.prototype.select=m.select,h.prototype.selectAll=m.selectAll}),d.plugin(function(a){function b(a,b,d,e,f,g){return null==b&&"[object SVGMatrix]"==c.call(a)?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,void(this.f=a.f)):void(null!=a?(this.a=+a,this.b=+b,this.c=+d,this.d=+e,this.e=+f,this.f=+g):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0))}var c=Object.prototype.toString,d=String,e=Math,f="";!function(c){function g(a){return a[0]*a[0]+a[1]*a[1]}function h(a){var b=e.sqrt(g(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}c.add=function(a,c,d,e,f,g){var h,i,j,k,l=[[],[],[]],m=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],n=[[a,d,f],[c,e,g],[0,0,1]];for(a&&a instanceof b&&(n=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),h=0;3>h;h++)for(i=0;3>i;i++){for(k=0,j=0;3>j;j++)k+=m[h][j]*n[j][i];l[h][i]=k}return this.a=l[0][0],this.b=l[1][0],this.c=l[0][1],this.d=l[1][1],this.e=l[0][2],this.f=l[1][2],this},c.invert=function(){var a=this,c=a.a*a.d-a.b*a.c;return new b(a.d/c,-a.b/c,-a.c/c,a.a/c,(a.c*a.f-a.d*a.e)/c,(a.b*a.e-a.a*a.f)/c)},c.clone=function(){return new b(this.a,this.b,this.c,this.d,this.e,this.f)},c.translate=function(a,b){return this.add(1,0,0,1,a,b)},c.scale=function(a,b,c,d){return null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d),this},c.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var f=+e.cos(b).toFixed(9),g=+e.sin(b).toFixed(9);return this.add(f,g,-g,f,c,d),this.add(1,0,0,1,-c,-d)},c.x=function(a,b){return a*this.a+b*this.c+this.e},c.y=function(a,b){return a*this.b+b*this.d+this.f},c.get=function(a){return+this[d.fromCharCode(97+a)].toFixed(4)},c.toString=function(){return"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")"},c.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},c.determinant=function(){return this.a*this.d-this.b*this.c},c.split=function(){var b={};b.dx=this.e,b.dy=this.f;var c=[[this.a,this.c],[this.b,this.d]];b.scalex=e.sqrt(g(c[0])),h(c[0]),b.shear=c[0][0]*c[1][0]+c[0][1]*c[1][1],c[1]=[c[1][0]-c[0][0]*b.shear,c[1][1]-c[0][1]*b.shear],b.scaley=e.sqrt(g(c[1])),h(c[1]),b.shear/=b.scaley,this.determinant()<0&&(b.scalex=-b.scalex);var d=-c[0][1],f=c[1][1];return 0>f?(b.rotate=a.deg(e.acos(f)),0>d&&(b.rotate=360-b.rotate)):b.rotate=a.deg(e.asin(d)),b.isSimple=!(+b.shear.toFixed(9)||b.scalex.toFixed(9)!=b.scaley.toFixed(9)&&b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate,b},c.toTransformString=function(a){var b=a||this.split();return+b.shear.toFixed(9)?"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]:(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[+b.dx.toFixed(4),+b.dy.toFixed(4)]:f)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:f)+(b.rotate?"r"+[+b.rotate.toFixed(4),0,0]:f))}}(b.prototype),a.Matrix=b,a.matrix=function(a,c,d,e,f,g){return new b(a,c,d,e,f,g)}}),d.plugin(function(a,c,d,e,f){function g(d){return function(e){if(b.stop(),e instanceof f&&1==e.node.childNodes.length&&("radialGradient"==e.node.firstChild.tagName||"linearGradient"==e.node.firstChild.tagName||"pattern"==e.node.firstChild.tagName)&&(e=e.node.firstChild,n(this).appendChild(e),e=l(e)),e instanceof c)if("radialGradient"==e.type||"linearGradient"==e.type||"pattern"==e.type){e.node.id||p(e.node,{id:e.id});var g=q(e.node.id)}else g=e.attr(d);else if(g=a.color(e),g.error){var h=a(n(this).ownerSVGElement).gradient(e);h?(h.node.id||p(h.node,{id:h.id}),g=q(h.node.id)):g=e}else g=r(g);var i={};i[d]=g,p(this.node,i),this.node.style[d]=t}}function h(a){b.stop(),a==+a&&(a+="px"),this.node.style.fontSize=a}function i(a){for(var b=[],c=a.childNodes,d=0,e=c.length;e>d;d++){var f=c[d];3==f.nodeType&&b.push(f.nodeValue),"tspan"==f.tagName&&b.push(1==f.childNodes.length&&3==f.firstChild.nodeType?f.firstChild.nodeValue:i(f))}return b}function j(){return b.stop(),this.node.style.fontSize}var k=a._.make,l=a._.wrap,m=a.is,n=a._.getSomeDefs,o=/^url\(#?([^)]+)\)$/,p=a._.$,q=a.url,r=String,s=a._.separator,t="";b.on("snap.util.attr.mask",function(a){if(a instanceof c||a instanceof f){if(b.stop(),a instanceof f&&1==a.node.childNodes.length&&(a=a.node.firstChild,n(this).appendChild(a),a=l(a)),"mask"==a.type)var d=a;else d=k("mask",n(this)),d.node.appendChild(a.node);!d.node.id&&p(d.node,{id:d.id}),p(this.node,{mask:q(d.id)})}}),function(a){b.on("snap.util.attr.clip",a),b.on("snap.util.attr.clip-path",a),b.on("snap.util.attr.clipPath",a)}(function(a){if(a instanceof c||a instanceof f){if(b.stop(),"clipPath"==a.type)var d=a;else d=k("clipPath",n(this)),d.node.appendChild(a.node),!d.node.id&&p(d.node,{id:d.id});p(this.node,{"clip-path":q(d.node.id||d.id)})}}),b.on("snap.util.attr.fill",g("fill")),b.on("snap.util.attr.stroke",g("stroke"));var u=/^([lr])(?:\(([^)]*)\))?(.*)$/i;b.on("snap.util.grad.parse",function(a){a=r(a);var b=a.match(u);if(!b)return null;var c=b[1],d=b[2],e=b[3];return d=d.split(/\s*,\s*/).map(function(a){return+a==a?+a:a}),1==d.length&&0==d[0]&&(d=[]),e=e.split("-"),e=e.map(function(a){a=a.split(":");var b={color:a[0]};return a[1]&&(b.offset=parseFloat(a[1])),b}),{type:c,params:d,stops:e}}),b.on("snap.util.attr.d",function(c){b.stop(),m(c,"array")&&m(c[0],"array")&&(c=a.path.toString.call(c)),c=r(c),c.match(/[ruo]/i)&&(c=a.path.toAbsolute(c)),p(this.node,{d:c})})(-1),b.on("snap.util.attr.#text",function(a){b.stop(),a=r(a);for(var c=e.doc.createTextNode(a);this.node.firstChild;)this.node.removeChild(this.node.firstChild);this.node.appendChild(c)})(-1),b.on("snap.util.attr.path",function(a){b.stop(),this.attr({d:a})})(-1),b.on("snap.util.attr.class",function(a){b.stop(),this.node.className.baseVal=a})(-1),b.on("snap.util.attr.viewBox",function(a){var c;c=m(a,"object")&&"x"in a?[a.x,a.y,a.width,a.height].join(" "):m(a,"array")?a.join(" "):a,p(this.node,{viewBox:c}),b.stop()})(-1),b.on("snap.util.attr.transform",function(a){this.transform(a),b.stop()})(-1),b.on("snap.util.attr.r",function(a){"rect"==this.type&&(b.stop(),p(this.node,{rx:a,ry:a}))})(-1),b.on("snap.util.attr.textpath",function(a){if(b.stop(),"text"==this.type){var d,e,f;if(!a&&this.textPath){for(e=this.textPath;e.node.firstChild;)this.node.appendChild(e.node.firstChild);return e.remove(),void delete this.textPath}if(m(a,"string")){var g=n(this),h=l(g.parentNode).path(a);g.appendChild(h.node),d=h.id,h.attr({id:d})}else a=l(a),a instanceof c&&(d=a.attr("id"),d||(d=a.id,a.attr({id:d})));if(d)if(e=this.textPath,f=this.node,e)e.attr({"xlink:href":"#"+d});else{for(e=p("textPath",{"xlink:href":"#"+d});f.firstChild;)e.appendChild(f.firstChild);f.appendChild(e),this.textPath=l(e)}}})(-1),b.on("snap.util.attr.text",function(a){if("text"==this.type){for(var c=this.node,d=function(a){var b=p("tspan");if(m(a,"array"))for(var c=0;c<a.length;c++)b.appendChild(d(a[c]));else b.appendChild(e.doc.createTextNode(a));return b.normalize&&b.normalize(),b};c.firstChild;)c.removeChild(c.firstChild);for(var f=d(a);f.firstChild;)c.appendChild(f.firstChild)}b.stop()})(-1),b.on("snap.util.attr.fontSize",h)(-1),b.on("snap.util.attr.font-size",h)(-1),b.on("snap.util.getattr.transform",function(){return b.stop(),this.transform()})(-1),b.on("snap.util.getattr.textpath",function(){return b.stop(),this.textPath})(-1),function(){function c(c){return function(){b.stop();var d=e.doc.defaultView.getComputedStyle(this.node,null).getPropertyValue("marker-"+c);return"none"==d?d:a(e.doc.getElementById(d.match(o)[1]))}}function d(a){return function(c){b.stop();var d="marker"+a.charAt(0).toUpperCase()+a.substring(1);if(""==c||!c)return void(this.node.style[d]="none");if("marker"==c.type){var e=c.node.id;return e||p(c.node,{id:c.id}),void(this.node.style[d]=q(e))}}}b.on("snap.util.getattr.marker-end",c("end"))(-1),b.on("snap.util.getattr.markerEnd",c("end"))(-1),b.on("snap.util.getattr.marker-start",c("start"))(-1),b.on("snap.util.getattr.markerStart",c("start"))(-1),b.on("snap.util.getattr.marker-mid",c("mid"))(-1),b.on("snap.util.getattr.markerMid",c("mid"))(-1),b.on("snap.util.attr.marker-end",d("end"))(-1),b.on("snap.util.attr.markerEnd",d("end"))(-1),b.on("snap.util.attr.marker-start",d("start"))(-1),b.on("snap.util.attr.markerStart",d("start"))(-1),b.on("snap.util.attr.marker-mid",d("mid"))(-1),b.on("snap.util.attr.markerMid",d("mid"))(-1)}(),b.on("snap.util.getattr.r",function(){return"rect"==this.type&&p(this.node,"rx")==p(this.node,"ry")?(b.stop(),p(this.node,"rx")):void 0})(-1),b.on("snap.util.getattr.text",function(){if("text"==this.type||"tspan"==this.type){b.stop();var a=i(this.node);return 1==a.length?a[0]:a}})(-1),b.on("snap.util.getattr.#text",function(){return this.node.textContent})(-1),b.on("snap.util.getattr.viewBox",function(){b.stop();var c=p(this.node,"viewBox");return c?(c=c.split(s),a._.box(+c[0],+c[1],+c[2],+c[3])):void 0})(-1),b.on("snap.util.getattr.points",function(){var a=p(this.node,"points");return b.stop(),a?a.split(s):void 0})(-1),b.on("snap.util.getattr.path",function(){var a=p(this.node,"d");return b.stop(),a})(-1),b.on("snap.util.getattr.class",function(){return this.node.className.baseVal})(-1),b.on("snap.util.getattr.fontSize",j)(-1),b.on("snap.util.getattr.font-size",j)(-1)}),d.plugin(function(a,b){var c=/\S+/g,d=String,e=b.prototype;e.addClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(h.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e||k.push(f);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.removeClass=function(a){var b,e,f,g,h=d(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];if(k.length){for(b=0;f=h[b++];)e=k.indexOf(f),~e&&k.splice(e,1);g=k.join(" "),j!=g&&(i.className.baseVal=g)}return this},e.hasClass=function(a){var b=this.node,d=b.className.baseVal,e=d.match(c)||[];return!!~e.indexOf(a)},e.toggleClass=function(a,b){if(null!=b)return b?this.addClass(a):this.removeClass(a);var d,e,f,g,h=(a||"").match(c)||[],i=this.node,j=i.className.baseVal,k=j.match(c)||[];for(d=0;f=h[d++];)e=k.indexOf(f),~e?k.splice(e,1):k.push(f);return g=k.join(" "),j!=g&&(i.className.baseVal=g),this}}),d.plugin(function(){function a(a){return a}function c(a){return function(b){return+b.toFixed(3)+a}}var d={"+":function(a,b){return a+b},"-":function(a,b){return a-b},"/":function(a,b){return a/b},"*":function(a,b){return a*b}},e=String,f=/[a-z]+$/i,g=/^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;b.on("snap.util.attr",function(a){var c=e(a).match(g);if(c){var h=b.nt(),i=h.substring(h.lastIndexOf(".")+1),j=this.attr(i),k={};b.stop();var l=c[3]||"",m=j.match(f),n=d[c[1]];if(m&&m==l?a=n(parseFloat(j),+c[2]):(j=this.asPX(i),a=n(this.asPX(i),this.asPX(i,c[2]+l))),isNaN(j)||isNaN(a))return;k[i]=a,this.attr(k)}})(-10),b.on("snap.util.equal",function(h,i){var j=e(this.attr(h)||""),k=e(i).match(g);if(k){b.stop();var l=k[3]||"",m=j.match(f),n=d[k[1]];return m&&m==l?{from:parseFloat(j),to:n(parseFloat(j),+k[2]),f:c(m)}:(j=this.asPX(h),{from:j,to:n(j,this.asPX(h,k[2]+l)),f:a})}})(-10)}),d.plugin(function(c,d,e,f){var g=e.prototype,h=c.is;g.rect=function(a,b,c,d,e,f){var g;return null==f&&(f=e),h(a,"object")&&"[object Object]"==a?g=a:null!=a&&(g={x:a,y:b,width:c,height:d},null!=e&&(g.rx=e,g.ry=f)),this.el("rect",g)},g.circle=function(a,b,c){var d;return h(a,"object")&&"[object Object]"==a?d=a:null!=a&&(d={cx:a,cy:b,r:c}),this.el("circle",d)};var i=function(){function a(){this.parentNode.removeChild(this)}return function(b,c){var d=f.doc.createElement("img"),e=f.doc.body;d.style.cssText="position:absolute;left:-9999em;top:-9999em",d.onload=function(){c.call(d),d.onload=d.onerror=null,e.removeChild(d)},d.onerror=a,e.appendChild(d),d.src=b}}();g.image=function(a,b,d,e,f){var g=this.el("image");if(h(a,"object")&&"src"in a)g.attr(a);else if(null!=a){var j={"xlink:href":a,preserveAspectRatio:"none"};null!=b&&null!=d&&(j.x=b,j.y=d),null!=e&&null!=f?(j.width=e,j.height=f):i(a,function(){c._.$(g.node,{width:this.offsetWidth,height:this.offsetHeight})}),c._.$(g.node,j)}return g},g.ellipse=function(a,b,c,d){var e;return h(a,"object")&&"[object Object]"==a?e=a:null!=a&&(e={cx:a,cy:b,rx:c,ry:d}),this.el("ellipse",e)},g.path=function(a){var b;return h(a,"object")&&!h(a,"array")?b=a:a&&(b={d:a}),this.el("path",b)},g.group=g.g=function(a){var b=this.el("g");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.svg=function(a,b,c,d,e,f,g,i){var j={};return h(a,"object")&&null==b?j=a:(null!=a&&(j.x=a),null!=b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),null!=e&&null!=f&&null!=g&&null!=i&&(j.viewBox=[e,f,g,i])),this.el("svg",j)},g.mask=function(a){var b=this.el("mask");return 1==arguments.length&&a&&!a.type?b.attr(a):arguments.length&&b.add(Array.prototype.slice.call(arguments,0)),b},g.ptrn=function(a,b,c,d,e,f,g,i){if(h(a,"object"))var j=a;else j={patternUnits:"userSpaceOnUse"},a&&(j.x=a),b&&(j.y=b),null!=c&&(j.width=c),null!=d&&(j.height=d),j.viewBox=null!=e&&null!=f&&null!=g&&null!=i?[e,f,g,i]:[a||0,b||0,c||0,d||0];return this.el("pattern",j)},g.use=function(a){return null!=a?(a instanceof d&&(a.attr("id")||a.attr({id:c._.id(a)}),a=a.attr("id")),"#"==String(a).charAt()&&(a=a.substring(1)),this.el("use",{"xlink:href":"#"+a})):d.prototype.use.call(this)},g.symbol=function(a,b,c,d){var e={};return null!=a&&null!=b&&null!=c&&null!=d&&(e.viewBox=[a,b,c,d]),this.el("symbol",e)},g.text=function(a,b,c){var d={};return h(a,"object")?d=a:null!=a&&(d={x:a,y:b,text:c||""}),this.el("text",d)},g.line=function(a,b,c,d){var e={};return h(a,"object")?e=a:null!=a&&(e={x1:a,x2:c,y1:b,y2:d}),this.el("line",e)},g.polyline=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polyline",b)},g.polygon=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0));var b={};return h(a,"object")&&!h(a,"array")?b=a:null!=a&&(b={points:a}),this.el("polygon",b)},function(){function d(){return this.selectAll("stop")}function e(a,b){var d=k("stop"),e={offset:+b+"%"};return a=c.color(a),e["stop-color"]=a.hex,a.opacity<1&&(e["stop-opacity"]=a.opacity),k(d,e),this.node.appendChild(d),this}function f(){if("linearGradient"==this.type){var a=k(this.node,"x1")||0,b=k(this.node,"x2")||1,d=k(this.node,"y1")||0,e=k(this.node,"y2")||0;return c._.box(a,d,math.abs(b-a),math.abs(e-d))}var f=this.node.cx||.5,g=this.node.cy||.5,h=this.node.r||0;return c._.box(f-h,g-h,2*h,2*h)}function h(a,c){function d(a,b){for(var c=(b-l)/(a-m),d=m;a>d;d++)g[d].offset=+(+l+c*(d-m)).toFixed(2);m=a,l=b}var e,f=b("snap.util.grad.parse",null,c).firstDefined();if(!f)return null;f.params.unshift(a),e="l"==f.type.toLowerCase()?i.apply(0,f.params):j.apply(0,f.params),f.type!=f.type.toLowerCase()&&k(e.node,{gradientUnits:"userSpaceOnUse"});var g=f.stops,h=g.length,l=0,m=0;h--;for(var n=0;h>n;n++)"offset"in g[n]&&d(n,g[n].offset);for(g[h].offset=g[h].offset||100,d(h,g[h].offset),n=0;h>=n;n++){var o=g[n];e.addStop(o.color,o.offset)}return e}function i(a,b,g,h,i){var j=c._.make("linearGradient",a);return j.stops=d,j.addStop=e,j.getBBox=f,null!=b&&k(j.node,{x1:b,y1:g,x2:h,y2:i}),j}function j(a,b,g,h,i,j){var l=c._.make("radialGradient",a);return l.stops=d,l.addStop=e,l.getBBox=f,null!=b&&k(l.node,{cx:b,cy:g,r:h}),null!=i&&null!=j&&k(l.node,{fx:i,fy:j}),l}var k=c._.$;g.gradient=function(a){return h(this.defs,a)},g.gradientLinear=function(a,b,c,d){return i(this.defs,a,b,c,d)},g.gradientRadial=function(a,b,c,d,e){return j(this.defs,a,b,c,d,e)},g.toString=function(){var a,b=this.node.ownerDocument,d=b.createDocumentFragment(),e=b.createElement("div"),f=this.node.cloneNode(!0);return d.appendChild(e),e.appendChild(f),c._.$(f,{xmlns:"http://www.w3.org/2000/svg"}),a=e.innerHTML,d.removeChild(d.firstChild),a},g.toDataURL=function(){return a&&a.btoa?"data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(this))):void 0},g.clear=function(){for(var a,b=this.node.firstChild;b;)a=b.nextSibling,"defs"!=b.tagName?b.parentNode.removeChild(b):g.clear.call({node:b}),b=a}}()}),d.plugin(function(a,b){function c(a){var b=c.ps=c.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[K](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]}function d(a,b,c,d){return null==a&&(a=b=c=d=0),null==b&&(b=a.y,c=a.width,d=a.height,a=a.x),{x:a,y:b,width:c,w:c,height:d,h:d,x2:a+c,y2:b+d,cx:a+c/2,cy:b+d/2,r1:N.min(c,d)/2,r2:N.max(c,d)/2,r0:N.sqrt(c*c+d*d)/2,path:w(a,b,c,d),vb:[a,b,c,d].join(" ")}}function e(){return this.join(",").replace(L,"$1")}function f(a){var b=J(a);return b.toString=e,b}function g(a,b,c,d,e,f,g,h,j){return null==j?n(a,b,c,d,e,f,g,h):i(a,b,c,d,e,f,g,h,o(a,b,c,d,e,f,g,h,j))}function h(c,d){function e(a){return+(+a).toFixed(3)}return a._.cacher(function(a,f,h){a instanceof b&&(a=a.attr("d")),a=E(a);for(var j,k,l,m,n,o="",p={},q=0,r=0,s=a.length;s>r;r++){if(l=a[r],"M"==l[0])j=+l[1],k=+l[2];else{if(m=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6]),q+m>f){if(d&&!p.start){if(n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q),o+=["C"+e(n.start.x),e(n.start.y),e(n.m.x),e(n.m.y),e(n.x),e(n.y)],h)return o;p.start=o,o=["M"+e(n.x),e(n.y)+"C"+e(n.n.x),e(n.n.y),e(n.end.x),e(n.end.y),e(l[5]),e(l[6])].join(),q+=m,j=+l[5],k=+l[6];continue}if(!c&&!d)return n=g(j,k,l[1],l[2],l[3],l[4],l[5],l[6],f-q)}q+=m,j=+l[5],k=+l[6]}o+=l.shift()+l}return p.end=o,n=c?q:d?p:i(j,k,l[0],l[1],l[2],l[3],l[4],l[5],1)},null,a._.clone)}function i(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/O;return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}}function j(b,c,e,f,g,h,i,j){a.is(b,"array")||(b=[b,c,e,f,g,h,i,j]);var k=D.apply(null,b);return d(k.min.x,k.min.y,k.max.x-k.min.x,k.max.y-k.min.y)}function k(a,b,c){return b>=a.x&&b<=a.x+a.width&&c>=a.y&&c<=a.y+a.height}function l(a,b){return a=d(a),b=d(b),k(b,a.x,a.y)||k(b,a.x2,a.y)||k(b,a.x,a.y2)||k(b,a.x2,a.y2)||k(a,b.x,b.y)||k(a,b.x2,b.y)||k(a,b.x,b.y2)||k(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)}function m(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function n(a,b,c,d,e,f,g,h,i){null==i&&(i=1),i=i>1?1:0>i?0:i;for(var j=i/2,k=12,l=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;k>p;p++){var q=j*l[p]+j,r=m(q,a,c,e,g),s=m(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return j*o}function o(a,b,c,d,e,f,g,h,i){if(!(0>i||n(a,b,c,d,e,f,g,h)<i)){var j,k=1,l=k/2,m=k-l,o=.01;for(j=n(a,b,c,d,e,f,g,h,m);S(j-i)>o;)l/=2,m+=(i>j?1:-1)*l,j=n(a,b,c,d,e,f,g,h,m);return m}}function p(a,b,c,d,e,f,g,h){if(!(Q(a,c)<P(e,g)||P(a,c)>Q(e,g)||Q(b,d)<P(f,h)||P(b,d)>Q(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+Q(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+Q(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+Q(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+Q(f,h).toFixed(2)))return{x:l,y:m}}}}function q(a,b,c){var d=j(a),e=j(b);if(!l(d,e))return c?0:[];for(var f=n.apply(0,a),g=n.apply(0,b),h=~~(f/8),k=~~(g/8),m=[],o=[],q={},r=c?0:[],s=0;h+1>s;s++){var t=i.apply(0,a.concat(s/h));m.push({x:t.x,y:t.y,t:s/h})}for(s=0;k+1>s;s++)t=i.apply(0,b.concat(s/k)),o.push({x:t.x,y:t.y,t:s/k});for(s=0;h>s;s++)for(var u=0;k>u;u++){var v=m[s],w=m[s+1],x=o[u],y=o[u+1],z=S(w.x-v.x)<.001?"y":"x",A=S(y.x-x.x)<.001?"y":"x",B=p(v.x,v.y,w.x,w.y,x.x,x.y,y.x,y.y);if(B){if(q[B.x.toFixed(4)]==B.y.toFixed(4))continue;q[B.x.toFixed(4)]=B.y.toFixed(4);var C=v.t+S((B[z]-v[z])/(w[z]-v[z]))*(w.t-v.t),D=x.t+S((B[A]-x[A])/(y[A]-x[A]))*(y.t-x.t);C>=0&&1>=C&&D>=0&&1>=D&&(c?r++:r.push({x:B.x,y:B.y,t1:C,t2:D}))}}return r}function r(a,b){return t(a,b)}function s(a,b){return t(a,b,1)}function t(a,b,c){a=E(a),b=E(b);for(var d,e,f,g,h,i,j,k,l,m,n=c?0:[],o=0,p=a.length;p>o;o++){var r=a[o];if("M"==r[0])d=h=r[1],e=i=r[2];else{"C"==r[0]?(l=[d,e].concat(r.slice(1)),d=l[6],e=l[7]):(l=[d,e,d,e,h,i,h,i],d=h,e=i);for(var s=0,t=b.length;t>s;s++){var u=b[s];if("M"==u[0])f=j=u[1],g=k=u[2];else{"C"==u[0]?(m=[f,g].concat(u.slice(1)),f=m[6],g=m[7]):(m=[f,g,f,g,j,k,j,k],f=j,g=k);var v=q(l,m,c);if(c)n+=v;else{for(var w=0,x=v.length;x>w;w++)v[w].segment1=o,v[w].segment2=s,v[w].bez1=l,v[w].bez2=m;n=n.concat(v)}}}}}return n}function u(a,b,c){var d=v(a);return k(d,b,c)&&t(a,[["M",b,c],["H",d.x2+10]],1)%2==1}function v(a){var b=c(a);if(b.bbox)return J(b.bbox);if(!a)return d();a=E(a);for(var e,f=0,g=0,h=[],i=[],j=0,k=a.length;k>j;j++)if(e=a[j],"M"==e[0])f=e[1],g=e[2],h.push(f),i.push(g);else{var l=D(f,g,e[1],e[2],e[3],e[4],e[5],e[6]);h=h.concat(l.min.x,l.max.x),i=i.concat(l.min.y,l.max.y),f=e[5],g=e[6]}var m=P.apply(0,h),n=P.apply(0,i),o=Q.apply(0,h),p=Q.apply(0,i),q=d(m,n,o-m,p-n);return b.bbox=J(q),q}function w(a,b,c,d,f){if(f)return[["M",+a+ +f,b],["l",c-2*f,0],["a",f,f,0,0,1,f,f],["l",0,d-2*f],["a",f,f,0,0,1,-f,f],["l",2*f-c,0],["a",f,f,0,0,1,-f,-f],["l",0,2*f-d],["a",f,f,0,0,1,f,-f],["z"]];var g=[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]];return g.toString=e,g}function x(a,b,c,d,f){if(null==f&&null==d&&(d=c),a=+a,b=+b,c=+c,d=+d,null!=f)var g=Math.PI/180,h=a+c*Math.cos(-d*g),i=a+c*Math.cos(-f*g),j=b+c*Math.sin(-d*g),k=b+c*Math.sin(-f*g),l=[["M",h,j],["A",c,c,0,+(f-d>180),0,i,k]];else l=[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]];return l.toString=e,l}function y(b){var d=c(b),g=String.prototype.toLowerCase;if(d.rel)return f(d.rel);a.is(b,"array")&&a.is(b&&b[0],"array")||(b=a.parsePathString(b));var h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=b[0][1],j=b[0][2],k=i,l=j,m++,h.push(["M",i,j]));for(var n=m,o=b.length;o>n;n++){var p=h[n]=[],q=b[n];if(q[0]!=g.call(q[0]))switch(p[0]=g.call(q[0]),p[0]){case"a":p[1]=q[1],p[2]=q[2],p[3]=q[3],p[4]=q[4],p[5]=q[5],p[6]=+(q[6]-i).toFixed(3),p[7]=+(q[7]-j).toFixed(3);break;case"v":p[1]=+(q[1]-j).toFixed(3);break;case"m":k=q[1],l=q[2];default:for(var r=1,s=q.length;s>r;r++)p[r]=+(q[r]-(r%2?i:j)).toFixed(3)}else{p=h[n]=[],"m"==q[0]&&(k=q[1]+i,l=q[2]+j);for(var t=0,u=q.length;u>t;t++)h[n][t]=q[t]}var v=h[n].length;switch(h[n][0]){case"z":i=k,j=l;break;case"h":i+=+h[n][v-1];break;case"v":j+=+h[n][v-1];break;default:i+=+h[n][v-2],j+=+h[n][v-1]}}return h.toString=e,d.rel=f(h),h}function z(b){var d=c(b);if(d.abs)return f(d.abs);if(I(b,"array")&&I(b&&b[0],"array")||(b=a.parsePathString(b)),!b||!b.length)return[["M",0,0]];var g,h=[],i=0,j=0,k=0,l=0,m=0;"M"==b[0][0]&&(i=+b[0][1],j=+b[0][2],k=i,l=j,m++,h[0]=["M",i,j]);for(var n,o,p=3==b.length&&"M"==b[0][0]&&"R"==b[1][0].toUpperCase()&&"Z"==b[2][0].toUpperCase(),q=m,r=b.length;r>q;q++){if(h.push(n=[]),o=b[q],g=o[0],g!=g.toUpperCase())switch(n[0]=g.toUpperCase(),n[0]){case"A":n[1]=o[1],n[2]=o[2],n[3]=o[3],n[4]=o[4],n[5]=o[5],n[6]=+o[6]+i,n[7]=+o[7]+j;break;case"V":n[1]=+o[1]+j;break;case"H":n[1]=+o[1]+i;break;case"R":for(var s=[i,j].concat(o.slice(1)),t=2,u=s.length;u>t;t++)s[t]=+s[t]+i,s[++t]=+s[t]+j;h.pop(),h=h.concat(G(s,p));break;case"O":h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);break;case"U":h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));break;case"M":k=+o[1]+i,l=+o[2]+j;default:for(t=1,u=o.length;u>t;t++)n[t]=+o[t]+(t%2?i:j)}else if("R"==g)s=[i,j].concat(o.slice(1)),h.pop(),h=h.concat(G(s,p)),n=["R"].concat(o.slice(-2));else if("O"==g)h.pop(),s=x(i,j,o[1],o[2]),s.push(s[0]),h=h.concat(s);else if("U"==g)h.pop(),h=h.concat(x(i,j,o[1],o[2],o[3])),n=["U"].concat(h[h.length-1].slice(-2));else for(var v=0,w=o.length;w>v;v++)n[v]=o[v];if(g=g.toUpperCase(),"O"!=g)switch(n[0]){case"Z":i=+k,j=+l;break;case"H":i=n[1];break;case"V":j=n[1];break;case"M":k=n[n.length-2],l=n[n.length-1];default:i=n[n.length-2],j=n[n.length-1]}}return h.toString=e,d.abs=f(h),h}function A(a,b,c,d){return[a,b,c,d,c,d]}function B(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]}function C(b,c,d,e,f,g,h,i,j,k){var l,m=120*O/180,n=O/180*(+f||0),o=[],p=a._.cacher(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(b,c,-n),b=l.x,c=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(O/180*f),N.sin(O/180*f),(b-i)/2),r=(c-j)/2,s=q*q/(d*d)+r*r/(e*e);s>1&&(s=N.sqrt(s),d=s*d,e=s*e);var t=d*d,u=e*e,v=(g==h?-1:1)*N.sqrt(S((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*d*r/e+(b+i)/2,x=v*-e*q/d+(c+j)/2,y=N.asin(((c-x)/e).toFixed(9)),z=N.asin(((j-x)/e).toFixed(9));y=w>b?O-y:y,z=w>i?O-z:z,0>y&&(y=2*O+y),0>z&&(z=2*O+z),h&&y>z&&(y-=2*O),!h&&z>y&&(z-=2*O)}var A=z-y;if(S(A)>m){var B=z,D=i,E=j;z=y+m*(h&&z>y?1:-1),i=w+d*N.cos(z),j=x+e*N.sin(z),o=C(i,j,d,e,f,0,h,D,E,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),J=N.tan(A/4),K=4/3*d*J,L=4/3*e*J,M=[b,c],P=[b+K*G,c-L*F],Q=[i+K*I,j-L*H],R=[i,j];if(P[0]=2*M[0]-P[0],P[1]=2*M[1]-P[1],k)return[P,Q,R].concat(o);o=[P,Q,R].concat(o).join().split(",");for(var T=[],U=0,V=o.length;V>U;U++)T[U]=U%2?p(o[U-1],o[U],n).y:p(o[U],o[U+1],n).x;return T}function D(a,b,c,d,e,f,g,h){for(var i,j,k,l,m,n,o,p,q=[],r=[[],[]],s=0;2>s;++s)if(0==s?(j=6*a-12*c+6*e,i=-3*a+9*c-9*e+3*g,k=3*c-3*a):(j=6*b-12*d+6*f,i=-3*b+9*d-9*f+3*h,k=3*d-3*b),S(i)<1e-12){if(S(j)<1e-12)continue;l=-k/j,l>0&&1>l&&q.push(l)}else o=j*j-4*k*i,p=N.sqrt(o),0>o||(m=(-j+p)/(2*i),m>0&&1>m&&q.push(m),n=(-j-p)/(2*i),n>0&&1>n&&q.push(n));for(var t,u=q.length,v=u;u--;)l=q[u],t=1-l,r[0][u]=t*t*t*a+3*t*t*l*c+3*t*l*l*e+l*l*l*g,r[1][u]=t*t*t*b+3*t*t*l*d+3*t*l*l*f+l*l*l*h;return r[0][v]=a,r[1][v]=b,r[0][v+1]=g,r[1][v+1]=h,r[0].length=r[1].length=v+2,{min:{x:P.apply(0,r[0]),y:P.apply(0,r[1])},max:{x:Q.apply(0,r[0]),y:Q.apply(0,r[1])}}}function E(a,b){var d=!b&&c(a);if(!b&&d.curve)return f(d.curve);for(var e=z(a),g=b&&z(b),h={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},i={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},j=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"].concat(C.apply(0,[b.x,b.y].concat(a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e].concat(a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"].concat(B(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"].concat(B(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"].concat(A(b.x,b.y,a[1],a[2]));break;case"H":a=["C"].concat(A(b.x,b.y,a[1],b.y));break;case"V":a=["C"].concat(A(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"].concat(A(b.x,b.y,b.X,b.Y))}return a}),k=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)m[b]="A",g&&(n[b]="A"),a.splice(b++,0,["C"].concat(c.splice(0,6)));a.splice(b,1),r=Q(e.length,g&&g.length||0)}},l=function(a,b,c,d,f){a&&b&&"M"==a[f][0]&&"M"!=b[f][0]&&(b.splice(f,0,["M",d.x,d.y]),c.bx=0,c.by=0,c.x=a[f][1],c.y=a[f][2],r=Q(e.length,g&&g.length||0))},m=[],n=[],o="",p="",q=0,r=Q(e.length,g&&g.length||0);r>q;q++){e[q]&&(o=e[q][0]),"C"!=o&&(m[q]=o,q&&(p=m[q-1])),e[q]=j(e[q],h,p),"A"!=m[q]&&"C"==o&&(m[q]="C"),k(e,q),g&&(g[q]&&(o=g[q][0]),"C"!=o&&(n[q]=o,q&&(p=n[q-1])),g[q]=j(g[q],i,p),"A"!=n[q]&&"C"==o&&(n[q]="C"),k(g,q)),l(e,g,h,i,q),l(g,e,i,h,q);var s=e[q],t=g&&g[q],u=s.length,v=g&&t.length;h.x=s[u-2],h.y=s[u-1],h.bx=M(s[u-4])||h.x,h.by=M(s[u-3])||h.y,i.bx=g&&(M(t[v-4])||i.x),i.by=g&&(M(t[v-3])||i.y),i.x=g&&t[v-2],i.y=g&&t[v-1]}return g||(d.curve=f(e)),g?[e,g]:e}function F(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=E(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a}function G(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}var H=b.prototype,I=a.is,J=a._.clone,K="hasOwnProperty",L=/,?([a-z]),?/gi,M=parseFloat,N=Math,O=N.PI,P=N.min,Q=N.max,R=N.pow,S=N.abs,T=h(1),U=h(),V=h(0,1),W=a._unit2px,X={path:function(a){return a.attr("path")},circle:function(a){var b=W(a);return x(b.cx,b.cy,b.r)},ellipse:function(a){var b=W(a);return x(b.cx||0,b.cy||0,b.rx,b.ry)},rect:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height,b.rx,b.ry)},image:function(a){var b=W(a);return w(b.x||0,b.y||0,b.width,b.height)},line:function(a){return"M"+[a.attr("x1")||0,a.attr("y1")||0,a.attr("x2"),a.attr("y2")]},polyline:function(a){return"M"+a.attr("points")},polygon:function(a){return"M"+a.attr("points")+"z"},deflt:function(a){var b=a.node.getBBox();return w(b.x,b.y,b.width,b.height)}};a.path=c,a.path.getTotalLength=T,a.path.getPointAtLength=U,a.path.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return V(a,b).end;var d=V(a,c,1);return b?V(d,b).end:d},H.getTotalLength=function(){return this.node.getTotalLength?this.node.getTotalLength():void 0},H.getPointAtLength=function(a){return U(this.attr("d"),a)},H.getSubpath=function(b,c){return a.path.getSubpath(this.attr("d"),b,c)},a._.box=d,a.path.findDotsAtSegment=i,a.path.bezierBBox=j,a.path.isPointInsideBBox=k,a.closest=function(b,c,e,f){for(var g=100,h=d(b-g/2,c-g/2,g,g),i=[],j=e[0].hasOwnProperty("x")?function(a){return{x:e[a].x,y:e[a].y}}:function(a){return{x:e[a],y:f[a]}},l=0;1e6>=g&&!l;){for(var m=0,n=e.length;n>m;m++){var o=j(m);if(k(h,o.x,o.y)){l++,i.push(o);break}}l||(g*=2,h=d(b-g/2,c-g/2,g,g))}if(1e6!=g){var p,q=1/0;for(m=0,n=i.length;n>m;m++){var r=a.len(b,c,i[m].x,i[m].y);q>r&&(q=r,i[m].len=r,p=i[m])}return p}},a.path.isBBoxIntersect=l,a.path.intersection=r,a.path.intersectionNumber=s,a.path.isPointInside=u,a.path.getBBox=v,a.path.get=X,a.path.toRelative=y,a.path.toAbsolute=z,a.path.toCubic=E,a.path.map=F,a.path.toString=e,a.path.clone=f}),d.plugin(function(a){var d=Math.max,e=Math.min,f=function(a){if(this.items=[],this.bindings={},this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)a[b]&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},g=f.prototype;g.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],a&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},g.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},g.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this},g.animate=function(d,e,f,g){"function"!=typeof f||f.length||(g=f,f=c.linear),d instanceof a._.Animation&&(g=d.callback,f=d.easing,e=f.dur,d=d.attr);var h=arguments;if(a.is(d,"array")&&a.is(h[h.length-1],"array"))var i=!0;var j,k=function(){j?this.b=j:j=this.b},l=0,m=this,n=g&&function(){++l==m.length&&g.call(this)
+};return this.forEach(function(a,c){b.once("snap.animcreated."+a.id,k),i?h[c]&&a.animate.apply(a,h[c]):a.animate(d,e,f,n)})},g.remove=function(){for(;this.length;)this.pop().remove();return this},g.bind=function(a,b,c){var d={};if("function"==typeof b)this.bindings[a]=b;else{var e=c||a;this.bindings[a]=function(a){d[e]=a,b.attr(d)}}return this},g.attr=function(a){var b={};for(var c in a)this.bindings[c]?this.bindings[c](a[c]):b[c]=a[c];for(var d=0,e=this.items.length;e>d;d++)this.items[d].attr(b);return this},g.clear=function(){for(;this.length;)this.pop()},g.splice=function(a,b){a=0>a?d(this.length+a,0):a,b=d(0,e(this.length-a,b));var c,g=[],h=[],i=[];for(c=2;c<arguments.length;c++)i.push(arguments[c]);for(c=0;b>c;c++)h.push(this[a+c]);for(;c<this.length-a;c++)g.push(this[a+c]);var j=i.length;for(c=0;c<j+g.length;c++)this.items[a+c]=this[a+c]=j>c?i[c]:g[c-j];for(c=this.items.length=this.length-=b-j;this[c];)delete this[c++];return new f(h)},g.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0;return!1},g.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},g.getBBox=function(){for(var a=[],b=[],c=[],f=[],g=this.items.length;g--;)if(!this.items[g].removed){var h=this.items[g].getBBox();a.push(h.x),b.push(h.y),c.push(h.x+h.width),f.push(h.y+h.height)}return a=e.apply(0,a),b=e.apply(0,b),c=d.apply(0,c),f=d.apply(0,f),{x:a,y:b,x2:c,y2:f,width:c-a,height:f-b,cx:a+(c-a)/2,cy:b+(f-b)/2}},g.clone=function(a){a=new f;for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},g.toString=function(){return"Snap‘s set"},g.type="set",a.Set=f,a.set=function(){var a=new f;return arguments.length&&a.push.apply(a,Array.prototype.slice.call(arguments,0)),a}}),d.plugin(function(a,c){function d(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}}function e(b,c,e){c=p(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];for(var f,g,h,i,l=Math.max(b.length,c.length),m=[],n=[],o=0;l>o;o++){if(h=b[o]||d(c[o]),i=c[o]||d(h),h[0]!=i[0]||"r"==h[0].toLowerCase()&&(h[2]!=i[2]||h[3]!=i[3])||"s"==h[0].toLowerCase()&&(h[3]!=i[3]||h[4]!=i[4])){b=a._.transform2matrix(b,e()),c=a._.transform2matrix(c,e()),m=[["m",b.a,b.b,b.c,b.d,b.e,b.f]],n=[["m",c.a,c.b,c.c,c.d,c.e,c.f]];break}for(m[o]=[],n[o]=[],f=0,g=Math.max(h.length,i.length);g>f;f++)f in h&&(m[o][f]=h[f]),f in i&&(n[o][f]=i[f])}return{from:k(m),to:k(n),f:j(m)}}function f(a){return a}function g(a){return function(b){return+b.toFixed(3)+a}}function h(a){return a.join(" ")}function i(b){return a.rgb(b[0],b[1],b[2])}function j(a){var b,c,d,e,f,g,h=0,i=[];for(b=0,c=a.length;c>b;b++){for(f="[",g=['"'+a[b][0]+'"'],d=1,e=a[b].length;e>d;d++)g[d]="val["+h++ +"]";f+=g+"]",i[b]=f}return Function("val","return Snap.path.toString.call(["+i+"])")}function k(a){for(var b=[],c=0,d=a.length;d>c;c++)for(var e=1,f=a[c].length;f>e;e++)b.push(a[c][e]);return b}function l(a){return isFinite(parseFloat(a))}function m(b,c){return a.is(b,"array")&&a.is(c,"array")?b.toString()==c.toString():!1}var n={},o=/[a-z]+$/i,p=String;n.stroke=n.fill="colour",c.prototype.equal=function(a,c){return b("snap.util.equal",this,a,c).firstDefined()},b.on("snap.util.equal",function(b,c){var d,q,r=p(this.attr(b)||""),s=this;if(l(r)&&l(c))return{from:parseFloat(r),to:parseFloat(c),f:f};if("colour"==n[b])return d=a.color(r),q=a.color(c),{from:[d.r,d.g,d.b,d.opacity],to:[q.r,q.g,q.b,q.opacity],f:i};if("viewBox"==b)return d=this.attr(b).vb.split(" ").map(Number),q=c.split(" ").map(Number),{from:d,to:q,f:h};if("transform"==b||"gradientTransform"==b||"patternTransform"==b)return c instanceof a.Matrix&&(c=c.toTransformString()),a._.rgTransform.test(c)||(c=a._.svgTransform2string(c)),e(r,c,function(){return s.getBBox(1)});if("d"==b||"path"==b)return d=a.path.toCubic(r,c),{from:k(d[0]),to:k(d[1]),f:j(d[0])};if("points"==b)return d=p(r).split(a._.separator),q=p(c).split(a._.separator),{from:d,to:q,f:function(a){return a}};var t=r.match(o),u=p(c).match(o);return t&&m(t,u)?{from:parseFloat(r),to:parseFloat(c),f:g(t)}:{from:this.asPX(b),to:this.asPX(b,c),f:f}})}),d.plugin(function(a,c,d,e){for(var f=c.prototype,g="hasOwnProperty",h=("createTouch"in e.doc),i=["click","dblclick","mousedown","mousemove","mouseout","mouseover","mouseup","touchstart","touchmove","touchend","touchcancel"],j={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},k=(function(a,b){var c="y"==a?"scrollTop":"scrollLeft",d=b&&b.node?b.node.ownerDocument:e.doc;return d[c in d.documentElement?"documentElement":"body"][c]}),l=function(){return this.originalEvent.preventDefault()},m=function(){return this.originalEvent.stopPropagation()},n=function(a,b,c,d){var e=h&&j[b]?j[b]:b,f=function(e){var f=k("y",d),i=k("x",d);if(h&&j[g](b))for(var n=0,o=e.targetTouches&&e.targetTouches.length;o>n;n++)if(e.targetTouches[n].target==a||a.contains(e.targetTouches[n].target)){var p=e;e=e.targetTouches[n],e.originalEvent=p,e.preventDefault=l,e.stopPropagation=m;break}var q=e.clientX+i,r=e.clientY+f;return c.call(d,e,q,r)};return b!==e&&a.addEventListener(b,f,!1),a.addEventListener(e,f,!1),function(){return b!==e&&a.removeEventListener(b,f,!1),a.removeEventListener(e,f,!1),!0}},o=[],p=function(a){for(var c,d=a.clientX,e=a.clientY,f=k("y"),g=k("x"),i=o.length;i--;){if(c=o[i],h){for(var j,l=a.touches&&a.touches.length;l--;)if(j=a.touches[l],j.identifier==c.el._drag.id||c.el.node.contains(j.target)){d=j.clientX,e=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();{var m=c.el.node;m.nextSibling,m.parentNode,m.style.display}d+=g,e+=f,b("snap.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},q=function(c){a.unmousemove(p).unmouseup(q);for(var d,e=o.length;e--;)d=o[e],d.el._drag={},b("snap.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c),b.off("snap.drag.*."+d.el.id);o=[]},r=i.length;r--;)!function(b){a[b]=f[b]=function(c,d){if(a.is(c,"function"))this.events=this.events||[],this.events.push({name:b,f:c,unbind:n(this.node||document,b,c,d||this)});else for(var e=0,f=this.events.length;f>e;e++)if(this.events[e].name==b)try{this.events[e].f.call(this)}catch(g){}return this},a["un"+b]=f["un"+b]=function(a){for(var c=this.events||[],d=c.length;d--;)if(c[d].name==b&&(c[d].f==a||!a))return c[d].unbind(),c.splice(d,1),!c.length&&delete this.events,this;return this}}(i[r]);f.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},f.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var s=[];f.drag=function(c,d,e,f,g,h){function i(i,j,l){(i.originalEvent||i).preventDefault(),k._drag.x=j,k._drag.y=l,k._drag.id=i.identifier,!o.length&&a.mousemove(p).mouseup(q),o.push({el:k,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("snap.drag.start."+k.id,d),c&&b.on("snap.drag.move."+k.id,c),e&&b.on("snap.drag.end."+k.id,e),b("snap.drag.start."+k.id,g||f||k,j,l,i)}function j(a,c,d){b("snap.draginit."+k.id,k,a,c,d)}var k=this;if(!arguments.length){var l;return k.drag(function(a,b){this.attr({transform:l+(l?"T":"t")+[a,b]})},function(){l=this.transform().local})}return b.on("snap.draginit."+k.id,i),k._drag={},s.push({el:k,start:i,init:j}),k.mousedown(j),k},f.undrag=function(){for(var c=s.length;c--;)s[c].el==this&&(this.unmousedown(s[c].init),s.splice(c,1),b.unbind("snap.drag.*."+this.id),b.unbind("snap.draginit."+this.id));return!s.length&&a.unmousemove(p).unmouseup(q),this}}),d.plugin(function(a,c,d){var e=(c.prototype,d.prototype),f=/^\s*url\((.+)\)/,g=String,h=a._.$;a.filter={},e.filter=function(b){var d=this;"svg"!=d.type&&(d=d.paper);var e=a.parse(g(b)),f=a._.id(),i=(d.node.offsetWidth,d.node.offsetHeight,h("filter"));return h(i,{id:f,filterUnits:"userSpaceOnUse"}),i.appendChild(e.node),d.defs.appendChild(i),new c(i)},b.on("snap.util.getattr.filter",function(){b.stop();var c=h(this.node,"filter");if(c){var d=g(c).match(f);return d&&a.select(d[1])}}),b.on("snap.util.attr.filter",function(d){if(d instanceof c&&"filter"==d.type){b.stop();var e=d.node.id;e||(h(d.node,{id:d.id}),e=d.id),h(this.node,{filter:a.url(e)})}d&&"none"!=d||(b.stop(),this.node.removeAttribute("filter"))}),a.filter.blur=function(b,c){null==b&&(b=2);var d=null==c?b:[b,c];return a.format('<feGaussianBlur stdDeviation="{def}"/>',{def:d})},a.filter.blur.toString=function(){return this()},a.filter.shadow=function(b,c,d,e,f){return"string"==typeof d&&(e=d,f=e,d=4),"string"!=typeof e&&(f=e,e="#000"),e=e||"#000",null==d&&(d=4),null==f&&(f=1),null==b&&(b=0,c=2),null==c&&(c=b),e=a.color(e),a.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>',{color:e,dx:b,dy:c,blur:d,opacity:f})},a.filter.shadow.toString=function(){return this()},a.filter.grayscale=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>',{a:.2126+.7874*(1-b),b:.7152-.7152*(1-b),c:.0722-.0722*(1-b),d:.2126-.2126*(1-b),e:.7152+.2848*(1-b),f:.0722-.0722*(1-b),g:.2126-.2126*(1-b),h:.0722+.9278*(1-b)})},a.filter.grayscale.toString=function(){return this()},a.filter.sepia=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>',{a:.393+.607*(1-b),b:.769-.769*(1-b),c:.189-.189*(1-b),d:.349-.349*(1-b),e:.686+.314*(1-b),f:.168-.168*(1-b),g:.272-.272*(1-b),h:.534-.534*(1-b),i:.131+.869*(1-b)})},a.filter.sepia.toString=function(){return this()},a.filter.saturate=function(b){return null==b&&(b=1),a.format('<feColorMatrix type="saturate" values="{amount}"/>',{amount:1-b})},a.filter.saturate.toString=function(){return this()},a.filter.hueRotate=function(b){return b=b||0,a.format('<feColorMatrix type="hueRotate" values="{angle}"/>',{angle:b})},a.filter.hueRotate.toString=function(){return this()},a.filter.invert=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>',{amount:b,amount2:1-b})},a.filter.invert.toString=function(){return this()},a.filter.brightness=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>',{amount:b})},a.filter.brightness.toString=function(){return this()},a.filter.contrast=function(b){return null==b&&(b=1),a.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>',{amount:b,amount2:.5-b/2})},a.filter.contrast.toString=function(){return this()}}),d.plugin(function(a,b){var c=a._.box,d=a.is,e=/^[^a-z]*([tbmlrc])/i,f=function(){return"T"+this.dx+","+this.dy};b.prototype.getAlign=function(a,b){null==b&&d(a,"string")&&(b=a,a=null),a=a||this.paper;var g=a.getBBox?a.getBBox():c(a),h=this.getBBox(),i={};switch(b=b&&b.match(e),b=b?b[1].toLowerCase():"c"){case"t":i.dx=0,i.dy=g.y-h.y;break;case"b":i.dx=0,i.dy=g.y2-h.y2;break;case"m":i.dx=0,i.dy=g.cy-h.cy;break;case"l":i.dx=g.x-h.x,i.dy=0;break;case"r":i.dx=g.x2-h.x2,i.dy=0;break;default:i.dx=g.cx-h.cx,i.dy=0}return i.toString=f,i},b.prototype.align=function(a,b){return this.transform("..."+this.getAlign(a,b))}}),d});
diff --git a/web/pgadmin/misc/static/explain/js/snap.svg.js b/web/pgadmin/misc/static/explain/js/snap.svg.js
new file mode 100644
index 0000000..ef0fb6d
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg.js
@@ -0,0 +1,8149 @@
+// Snap.svg 0.4.1
+// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// build: 2015-04-13
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ┌────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.4.2 - JavaScript Events Library                      │ \\
+// ├────────────────────────────────────────────────────────────┤ \\
+// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+// └────────────────────────────────────────────────────────────┘ \\
+(function (glob) {
+    var version = "0.4.2",
+        has = "hasOwnProperty",
+        separator = /[\.\/]/,
+        comaseparator = /\s*,\s*/,
+        wildcard = "*",
+        fun = function () {},
+        numsort = function (a, b) {
+            return a - b;
+        },
+        current_event,
+        stop,
+        events = {n: {}},
+        firstDefined = function () {
+            for (var i = 0, ii = this.length; i < ii; i++) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+        lastDefined = function () {
+            var i = this.length;
+            while (--i) {
+                if (typeof this[i] != "undefined") {
+                    return this[i];
+                }
+            }
+        },
+    /*\
+     * eve
+     [ method ]
+     * Fires event with given `name`, given scope and other parameters.
+     > Arguments
+     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
+     - scope (object) context for the event handlers
+     - varargs (...) the rest of arguments will be sent to event handlers
+     = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value.
+    \*/
+        eve = function (name, scope) {
+            name = String(name);
+            var e = events,
+                oldstop = stop,
+                args = Array.prototype.slice.call(arguments, 2),
+                listeners = eve.listeners(name),
+                z = 0,
+                f = false,
+                l,
+                indexed = [],
+                queue = {},
+                out = [],
+                ce = current_event,
+                errors = [];
+            out.firstDefined = firstDefined;
+            out.lastDefined = lastDefined;
+            current_event = name;
+            stop = 0;
+            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                indexed.push(listeners[i].zIndex);
+                if (listeners[i].zIndex < 0) {
+                    queue[listeners[i].zIndex] = listeners[i];
+                }
+            }
+            indexed.sort(numsort);
+            while (indexed[z] < 0) {
+                l = queue[indexed[z++]];
+                out.push(l.apply(scope, args));
+                if (stop) {
+                    stop = oldstop;
+                    return out;
+                }
+            }
+            for (i = 0; i < ii; i++) {
+                l = listeners[i];
+                if ("zIndex" in l) {
+                    if (l.zIndex == indexed[z]) {
+                        out.push(l.apply(scope, args));
+                        if (stop) {
+                            break;
+                        }
+                        do {
+                            z++;
+                            l = queue[indexed[z]];
+                            l && out.push(l.apply(scope, args));
+                            if (stop) {
+                                break;
+                            }
+                        } while (l)
+                    } else {
+                        queue[l.zIndex] = l;
+                    }
+                } else {
+                    out.push(l.apply(scope, args));
+                    if (stop) {
+                        break;
+                    }
+                }
+            }
+            stop = oldstop;
+            current_event = ce;
+            return out;
+        };
+        // Undocumented. Debug only.
+        eve._events = events;
+    /*\
+     * eve.listeners
+     [ method ]
+     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
+     > Arguments
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated
+     = (array) array of event handlers
+    \*/
+    eve.listeners = function (name) {
+        var names = name.split(separator),
+            e = events,
+            item,
+            items,
+            k,
+            i,
+            ii,
+            j,
+            jj,
+            nes,
+            es = [e],
+            out = [];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            nes = [];
+            for (j = 0, jj = es.length; j < jj; j++) {
+                e = es[j].n;
+                items = [e[names[i]], e[wildcard]];
+                k = 2;
+                while (k--) {
+                    item = items[k];
+                    if (item) {
+                        nes.push(item);
+                        out = out.concat(item.f || []);
+                    }
+                }
+            }
+            es = nes;
+        }
+        return out;
+    };
+    /*\
+     * eve.on
+     [ method ]
+     **
+     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
+     | eve.on("*.under.*", f);
+     | eve("mouse.under.floor"); // triggers f
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
+     > Example:
+     | eve.on("mouse", eatIt)(2);
+     | eve.on("mouse", scream);
+     | eve.on("mouse", catchIt)(1);
+     * This will ensure that `catchIt` function will be called before `eatIt`.
+     *
+     * If you want to put your handler before non-indexed handlers, specify a negative value.
+     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
+    \*/
+    eve.on = function (name, f) {
+        name = String(name);
+        if (typeof f != "function") {
+            return function () {};
+        }
+        var names = name.split(comaseparator);
+        for (var i = 0, ii = names.length; i < ii; i++) {
+            (function (name) {
+                var names = name.split(separator),
+                    e = events,
+                    exist;
+                for (var i = 0, ii = names.length; i < ii; i++) {
+                    e = e.n;
+                    e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
+                }
+                e.f = e.f || [];
+                for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+                    exist = true;
+                    break;
+                }
+                !exist && e.f.push(f);
+            }(names[i]));
+        }
+        return function (zIndex) {
+            if (+zIndex == +zIndex) {
+                f.zIndex = +zIndex;
+            }
+        };
+    };
+    /*\
+     * eve.f
+     [ method ]
+     **
+     * Returns function that will fire given event with optional arguments.
+     * Arguments that will be passed to the result function will be also
+     * concated to the list of final arguments.
+     | el.onclick = eve.f("click", 1, 2);
+     | eve.on("click", function (a, b, c) {
+     |     console.log(a, b, c); // 1, 2, [event object]
+     | });
+     > Arguments
+     - event (string) event name
+     - varargs (…) and any other arguments
+     = (function) possible event handler function
+    \*/
+    eve.f = function (event) {
+        var attrs = [].slice.call(arguments, 1);
+        return function () {
+            eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
+        };
+    };
+    /*\
+     * eve.stop
+     [ method ]
+     **
+     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
+    \*/
+    eve.stop = function () {
+        stop = 1;
+    };
+    /*\
+     * eve.nt
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     > Arguments
+     **
+     - subname (string) #optional subname of the event
+     **
+     = (string) name of the event, if `subname` is not specified
+     * or
+     = (boolean) `true`, if current event’s name contains `subname`
+    \*/
+    eve.nt = function (subname) {
+        if (subname) {
+            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+        }
+        return current_event;
+    };
+    /*\
+     * eve.nts
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     **
+     = (array) names of the event
+    \*/
+    eve.nts = function () {
+        return current_event.split(separator);
+    };
+    /*\
+     * eve.off
+     [ method ]
+     **
+     * Removes given function from the list of event listeners assigned to given name.
+     * If no arguments specified all the events will be cleared.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+    \*/
+    /*\
+     * eve.unbind
+     [ method ]
+     **
+     * See @eve.off
+    \*/
+    eve.off = eve.unbind = function (name, f) {
+        if (!name) {
+            eve._events = events = {n: {}};
+            return;
+        }
+        var names = name.split(comaseparator);
+        if (names.length > 1) {
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                eve.off(names[i], f);
+            }
+            return;
+        }
+        names = name.split(separator);
+        var e,
+            key,
+            splice,
+            i, ii, j, jj,
+            cur = [events];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
+                splice = [j, 1];
+                e = cur[j].n;
+                if (names[i] != wildcard) {
+                    if (e[names[i]]) {
+                        splice.push(e[names[i]]);
+                    }
+                } else {
+                    for (key in e) if (e[has](key)) {
+                        splice.push(e[key]);
+                    }
+                }
+                cur.splice.apply(cur, splice);
+            }
+        }
+        for (i = 0, ii = cur.length; i < ii; i++) {
+            e = cur[i];
+            while (e.n) {
+                if (f) {
+                    if (e.f) {
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
+                            break;
+                        }
+                        !e.f.length && delete e.f;
+                    }
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        var funcs = e.n[key].f;
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
+                            break;
+                        }
+                        !funcs.length && delete e.n[key].f;
+                    }
+                } else {
+                    delete e.f;
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        delete e.n[key].f;
+                    }
+                }
+                e = e.n;
+            }
+        }
+    };
+    /*\
+     * eve.once
+     [ method ]
+     **
+     * Binds given event handler with a given name to only run once then unbind itself.
+     | eve.once("login", f);
+     | eve("login"); // triggers f
+     | eve("login"); // no listeners
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) same return function as @eve.on
+    \*/
+    eve.once = function (name, f) {
+        var f2 = function () {
+            eve.unbind(name, f2);
+            return f.apply(this, arguments);
+        };
+        return eve.on(name, f2);
+    };
+    /*\
+     * eve.version
+     [ property (string) ]
+     **
+     * Current version of the library.
+    \*/
+    eve.version = version;
+    eve.toString = function () {
+        return "You are running Eve " + version;
+    };
+    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
+})(this);
+
+(function (glob, factory) {
+    // AMD support
+    if (typeof define == "function" && define.amd) {
+        // Define as an anonymous module
+        define(["eve"], function (eve) {
+            return factory(glob, eve);
+        });
+    } else if (typeof exports != 'undefined') {
+        // Next for Node.js or CommonJS
+        var eve = require('eve');
+        module.exports = factory(glob, eve);
+    } else {
+        // Browser globals (glob is window)
+        // Snap adds itself to window
+        factory(glob, glob.eve);
+    }
+}(window || this, function (window, eve) {
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+var mina = (function (eve) {
+    var animations = {},
+    requestAnimFrame = window.requestAnimationFrame       ||
+                       window.webkitRequestAnimationFrame ||
+                       window.mozRequestAnimationFrame    ||
+                       window.oRequestAnimationFrame      ||
+                       window.msRequestAnimationFrame     ||
+                       function (callback) {
+                           setTimeout(callback, 16);
+                       },
+    isArray = Array.isArray || function (a) {
+        return a instanceof Array ||
+            Object.prototype.toString.call(a) == "[object Array]";
+    },
+    idgen = 0,
+    idprefix = "M" + (+new Date).toString(36),
+    ID = function () {
+        return idprefix + (idgen++).toString(36);
+    },
+    diff = function (a, b, A, B) {
+        if (isArray(a)) {
+            res = [];
+            for (var i = 0, ii = a.length; i < ii; i++) {
+                res[i] = diff(a[i], b, A[i], B);
+            }
+            return res;
+        }
+        var dif = (A - a) / (B - b);
+        return function (bb) {
+            return a + dif * (bb - b);
+        };
+    },
+    timer = Date.now || function () {
+        return +new Date;
+    },
+    sta = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.s;
+        }
+        var ds = a.s - val;
+        a.b += a.dur * ds;
+        a.B += a.dur * ds;
+        a.s = val;
+    },
+    speed = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.spd;
+        }
+        a.spd = val;
+    },
+    duration = function (val) {
+        var a = this;
+        if (val == null) {
+            return a.dur;
+        }
+        a.s = a.s * val / a.dur;
+        a.dur = val;
+    },
+    stopit = function () {
+        var a = this;
+        delete animations[a.id];
+        a.update();
+        eve("mina.stop." + a.id, a);
+    },
+    pause = function () {
+        var a = this;
+        if (a.pdif) {
+            return;
+        }
+        delete animations[a.id];
+        a.update();
+        a.pdif = a.get() - a.b;
+    },
+    resume = function () {
+        var a = this;
+        if (!a.pdif) {
+            return;
+        }
+        a.b = a.get() - a.pdif;
+        delete a.pdif;
+        animations[a.id] = a;
+    },
+    update = function () {
+        var a = this,
+            res;
+        if (isArray(a.start)) {
+            res = [];
+            for (var j = 0, jj = a.start.length; j < jj; j++) {
+                res[j] = +a.start[j] +
+                    (a.end[j] - a.start[j]) * a.easing(a.s);
+            }
+        } else {
+            res = +a.start + (a.end - a.start) * a.easing(a.s);
+        }
+        a.set(res);
+    },
+    frame = function () {
+        var len = 0;
+        for (var i in animations) if (animations.hasOwnProperty(i)) {
+            var a = animations[i],
+                b = a.get(),
+                res;
+            len++;
+            a.s = (b - a.b) / (a.dur / a.spd);
+            if (a.s >= 1) {
+                delete animations[i];
+                a.s = 1;
+                len--;
+                (function (a) {
+                    setTimeout(function () {
+                        eve("mina.finish." + a.id, a);
+                    });
+                }(a));
+            }
+            a.update();
+        }
+        len && requestAnimFrame(frame);
+    },
+    /*\
+     * mina
+     [ method ]
+     **
+     * Generic animation of numbers
+     **
+     - a (number) start _slave_ number
+     - A (number) end _slave_ number
+     - b (number) start _master_ number (start time in general case)
+     - B (number) end _master_ number (end time in gereal case)
+     - get (function) getter of _master_ number (see @mina.time)
+     - set (function) setter of _slave_ number
+     - easing (function) #optional easing function, default is @mina.linear
+     = (object) animation descriptor
+     o {
+     o         id (string) animation id,
+     o         start (number) start _slave_ number,
+     o         end (number) end _slave_ number,
+     o         b (number) start _master_ number,
+     o         s (number) animation status (0..1),
+     o         dur (number) animation duration,
+     o         spd (number) animation speed,
+     o         get (function) getter of _master_ number (see @mina.time),
+     o         set (function) setter of _slave_ number,
+     o         easing (function) easing function, default is @mina.linear,
+     o         status (function) status getter/setter,
+     o         speed (function) speed getter/setter,
+     o         duration (function) duration getter/setter,
+     o         stop (function) animation stopper
+     o         pause (function) pauses the animation
+     o         resume (function) resumes the animation
+     o         update (function) calles setter with the right value of the animation
+     o }
+    \*/
+    mina = function (a, A, b, B, get, set, easing) {
+        var anim = {
+            id: ID(),
+            start: a,
+            end: A,
+            b: b,
+            s: 0,
+            dur: B - b,
+            spd: 1,
+            get: get,
+            set: set,
+            easing: easing || mina.linear,
+            status: sta,
+            speed: speed,
+            duration: duration,
+            stop: stopit,
+            pause: pause,
+            resume: resume,
+            update: update
+        };
+        animations[anim.id] = anim;
+        var len = 0, i;
+        for (i in animations) if (animations.hasOwnProperty(i)) {
+            len++;
+            if (len == 2) {
+                break;
+            }
+        }
+        len == 1 && requestAnimFrame(frame);
+        return anim;
+    };
+    /*\
+     * mina.time
+     [ method ]
+     **
+     * Returns the current time. Equivalent to:
+     | function () {
+     |     return (new Date).getTime();
+     | }
+    \*/
+    mina.time = timer;
+    /*\
+     * mina.getById
+     [ method ]
+     **
+     * Returns an animation by its id
+     - id (string) animation's id
+     = (object) See @mina
+    \*/
+    mina.getById = function (id) {
+        return animations[id] || null;
+    };
+
+    /*\
+     * mina.linear
+     [ method ]
+     **
+     * Default linear easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.linear = function (n) {
+        return n;
+    };
+    /*\
+     * mina.easeout
+     [ method ]
+     **
+     * Easeout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeout = function (n) {
+        return Math.pow(n, 1.7);
+    };
+    /*\
+     * mina.easein
+     [ method ]
+     **
+     * Easein easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easein = function (n) {
+        return Math.pow(n, .48);
+    };
+    /*\
+     * mina.easeinout
+     [ method ]
+     **
+     * Easeinout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.easeinout = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        if (n == 0) {
+            return 0;
+        }
+        var q = .48 - n / 1.04,
+            Q = Math.sqrt(.1734 + q * q),
+            x = Q - q,
+            X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+            y = -Q - q,
+            Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+            t = X + Y + .5;
+        return (1 - t) * 3 * t * t + t * t * t;
+    };
+    /*\
+     * mina.backin
+     [ method ]
+     **
+     * Backin easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backin = function (n) {
+        if (n == 1) {
+            return 1;
+        }
+        var s = 1.70158;
+        return n * n * ((s + 1) * n - s);
+    };
+    /*\
+     * mina.backout
+     [ method ]
+     **
+     * Backout easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.backout = function (n) {
+        if (n == 0) {
+            return 0;
+        }
+        n = n - 1;
+        var s = 1.70158;
+        return n * n * ((s + 1) * n + s) + 1;
+    };
+    /*\
+     * mina.elastic
+     [ method ]
+     **
+     * Elastic easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.elastic = function (n) {
+        if (n == !!n) {
+            return n;
+        }
+        return Math.pow(2, -10 * n) * Math.sin((n - .075) *
+            (2 * Math.PI) / .3) + 1;
+    };
+    /*\
+     * mina.bounce
+     [ method ]
+     **
+     * Bounce easing
+     - n (number) input 0..1
+     = (number) output 0..1
+    \*/
+    mina.bounce = function (n) {
+        var s = 7.5625,
+            p = 2.75,
+            l;
+        if (n < (1 / p)) {
+            l = s * n * n;
+        } else {
+            if (n < (2 / p)) {
+                n -= (1.5 / p);
+                l = s * n * n + .75;
+            } else {
+                if (n < (2.5 / p)) {
+                    n -= (2.25 / p);
+                    l = s * n * n + .9375;
+                } else {
+                    n -= (2.625 / p);
+                    l = s * n * n + .984375;
+                }
+            }
+        }
+        return l;
+    };
+    window.mina = mina;
+    return mina;
+})(typeof eve == "undefined" ? function () {} : eve);
+// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var Snap = (function(root) {
+Snap.version = "0.4.0";
+/*\
+ * Snap
+ [ method ]
+ **
+ * Creates a drawing surface or wraps existing SVG element.
+ **
+ - width (number|string) width of surface
+ - height (number|string) height of surface
+ * or
+ - DOM (SVGElement) element to be wrapped into Snap structure
+ * or
+ - array (array) array of elements (will return set of elements)
+ * or
+ - query (string) CSS query selector
+ = (object) @Element
+\*/
+function Snap(w, h) {
+    if (w) {
+        if (w.nodeType) {
+            return wrap(w);
+        }
+        if (is(w, "array") && Snap.set) {
+            return Snap.set.apply(Snap, w);
+        }
+        if (w instanceof Element) {
+            return w;
+        }
+        if (h == null) {
+            w = glob.doc.querySelector(String(w));
+            return wrap(w);
+        }
+    }
+    w = w == null ? "100%" : w;
+    h = h == null ? "100%" : h;
+    return new Paper(w, h);
+}
+Snap.toString = function () {
+    return "Snap v" + this.version;
+};
+Snap._ = {};
+var glob = {
+    win: root.window,
+    doc: root.window.document
+};
+Snap._.glob = glob;
+var has = "hasOwnProperty",
+    Str = String,
+    toFloat = parseFloat,
+    toInt = parseInt,
+    math = Math,
+    mmax = math.max,
+    mmin = math.min,
+    abs = math.abs,
+    pow = math.pow,
+    PI = math.PI,
+    round = math.round,
+    E = "",
+    S = " ",
+    objectToString = Object.prototype.toString,
+    ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+    colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i,
+    bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+    reURLValue = /^url\(#?([^)]+)\)$/,
+    separator = Snap._.separator = /[,\s]+/,
+    whitespace = /[\s]/g,
+    commaSpaces = /[\s]*,[\s]*/,
+    hsrg = {hs: 1, rg: 1},
+    pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig,
+    pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig,
+    idgen = 0,
+    idprefix = "S" + (+new Date).toString(36),
+    ID = function (el) {
+        return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36);
+    },
+    xlink = "http://www.w3.org/1999/xlink",
+    xmlns = "http://www.w3.org/2000/svg",
+    hub = {},
+    URL = Snap.url = function (url) {
+        return "url('#" + url + "')";
+    };
+
+function $(el, attr) {
+    if (attr) {
+        if (el == "#text") {
+            el = glob.doc.createTextNode(attr.text || attr["#text"] || "");
+        }
+        if (el == "#comment") {
+            el = glob.doc.createComment(attr.text || attr["#text"] || "");
+        }
+        if (typeof el == "string") {
+            el = $(el);
+        }
+        if (typeof attr == "string") {
+            if (el.nodeType == 1) {
+                if (attr.substring(0, 6) == "xlink:") {
+                    return el.getAttributeNS(xlink, attr.substring(6));
+                }
+                if (attr.substring(0, 4) == "xml:") {
+                    return el.getAttributeNS(xmlns, attr.substring(4));
+                }
+                return el.getAttribute(attr);
+            } else if (attr == "text") {
+                return el.nodeValue;
+            } else {
+                return null;
+            }
+        }
+        if (el.nodeType == 1) {
+            for (var key in attr) if (attr[has](key)) {
+                var val = Str(attr[key]);
+                if (val) {
+                    if (key.substring(0, 6) == "xlink:") {
+                        el.setAttributeNS(xlink, key.substring(6), val);
+                    } else if (key.substring(0, 4) == "xml:") {
+                        el.setAttributeNS(xmlns, key.substring(4), val);
+                    } else {
+                        el.setAttribute(key, val);
+                    }
+                } else {
+                    el.removeAttribute(key);
+                }
+            }
+        } else if ("text" in attr) {
+            el.nodeValue = attr.text;
+        }
+    } else {
+        el = glob.doc.createElementNS(xmlns, el);
+    }
+    return el;
+}
+Snap._.$ = $;
+Snap._.id = ID;
+function getAttrs(el) {
+    var attrs = el.attributes,
+        name,
+        out = {};
+    for (var i = 0; i < attrs.length; i++) {
+        if (attrs[i].namespaceURI == xlink) {
+            name = "xlink:";
+        } else {
+            name = "";
+        }
+        name += attrs[i].name;
+        out[name] = attrs[i].textContent;
+    }
+    return out;
+}
+function is(o, type) {
+    type = Str.prototype.toLowerCase.call(type);
+    if (type == "finite") {
+        return isFinite(o);
+    }
+    if (type == "array" &&
+        (o instanceof Array || Array.isArray && Array.isArray(o))) {
+        return true;
+    }
+    return  (type == "null" && o === null) ||
+            (type == typeof o && o !== null) ||
+            (type == "object" && o === Object(o)) ||
+            objectToString.call(o).slice(8, -1).toLowerCase() == type;
+}
+/*\
+ * Snap.format
+ [ method ]
+ **
+ * Replaces construction of type `{<name>}` to the corresponding argument
+ **
+ - token (string) string to format
+ - json (object) object which properties are used as a replacement
+ = (string) formatted string
+ > Usage
+ | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z"
+ | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
+ |     x: 10,
+ |     y: 20,
+ |     dim: {
+ |         width: 40,
+ |         height: 50,
+ |         "negative width": -40
+ |     }
+ | }));
+\*/
+Snap.format = (function () {
+    var tokenRegex = /\{([^\}]+)\}/g,
+        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+        replacer = function (all, key, obj) {
+            var res = obj;
+            key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+                name = name || quotedName;
+                if (res) {
+                    if (name in res) {
+                        res = res[name];
+                    }
+                    typeof res == "function" && isFunc && (res = res());
+                }
+            });
+            res = (res == null || res == obj ? all : res) + "";
+            return res;
+        };
+    return function (str, obj) {
+        return Str(str).replace(tokenRegex, function (all, key) {
+            return replacer(all, key, obj);
+        });
+    };
+})();
+function clone(obj) {
+    if (typeof obj == "function" || Object(obj) !== obj) {
+        return obj;
+    }
+    var res = new obj.constructor;
+    for (var key in obj) if (obj[has](key)) {
+        res[key] = clone(obj[key]);
+    }
+    return res;
+}
+Snap._.clone = clone;
+function repush(array, item) {
+    for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+        return array.push(array.splice(i, 1)[0]);
+    }
+}
+function cacher(f, scope, postprocessor) {
+    function newf() {
+        var arg = Array.prototype.slice.call(arguments, 0),
+            args = arg.join("\u2400"),
+            cache = newf.cache = newf.cache || {},
+            count = newf.count = newf.count || [];
+        if (cache[has](args)) {
+            repush(count, args);
+            return postprocessor ? postprocessor(cache[args]) : cache[args];
+        }
+        count.length >= 1e3 && delete cache[count.shift()];
+        count.push(args);
+        cache[args] = f.apply(scope, arg);
+        return postprocessor ? postprocessor(cache[args]) : cache[args];
+    }
+    return newf;
+}
+Snap._.cacher = cacher;
+function angle(x1, y1, x2, y2, x3, y3) {
+    if (x3 == null) {
+        var x = x1 - x2,
+            y = y1 - y2;
+        if (!x && !y) {
+            return 0;
+        }
+        return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+    } else {
+        return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3);
+    }
+}
+function rad(deg) {
+    return deg % 360 * PI / 180;
+}
+function deg(rad) {
+    return rad * 180 / PI % 360;
+}
+function x_y() {
+    return this.x + S + this.y;
+}
+function x_y_w_h() {
+    return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+}
+
+/*\
+ * Snap.rad
+ [ method ]
+ **
+ * Transform angle to radians
+ - deg (number) angle in degrees
+ = (number) angle in radians
+\*/
+Snap.rad = rad;
+/*\
+ * Snap.deg
+ [ method ]
+ **
+ * Transform angle to degrees
+ - rad (number) angle in radians
+ = (number) angle in degrees
+\*/
+Snap.deg = deg;
+/*\
+ * Snap.sin
+ [ method ]
+ **
+ * Equivalent to `Math.sin()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) sin
+\*/
+Snap.sin = function (angle) {
+    return math.sin(Snap.rad(angle));
+};
+/*\
+ * Snap.tan
+ [ method ]
+ **
+ * Equivalent to `Math.tan()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) tan
+\*/
+Snap.tan = function (angle) {
+    return math.tan(Snap.rad(angle));
+};
+/*\
+ * Snap.cos
+ [ method ]
+ **
+ * Equivalent to `Math.cos()` only works with degrees, not radians.
+ - angle (number) angle in degrees
+ = (number) cos
+\*/
+Snap.cos = function (angle) {
+    return math.cos(Snap.rad(angle));
+};
+/*\
+ * Snap.asin
+ [ method ]
+ **
+ * Equivalent to `Math.asin()` only works with degrees, not radians.
+ - num (number) value
+ = (number) asin in degrees
+\*/
+Snap.asin = function (num) {
+    return Snap.deg(math.asin(num));
+};
+/*\
+ * Snap.acos
+ [ method ]
+ **
+ * Equivalent to `Math.acos()` only works with degrees, not radians.
+ - num (number) value
+ = (number) acos in degrees
+\*/
+Snap.acos = function (num) {
+    return Snap.deg(math.acos(num));
+};
+/*\
+ * Snap.atan
+ [ method ]
+ **
+ * Equivalent to `Math.atan()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan in degrees
+\*/
+Snap.atan = function (num) {
+    return Snap.deg(math.atan(num));
+};
+/*\
+ * Snap.atan2
+ [ method ]
+ **
+ * Equivalent to `Math.atan2()` only works with degrees, not radians.
+ - num (number) value
+ = (number) atan2 in degrees
+\*/
+Snap.atan2 = function (num) {
+    return Snap.deg(math.atan2(num));
+};
+/*\
+ * Snap.angle
+ [ method ]
+ **
+ * Returns an angle between two or three points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ - x3 (number) #optional x coord of third point
+ - y3 (number) #optional y coord of third point
+ = (number) angle in degrees
+\*/
+Snap.angle = angle;
+/*\
+ * Snap.len
+ [ method ]
+ **
+ * Returns distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len = function (x1, y1, x2, y2) {
+    return Math.sqrt(Snap.len2(x1, y1, x2, y2));
+};
+/*\
+ * Snap.len2
+ [ method ]
+ **
+ * Returns squared distance between two points
+ > Parameters
+ - x1 (number) x coord of first point
+ - y1 (number) y coord of first point
+ - x2 (number) x coord of second point
+ - y2 (number) y coord of second point
+ = (number) distance
+\*/
+Snap.len2 = function (x1, y1, x2, y2) {
+    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
+};
+/*\
+ * Snap.closestPoint
+ [ method ]
+ **
+ * Returns closest point to a given one on a given path.
+ > Parameters
+ - path (Element) path element
+ - x (number) x coord of a point
+ - y (number) y coord of a point
+ = (object) in format
+ {
+    x (number) x coord of the point on the path
+    y (number) y coord of the point on the path
+    length (number) length of the path to the point
+    distance (number) distance from the given point to the path
+ }
+\*/
+// Copied from http://bl.ocks.org/mbostock/8027637
+Snap.closestPoint = function (path, x, y) {
+    function distance2(p) {
+        var dx = p.x - x,
+            dy = p.y - y;
+        return dx * dx + dy * dy;
+    }
+    var pathNode = path.node,
+        pathLength = pathNode.getTotalLength(),
+        precision = pathLength / pathNode.pathSegList.numberOfItems * .125,
+        best,
+        bestLength,
+        bestDistance = Infinity;
+
+    // linear scan for coarse approximation
+    for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
+        if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
+            best = scan, bestLength = scanLength, bestDistance = scanDistance;
+        }
+    }
+
+    // binary search for precise estimate
+    precision *= .5;
+    while (precision > .5) {
+        var before,
+            after,
+            beforeLength,
+            afterLength,
+            beforeDistance,
+            afterDistance;
+        if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
+            best = before, bestLength = beforeLength, bestDistance = beforeDistance;
+        } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
+            best = after, bestLength = afterLength, bestDistance = afterDistance;
+        } else {
+            precision *= .5;
+        }
+    }
+
+    best = {
+        x: best.x,
+        y: best.y,
+        length: bestLength,
+        distance: Math.sqrt(bestDistance)
+    };
+    return best;
+}
+/*\
+ * Snap.is
+ [ method ]
+ **
+ * Handy replacement for the `typeof` operator
+ - o (…) any object or primitive
+ - type (string) name of the type, e.g., `string`, `function`, `number`, etc.
+ = (boolean) `true` if given value is of given type
+\*/
+Snap.is = is;
+/*\
+ * Snap.snapTo
+ [ method ]
+ **
+ * Snaps given value to given grid
+ - values (array|number) given array of values or step of the grid
+ - value (number) value to adjust
+ - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`.
+ = (number) adjusted value
+\*/
+Snap.snapTo = function (values, value, tolerance) {
+    tolerance = is(tolerance, "finite") ? tolerance : 10;
+    if (is(values, "array")) {
+        var i = values.length;
+        while (i--) if (abs(values[i] - value) <= tolerance) {
+            return values[i];
+        }
+    } else {
+        values = +values;
+        var rem = value % values;
+        if (rem < tolerance) {
+            return value - rem;
+        }
+        if (rem > values - tolerance) {
+            return value - rem + values;
+        }
+    }
+    return value;
+};
+// Colour
+/*\
+ * Snap.getRGB
+ [ method ]
+ **
+ * Parses color string as RGB object
+ - color (string) color string in one of the following formats:
+ # <ul>
+ #     <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li>
+ #     <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li>
+ #     <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li>
+ #     <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200,&nbsp;100,&nbsp;0)</code>)</li>
+ #     <li>rgba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li>
+ #     <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>)</li>
+ #     <li>hsba(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li>
+ #     <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;0.5)</code>)</li>
+ #     <li>hsla(•••, •••, •••, •••) — also with opacity</li>
+ #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
+ #     <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li>
+ # </ul>
+ * Note that `%` can be used any time: `rgb(20%, 255, 50%)`.
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) true if string can't be parsed
+ o }
+\*/
+Snap.getRGB = cacher(function (colour) {
+    if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    if (colour == "none") {
+        return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString};
+    }
+    !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+    if (!colour) {
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+    }
+    var res,
+        red,
+        green,
+        blue,
+        opacity,
+        t,
+        values,
+        rgb = colour.match(colourRegExp);
+    if (rgb) {
+        if (rgb[2]) {
+            blue = toInt(rgb[2].substring(5), 16);
+            green = toInt(rgb[2].substring(3, 5), 16);
+            red = toInt(rgb[2].substring(1, 3), 16);
+        }
+        if (rgb[3]) {
+            blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+            green = toInt((t = rgb[3].charAt(2)) + t, 16);
+            red = toInt((t = rgb[3].charAt(1)) + t, 16);
+        }
+        if (rgb[4]) {
+            values = rgb[4].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red *= 2.55);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green *= 2.55);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue *= 2.55);
+            rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+        }
+        if (rgb[5]) {
+            values = rgb[5].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsb2rgb(red, green, blue, opacity);
+        }
+        if (rgb[6]) {
+            values = rgb[6].split(commaSpaces);
+            red = toFloat(values[0]);
+            values[0].slice(-1) == "%" && (red /= 100);
+            green = toFloat(values[1]);
+            values[1].slice(-1) == "%" && (green /= 100);
+            blue = toFloat(values[2]);
+            values[2].slice(-1) == "%" && (blue /= 100);
+            (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+            rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+            values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            return Snap.hsl2rgb(red, green, blue, opacity);
+        }
+        red = mmin(math.round(red), 255);
+        green = mmin(math.round(green), 255);
+        blue = mmin(math.round(blue), 255);
+        opacity = mmin(mmax(opacity, 0), 1);
+        rgb = {r: red, g: green, b: blue, toString: rgbtoString};
+        rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+        rgb.opacity = is(opacity, "finite") ? opacity : 1;
+        return rgb;
+    }
+    return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString};
+}, Snap);
+/*\
+ * Snap.hsb
+ [ method ]
+ **
+ * Converts HSB values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - b (number) value or brightness
+ = (string) hex representation of the color
+\*/
+Snap.hsb = cacher(function (h, s, b) {
+    return Snap.hsb2rgb(h, s, b).hex;
+});
+/*\
+ * Snap.hsl
+ [ method ]
+ **
+ * Converts HSL values to a hex representation of the color
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (string) hex representation of the color
+\*/
+Snap.hsl = cacher(function (h, s, l) {
+    return Snap.hsl2rgb(h, s, l).hex;
+});
+/*\
+ * Snap.rgb
+ [ method ]
+ **
+ * Converts RGB values to a hex representation of the color
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (string) hex representation of the color
+\*/
+Snap.rgb = cacher(function (r, g, b, o) {
+    if (is(o, "finite")) {
+        var round = math.round;
+        return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")";
+    }
+    return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+});
+var toHex = function (color) {
+    var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0],
+        red = "rgb(255, 0, 0)";
+    toHex = cacher(function (color) {
+        if (color.toLowerCase() == "red") {
+            return red;
+        }
+        i.style.color = red;
+        i.style.color = color;
+        var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+        return out == red ? null : out;
+    });
+    return toHex(color);
+},
+hsbtoString = function () {
+    return "hsb(" + [this.h, this.s, this.b] + ")";
+},
+hsltoString = function () {
+    return "hsl(" + [this.h, this.s, this.l] + ")";
+},
+rgbtoString = function () {
+    return this.opacity == 1 || this.opacity == null ?
+            this.hex :
+            "rgba(" + [this.r, this.g, this.b, this.opacity] + ")";
+},
+prepareRGB = function (r, g, b) {
+    if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) {
+        b = r.b;
+        g = r.g;
+        r = r.r;
+    }
+    if (g == null && is(r, string)) {
+        var clr = Snap.getRGB(r);
+        r = clr.r;
+        g = clr.g;
+        b = clr.b;
+    }
+    if (r > 1 || g > 1 || b > 1) {
+        r /= 255;
+        g /= 255;
+        b /= 255;
+    }
+
+    return [r, g, b];
+},
+packageRGB = function (r, g, b, o) {
+    r = math.round(r * 255);
+    g = math.round(g * 255);
+    b = math.round(b * 255);
+    var rgb = {
+        r: r,
+        g: g,
+        b: b,
+        opacity: is(o, "finite") ? o : 1,
+        hex: Snap.rgb(r, g, b),
+        toString: rgbtoString
+    };
+    is(o, "finite") && (rgb.opacity = o);
+    return rgb;
+};
+/*\
+ * Snap.color
+ [ method ]
+ **
+ * Parses the color string and returns an object featuring the color's component values
+ - clr (string) color string in one of the supported formats (see @Snap.getRGB)
+ = (object) Combined RGB/HSB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••,
+ o     error (boolean) `true` if string can't be parsed,
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     v (number) value (brightness),
+ o     l (number) lightness
+ o }
+\*/
+Snap.color = function (clr) {
+    var rgb;
+    if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+        rgb = Snap.hsb2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+        rgb = Snap.hsl2rgb(clr);
+        clr.r = rgb.r;
+        clr.g = rgb.g;
+        clr.b = rgb.b;
+        clr.opacity = 1;
+        clr.hex = rgb.hex;
+    } else {
+        if (is(clr, "string")) {
+            clr = Snap.getRGB(clr);
+        }
+        if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr && !("error" in clr)) {
+            rgb = Snap.rgb2hsl(clr);
+            clr.h = rgb.h;
+            clr.s = rgb.s;
+            clr.l = rgb.l;
+            rgb = Snap.rgb2hsb(clr);
+            clr.v = rgb.b;
+        } else {
+            clr = {hex: "none"};
+            clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+            clr.error = 1;
+        }
+    }
+    clr.toString = rgbtoString;
+    return clr;
+};
+/*\
+ * Snap.hsb2rgb
+ [ method ]
+ **
+ * Converts HSB values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - v (number) value or brightness
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsb2rgb = function (h, s, v, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "b" in h) {
+        v = h.b;
+        s = h.s;
+        o = h.o;
+        h = h.h;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = v * s;
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = v - C;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.hsl2rgb
+ [ method ]
+ **
+ * Converts HSL values to an RGB object
+ - h (number) hue
+ - s (number) saturation
+ - l (number) luminosity
+ = (object) RGB object in the following format:
+ o {
+ o     r (number) red,
+ o     g (number) green,
+ o     b (number) blue,
+ o     hex (string) color in HTML/CSS format: #••••••
+ o }
+\*/
+Snap.hsl2rgb = function (h, s, l, o) {
+    if (is(h, "object") && "h" in h && "s" in h && "l" in h) {
+        l = h.l;
+        s = h.s;
+        h = h.h;
+    }
+    if (h > 1 || s > 1 || l > 1) {
+        h /= 360;
+        s /= 100;
+        l /= 100;
+    }
+    h *= 360;
+    var R, G, B, X, C;
+    h = (h % 360) / 60;
+    C = 2 * s * (l < .5 ? l : 1 - l);
+    X = C * (1 - abs(h % 2 - 1));
+    R = G = B = l - C / 2;
+
+    h = ~~h;
+    R += [C, X, 0, 0, X, C][h];
+    G += [X, C, C, X, 0, 0][h];
+    B += [0, 0, X, C, C, X][h];
+    return packageRGB(R, G, B, o);
+};
+/*\
+ * Snap.rgb2hsb
+ [ method ]
+ **
+ * Converts RGB values to an HSB object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSB object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     b (number) brightness
+ o }
+\*/
+Snap.rgb2hsb = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, V, C;
+    V = mmax(r, g, b);
+    C = V - mmin(r, g, b);
+    H = (C == 0 ? null :
+         V == r ? (g - b) / C :
+         V == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4
+        );
+    H = ((H + 360) % 6) * 60 / 360;
+    S = C == 0 ? 0 : C / V;
+    return {h: H, s: S, b: V, toString: hsbtoString};
+};
+/*\
+ * Snap.rgb2hsl
+ [ method ]
+ **
+ * Converts RGB values to an HSL object
+ - r (number) red
+ - g (number) green
+ - b (number) blue
+ = (object) HSL object in the following format:
+ o {
+ o     h (number) hue,
+ o     s (number) saturation,
+ o     l (number) luminosity
+ o }
+\*/
+Snap.rgb2hsl = function (r, g, b) {
+    b = prepareRGB(r, g, b);
+    r = b[0];
+    g = b[1];
+    b = b[2];
+
+    var H, S, L, M, m, C;
+    M = mmax(r, g, b);
+    m = mmin(r, g, b);
+    C = M - m;
+    H = (C == 0 ? null :
+         M == r ? (g - b) / C :
+         M == g ? (b - r) / C + 2 :
+                  (r - g) / C + 4);
+    H = ((H + 360) % 6) * 60 / 360;
+    L = (M + m) / 2;
+    S = (C == 0 ? 0 :
+         L < .5 ? C / (2 * L) :
+                  C / (2 - 2 * L));
+    return {h: H, s: S, l: L, toString: hsltoString};
+};
+
+// Transformations
+/*\
+ * Snap.parsePathString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given path string into an array of arrays of path segments
+ - pathString (string|array) path string or array of segments (in the last case it is returned straight away)
+ = (array) array of segments
+\*/
+Snap.parsePathString = function (pathString) {
+    if (!pathString) {
+        return null;
+    }
+    var pth = Snap.path(pathString);
+    if (pth.arr) {
+        return Snap.path.clone(pth.arr);
+    }
+
+    var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
+        data = [];
+    if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
+        data = Snap.path.clone(pathString);
+    }
+    if (!data.length) {
+        Str(pathString).replace(pathCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            if (name == "m" && params.length > 2) {
+                data.push([b].concat(params.splice(0, 2)));
+                name = "l";
+                b = b == "m" ? "l" : "L";
+            }
+            if (name == "o" && params.length == 1) {
+                data.push([b, params[0]]);
+            }
+            if (name == "r") {
+                data.push([b].concat(params));
+            } else while (params.length >= paramCounts[name]) {
+                data.push([b].concat(params.splice(0, paramCounts[name])));
+                if (!paramCounts[name]) {
+                    break;
+                }
+            }
+        });
+    }
+    data.toString = Snap.path.toString;
+    pth.arr = Snap.path.clone(data);
+    return data;
+};
+/*\
+ * Snap.parseTransformString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given transform string into an array of transformations
+ - TString (string|array) transform string or array of transformations (in the last case it is returned straight away)
+ = (array) array of transformations
+\*/
+var parseTransformString = Snap.parseTransformString = function (TString) {
+    if (!TString) {
+        return null;
+    }
+    var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+        data = [];
+    if (is(TString, "array") && is(TString[0], "array")) { // rough assumption
+        data = Snap.path.clone(TString);
+    }
+    if (!data.length) {
+        Str(TString).replace(tCommand, function (a, b, c) {
+            var params = [],
+                name = b.toLowerCase();
+            c.replace(pathValues, function (a, b) {
+                b && params.push(+b);
+            });
+            data.push([b].concat(params));
+        });
+    }
+    data.toString = Snap.path.toString;
+    return data;
+};
+function svgTransform2string(tstr) {
+    var res = [];
+    tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
+        params = params.split(/\s*,\s*|\s+/);
+        if (name == "rotate" && params.length == 1) {
+            params.push(0, 0);
+        }
+        if (name == "scale") {
+            if (params.length > 2) {
+                params = params.slice(0, 2);
+            } else if (params.length == 2) {
+                params.push(0, 0);
+            }
+            if (params.length == 1) {
+                params.push(params[0], 0, 0);
+            }
+        }
+        if (name == "skewX") {
+            res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]);
+        } else if (name == "skewY") {
+            res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]);
+        } else {
+            res.push([name.charAt(0)].concat(params));
+        }
+        return all;
+    });
+    return res;
+}
+Snap._.svgTransform2string = svgTransform2string;
+Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i;
+function transform2matrix(tstr, bbox) {
+    var tdata = parseTransformString(tstr),
+        m = new Snap.Matrix;
+    if (tdata) {
+        for (var i = 0, ii = tdata.length; i < ii; i++) {
+            var t = tdata[i],
+                tlen = t.length,
+                command = Str(t[0]).toLowerCase(),
+                absolute = t[0] != command,
+                inver = absolute ? m.invert() : 0,
+                x1,
+                y1,
+                x2,
+                y2,
+                bb;
+            if (command == "t" && tlen == 2){
+                m.translate(t[1], 0);
+            } else if (command == "t" && tlen == 3) {
+                if (absolute) {
+                    x1 = inver.x(0, 0);
+                    y1 = inver.y(0, 0);
+                    x2 = inver.x(t[1], t[2]);
+                    y2 = inver.y(t[1], t[2]);
+                    m.translate(x2 - x1, y2 - y1);
+                } else {
+                    m.translate(t[1], t[2]);
+                }
+            } else if (command == "r") {
+                if (tlen == 2) {
+                    bb = bb || bbox;
+                    m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.rotate(t[1], x2, y2);
+                    } else {
+                        m.rotate(t[1], t[2], t[3]);
+                    }
+                }
+            } else if (command == "s") {
+                if (tlen == 2 || tlen == 3) {
+                    bb = bb || bbox;
+                    m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                } else if (tlen == 4) {
+                    if (absolute) {
+                        x2 = inver.x(t[2], t[3]);
+                        y2 = inver.y(t[2], t[3]);
+                        m.scale(t[1], t[1], x2, y2);
+                    } else {
+                        m.scale(t[1], t[1], t[2], t[3]);
+                    }
+                } else if (tlen == 5) {
+                    if (absolute) {
+                        x2 = inver.x(t[3], t[4]);
+                        y2 = inver.y(t[3], t[4]);
+                        m.scale(t[1], t[2], x2, y2);
+                    } else {
+                        m.scale(t[1], t[2], t[3], t[4]);
+                    }
+                }
+            } else if (command == "m" && tlen == 7) {
+                m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+            }
+        }
+    }
+    return m;
+}
+Snap._.transform2matrix = transform2matrix;
+Snap._unit2px = unit2px;
+var contains = glob.doc.contains || glob.doc.compareDocumentPosition ?
+    function (a, b) {
+        var adown = a.nodeType == 9 ? a.documentElement : a,
+            bup = b && b.parentNode;
+            return a == bup || !!(bup && bup.nodeType == 1 && (
+                adown.contains ?
+                    adown.contains(bup) :
+                    a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
+            ));
+    } :
+    function (a, b) {
+        if (b) {
+            while (b) {
+                b = b.parentNode;
+                if (b == a) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
+function getSomeDefs(el) {
+    var p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) ||
+            (el.node.parentNode && wrap(el.node.parentNode)) ||
+            Snap.select("svg") ||
+            Snap(0, 0),
+        pdefs = p.select("defs"),
+        defs  = pdefs == null ? false : pdefs.node;
+    if (!defs) {
+        defs = make("defs", p.node).node;
+    }
+    return defs;
+}
+function getSomeSVG(el) {
+    return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg");
+}
+Snap._.getSomeDefs = getSomeDefs;
+Snap._.getSomeSVG = getSomeSVG;
+function unit2px(el, name, value) {
+    var svg = getSomeSVG(el).node,
+        out = {},
+        mgr = svg.querySelector(".svg---mgr");
+    if (!mgr) {
+        mgr = $("rect");
+        $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"});
+        svg.appendChild(mgr);
+    }
+    function getW(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {width: val});
+        try {
+            return mgr.getBBox().width;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function getH(val) {
+        if (val == null) {
+            return E;
+        }
+        if (val == +val) {
+            return val;
+        }
+        $(mgr, {height: val});
+        try {
+            return mgr.getBBox().height;
+        } catch (e) {
+            return 0;
+        }
+    }
+    function set(nam, f) {
+        if (name == null) {
+            out[nam] = f(el.attr(nam) || 0);
+        } else if (nam == name) {
+            out = f(value == null ? el.attr(nam) || 0 : value);
+        }
+    }
+    switch (el.type) {
+        case "rect":
+            set("rx", getW);
+            set("ry", getH);
+        case "image":
+            set("width", getW);
+            set("height", getH);
+        case "text":
+            set("x", getW);
+            set("y", getH);
+        break;
+        case "circle":
+            set("cx", getW);
+            set("cy", getH);
+            set("r", getW);
+        break;
+        case "ellipse":
+            set("cx", getW);
+            set("cy", getH);
+            set("rx", getW);
+            set("ry", getH);
+        break;
+        case "line":
+            set("x1", getW);
+            set("x2", getW);
+            set("y1", getH);
+            set("y2", getH);
+        break;
+        case "marker":
+            set("refX", getW);
+            set("markerWidth", getW);
+            set("refY", getH);
+            set("markerHeight", getH);
+        break;
+        case "radialGradient":
+            set("fx", getW);
+            set("fy", getH);
+        break;
+        case "tspan":
+            set("dx", getW);
+            set("dy", getH);
+        break;
+        default:
+            set(name, getW);
+    }
+    svg.removeChild(mgr);
+    return out;
+}
+/*\
+ * Snap.select
+ [ method ]
+ **
+ * Wraps a DOM element specified by CSS selector as @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.select = function (query) {
+    query = Str(query).replace(/([^\\]):/g, "$1\\:");
+    return wrap(glob.doc.querySelector(query));
+};
+/*\
+ * Snap.selectAll
+ [ method ]
+ **
+ * Wraps DOM elements specified by CSS selector as set or array of @Element
+ - query (string) CSS selector of the element
+ = (Element) the current element
+\*/
+Snap.selectAll = function (query) {
+    var nodelist = glob.doc.querySelectorAll(query),
+        set = (Snap.set || Array)();
+    for (var i = 0; i < nodelist.length; i++) {
+        set.push(wrap(nodelist[i]));
+    }
+    return set;
+};
+
+function add2group(list) {
+    if (!is(list, "array")) {
+        list = Array.prototype.slice.call(arguments, 0);
+    }
+    var i = 0,
+        j = 0,
+        node = this.node;
+    while (this[i]) delete this[i++];
+    for (i = 0; i < list.length; i++) {
+        if (list[i].type == "set") {
+            list[i].forEach(function (el) {
+                node.appendChild(el.node);
+            });
+        } else {
+            node.appendChild(list[i].node);
+        }
+    }
+    var children = node.childNodes;
+    for (i = 0; i < children.length; i++) {
+        this[j++] = wrap(children[i]);
+    }
+    return this;
+}
+// Hub garbage collector every 10s
+setInterval(function () {
+    for (var key in hub) if (hub[has](key)) {
+        var el = hub[key],
+            node = el.node;
+        if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
+            delete hub[key];
+        }
+    }
+}, 1e4);
+function Element(el) {
+    if (el.snap in hub) {
+        return hub[el.snap];
+    }
+    var svg;
+    try {
+        svg = el.ownerSVGElement;
+    } catch(e) {}
+    /*\
+     * Element.node
+     [ property (object) ]
+     **
+     * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
+     > Usage
+     | // draw a circle at coordinate 10,10 with radius of 10
+     | var c = paper.circle(10, 10, 10);
+     | c.node.onclick = function () {
+     |     c.attr("fill", "red");
+     | };
+    \*/
+    this.node = el;
+    if (svg) {
+        this.paper = new Paper(svg);
+    }
+    /*\
+     * Element.type
+     [ property (string) ]
+     **
+     * SVG tag name of the given element.
+    \*/
+    this.type = el.tagName || el.nodeName;
+    var id = this.id = ID(this);
+    this.anims = {};
+    this._ = {
+        transform: []
+    };
+    el.snap = id;
+    hub[id] = this;
+    if (this.type == "g") {
+        this.add = add2group;
+    }
+    if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) {
+        for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
+            this[method] = Paper.prototype[method];
+        }
+    }
+}
+   /*\
+     * Element.attr
+     [ method ]
+     **
+     * Gets or sets given attributes of the element.
+     **
+     - params (object) contains key-value pairs of attributes you want to set
+     * or
+     - param (string) name of the attribute
+     = (Element) the current element
+     * or
+     = (string) value of attribute
+     > Usage
+     | el.attr({
+     |     fill: "#fc0",
+     |     stroke: "#000",
+     |     strokeWidth: 2, // CamelCase...
+     |     "fill-opacity": 0.5, // or dash-separated names
+     |     width: "*=2" // prefixed values
+     | });
+     | console.log(el.attr("fill")); // #fc0
+     * Prefixed values in format `"+=10"` supported. All four operations
+     * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+`
+     * and `-`: `"+=2em"`.
+    \*/
+    Element.prototype.attr = function (params, value) {
+        var el = this,
+            node = el.node;
+        if (!params) {
+            if (node.nodeType != 1) {
+                return {
+                    text: node.nodeValue
+                };
+            }
+            var attr = node.attributes,
+                out = {};
+            for (var i = 0, ii = attr.length; i < ii; i++) {
+                out[attr[i].nodeName] = attr[i].nodeValue;
+            }
+            return out;
+        }
+        if (is(params, "string")) {
+            if (arguments.length > 1) {
+                var json = {};
+                json[params] = value;
+                params = json;
+            } else {
+                return eve("snap.util.getattr." + params, el).firstDefined();
+            }
+        }
+        for (var att in params) {
+            if (params[has](att)) {
+                eve("snap.util.attr." + att, el, params[att]);
+            }
+        }
+        return el;
+    };
+/*\
+ * Snap.parse
+ [ method ]
+ **
+ * Parses SVG fragment and converts it into a @Fragment
+ **
+ - svg (string) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.parse = function (svg) {
+    var f = glob.doc.createDocumentFragment(),
+        full = true,
+        div = glob.doc.createElement("div");
+    svg = Str(svg);
+    if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) {
+        svg = "<svg>" + svg + "</svg>";
+        full = false;
+    }
+    div.innerHTML = svg;
+    svg = div.getElementsByTagName("svg")[0];
+    if (svg) {
+        if (full) {
+            f = svg;
+        } else {
+            while (svg.firstChild) {
+                f.appendChild(svg.firstChild);
+            }
+        }
+    }
+    return new Fragment(f);
+};
+function Fragment(frag) {
+    this.node = frag;
+}
+/*\
+ * Snap.fragment
+ [ method ]
+ **
+ * Creates a DOM fragment from a given list of elements or strings
+ **
+ - varargs (…) SVG string
+ = (Fragment) the @Fragment
+\*/
+Snap.fragment = function () {
+    var args = Array.prototype.slice.call(arguments, 0),
+        f = glob.doc.createDocumentFragment();
+    for (var i = 0, ii = args.length; i < ii; i++) {
+        var item = args[i];
+        if (item.node && item.node.nodeType) {
+            f.appendChild(item.node);
+        }
+        if (item.nodeType) {
+            f.appendChild(item);
+        }
+        if (typeof item == "string") {
+            f.appendChild(Snap.parse(item).node);
+        }
+    }
+    return new Fragment(f);
+};
+
+function make(name, parent) {
+    var res = $(name);
+    parent.appendChild(res);
+    var el = wrap(res);
+    return el;
+}
+function Paper(w, h) {
+    var res,
+        desc,
+        defs,
+        proto = Paper.prototype;
+    if (w && w.tagName == "svg") {
+        if (w.snap in hub) {
+            return hub[w.snap];
+        }
+        var doc = w.ownerDocument;
+        res = new Element(w);
+        desc = w.getElementsByTagName("desc")[0];
+        defs = w.getElementsByTagName("defs")[0];
+        if (!desc) {
+            desc = $("desc");
+            desc.appendChild(doc.createTextNode("Created with Snap"));
+            res.node.appendChild(desc);
+        }
+        if (!defs) {
+            defs = $("defs");
+            res.node.appendChild(defs);
+        }
+        res.defs = defs;
+        for (var key in proto) if (proto[has](key)) {
+            res[key] = proto[key];
+        }
+        res.paper = res.root = res;
+    } else {
+        res = make("svg", glob.doc.body);
+        $(res.node, {
+            height: h,
+            version: 1.1,
+            width: w,
+            xmlns: xmlns
+        });
+    }
+    return res;
+}
+function wrap(dom) {
+    if (!dom) {
+        return dom;
+    }
+    if (dom instanceof Element || dom instanceof Fragment) {
+        return dom;
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "svg") {
+        return new Paper(dom);
+    }
+    if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") {
+        return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]);
+    }
+    return new Element(dom);
+}
+
+Snap._.make = make;
+Snap._.wrap = wrap;
+/*\
+ * Paper.el
+ [ method ]
+ **
+ * Creates an element on paper with a given name and no attributes
+ **
+ - name (string) tag name
+ - attr (object) attributes
+ = (Element) the current element
+ > Usage
+ | var c = paper.circle(10, 10, 10); // is the same as...
+ | var c = paper.el("circle").attr({
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+ | // and the same as
+ | var c = paper.el("circle", {
+ |     cx: 10,
+ |     cy: 10,
+ |     r: 10
+ | });
+\*/
+Paper.prototype.el = function (name, attr) {
+    var el = make(name, this.node);
+    attr && el.attr(attr);
+    return el;
+};
+/*\
+ * Element.children
+ [ method ]
+ **
+ * Returns array of all the children of the element.
+ = (array) array of Elements
+\*/
+Element.prototype.children = function () {
+    var out = [],
+        ch = this.node.childNodes;
+    for (var i = 0, ii = ch.length; i < ii; i++) {
+        out[i] = Snap(ch[i]);
+    }
+    return out;
+};
+function jsonFiller(root, o) {
+    for (var i = 0, ii = root.length; i < ii; i++) {
+        var item = {
+                type: root[i].type,
+                attr: root[i].attr()
+            },
+            children = root[i].children();
+        o.push(item);
+        if (children.length) {
+            jsonFiller(children, item.childNodes = []);
+        }
+    }
+}
+/*\
+ * Element.toJSON
+ [ method ]
+ **
+ * Returns object representation of the given element and all its children.
+ = (object) in format
+ o {
+ o     type (string) this.type,
+ o     attr (object) attributes map,
+ o     childNodes (array) optional array of children in the same format
+ o }
+\*/
+Element.prototype.toJSON = function () {
+    var out = [];
+    jsonFiller([this], out);
+    return out[0];
+};
+// default
+eve.on("snap.util.getattr", function () {
+    var att = eve.nt();
+    att = att.substring(att.lastIndexOf(".") + 1);
+    var css = att.replace(/[A-Z]/g, function (letter) {
+        return "-" + letter.toLowerCase();
+    });
+    if (cssAttr[has](css)) {
+        return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css);
+    } else {
+        return $(this.node, att);
+    }
+});
+var cssAttr = {
+    "alignment-baseline": 0,
+    "baseline-shift": 0,
+    "clip": 0,
+    "clip-path": 0,
+    "clip-rule": 0,
+    "color": 0,
+    "color-interpolation": 0,
+    "color-interpolation-filters": 0,
+    "color-profile": 0,
+    "color-rendering": 0,
+    "cursor": 0,
+    "direction": 0,
+    "display": 0,
+    "dominant-baseline": 0,
+    "enable-background": 0,
+    "fill": 0,
+    "fill-opacity": 0,
+    "fill-rule": 0,
+    "filter": 0,
+    "flood-color": 0,
+    "flood-opacity": 0,
+    "font": 0,
+    "font-family": 0,
+    "font-size": 0,
+    "font-size-adjust": 0,
+    "font-stretch": 0,
+    "font-style": 0,
+    "font-variant": 0,
+    "font-weight": 0,
+    "glyph-orientation-horizontal": 0,
+    "glyph-orientation-vertical": 0,
+    "image-rendering": 0,
+    "kerning": 0,
+    "letter-spacing": 0,
+    "lighting-color": 0,
+    "marker": 0,
+    "marker-end": 0,
+    "marker-mid": 0,
+    "marker-start": 0,
+    "mask": 0,
+    "opacity": 0,
+    "overflow": 0,
+    "pointer-events": 0,
+    "shape-rendering": 0,
+    "stop-color": 0,
+    "stop-opacity": 0,
+    "stroke": 0,
+    "stroke-dasharray": 0,
+    "stroke-dashoffset": 0,
+    "stroke-linecap": 0,
+    "stroke-linejoin": 0,
+    "stroke-miterlimit": 0,
+    "stroke-opacity": 0,
+    "stroke-width": 0,
+    "text-anchor": 0,
+    "text-decoration": 0,
+    "text-rendering": 0,
+    "unicode-bidi": 0,
+    "visibility": 0,
+    "word-spacing": 0,
+    "writing-mode": 0
+};
+
+eve.on("snap.util.attr", function (value) {
+    var att = eve.nt(),
+        attr = {};
+    att = att.substring(att.lastIndexOf(".") + 1);
+    attr[att] = value;
+    var style = att.replace(/-(\w)/gi, function (all, letter) {
+            return letter.toUpperCase();
+        }),
+        css = att.replace(/[A-Z]/g, function (letter) {
+            return "-" + letter.toLowerCase();
+        });
+    if (cssAttr[has](css)) {
+        this.node.style[style] = value == null ? E : value;
+    } else {
+        $(this.node, attr);
+    }
+});
+(function (proto) {}(Paper.prototype));
+
+// simple ajax
+/*\
+ * Snap.ajax
+ [ method ]
+ **
+ * Simple implementation of Ajax
+ **
+ - url (string) URL
+ - postData (object|string) data for post request
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ * or
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+ = (XMLHttpRequest) the XMLHttpRequest object, just in case
+\*/
+Snap.ajax = function (url, postData, callback, scope){
+    var req = new XMLHttpRequest,
+        id = ID();
+    if (req) {
+        if (is(postData, "function")) {
+            scope = callback;
+            callback = postData;
+            postData = null;
+        } else if (is(postData, "object")) {
+            var pd = [];
+            for (var key in postData) if (postData.hasOwnProperty(key)) {
+                pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
+            }
+            postData = pd.join("&");
+        }
+        req.open((postData ? "POST" : "GET"), url, true);
+        if (postData) {
+            req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+            req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+        }
+        if (callback) {
+            eve.once("snap.ajax." + id + ".0", callback);
+            eve.once("snap.ajax." + id + ".200", callback);
+            eve.once("snap.ajax." + id + ".304", callback);
+        }
+        req.onreadystatechange = function() {
+            if (req.readyState != 4) return;
+            eve("snap.ajax." + id + "." + req.status, scope, req);
+        };
+        if (req.readyState == 4) {
+            return req;
+        }
+        req.send(postData);
+        return req;
+    }
+};
+/*\
+ * Snap.load
+ [ method ]
+ **
+ * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX)
+ **
+ - url (string) URL
+ - callback (function) callback
+ - scope (object) #optional scope of callback
+\*/
+Snap.load = function (url, callback, scope) {
+    Snap.ajax(url, function (req) {
+        var f = Snap.parse(req.responseText);
+        scope ? callback.call(scope, f) : callback(f);
+    });
+};
+var getOffset = function (elem) {
+    var box = elem.getBoundingClientRect(),
+        doc = elem.ownerDocument,
+        body = doc.body,
+        docElem = doc.documentElement,
+        clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+        top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
+        left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+    return {
+        y: top,
+        x: left
+    };
+};
+/*\
+ * Snap.getElementByPoint
+ [ method ]
+ **
+ * Returns you topmost element under given point.
+ **
+ = (object) Snap element object
+ - x (number) x coordinate from the top left corner of the window
+ - y (number) y coordinate from the top left corner of the window
+ > Usage
+ | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
+\*/
+Snap.getElementByPoint = function (x, y) {
+    var paper = this,
+        svg = paper.canvas,
+        target = glob.doc.elementFromPoint(x, y);
+    if (glob.win.opera && target.tagName == "svg") {
+        var so = getOffset(target),
+            sr = target.createSVGRect();
+        sr.x = x - so.x;
+        sr.y = y - so.y;
+        sr.width = sr.height = 1;
+        var hits = target.getIntersectionList(sr, null);
+        if (hits.length) {
+            target = hits[hits.length - 1];
+        }
+    }
+    if (!target) {
+        return null;
+    }
+    return wrap(target);
+};
+/*\
+ * Snap.plugin
+ [ method ]
+ **
+ * Let you write plugins. You pass in a function with five arguments, like this:
+ | Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
+ |     Snap.newmethod = function () {};
+ |     Element.prototype.newmethod = function () {};
+ |     Paper.prototype.newmethod = function () {};
+ | });
+ * Inside the function you have access to all main objects (and their
+ * prototypes). This allow you to extend anything you want.
+ **
+ - f (function) your plugin body
+\*/
+Snap.plugin = function (f) {
+    f(Snap, Element, Paper, glob, Fragment);
+};
+glob.win.Snap = Snap;
+return Snap;
+}(window || this));
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        Str = String,
+        unit2px = Snap._unit2px,
+        $ = Snap._.$,
+        make = Snap._.make,
+        getSomeDefs = Snap._.getSomeDefs,
+        has = "hasOwnProperty",
+        wrap = Snap._.wrap;
+    /*\
+     * Element.getBBox
+     [ method ]
+     **
+     * Returns the bounding box descriptor for the given element
+     **
+     = (object) bounding box descriptor:
+     o {
+     o     cx: (number) x of the center,
+     o     cy: (number) x of the center,
+     o     h: (number) height,
+     o     height: (number) height,
+     o     path: (string) path command for the box,
+     o     r0: (number) radius of a circle that fully encloses the box,
+     o     r1: (number) radius of the smallest circle that can be enclosed,
+     o     r2: (number) radius of the largest circle that can be enclosed,
+     o     vb: (string) box as a viewbox command,
+     o     w: (number) width,
+     o     width: (number) width,
+     o     x2: (number) x of the right side,
+     o     x: (number) x of the left side,
+     o     y2: (number) y of the bottom edge,
+     o     y: (number) y of the top edge
+     o }
+    \*/
+    elproto.getBBox = function (isWithoutTransform) {
+        if (!Snap.Matrix || !Snap.path) {
+            return this.node.getBBox();
+        }
+        var el = this,
+            m = new Snap.Matrix;
+        if (el.removed) {
+            return Snap._.box();
+        }
+        while (el.type == "use") {
+            if (!isWithoutTransform) {
+                m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0));
+            }
+            if (el.original) {
+                el = el.original;
+            } else {
+                var href = el.attr("xlink:href");
+                el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1));
+            }
+        }
+        var _ = el._,
+            pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt;
+        try {
+            if (isWithoutTransform) {
+                _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox());
+                return Snap._.box(_.bboxwt);
+            } else {
+                el.realPath = pathfinder(el);
+                el.matrix = el.transform().localMatrix;
+                _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix)));
+                return Snap._.box(_.bbox);
+            }
+        } catch (e) {
+            // Firefox doesn’t give you bbox of hidden element
+            return Snap._.box();
+        }
+    };
+    var propString = function () {
+        return this.string;
+    };
+    function extractTransform(el, tstr) {
+        if (tstr == null) {
+            var doReturn = true;
+            if (el.type == "linearGradient" || el.type == "radialGradient") {
+                tstr = el.node.getAttribute("gradientTransform");
+            } else if (el.type == "pattern") {
+                tstr = el.node.getAttribute("patternTransform");
+            } else {
+                tstr = el.node.getAttribute("transform");
+            }
+            if (!tstr) {
+                return new Snap.Matrix;
+            }
+            tstr = Snap._.svgTransform2string(tstr);
+        } else {
+            if (!Snap._.rgTransform.test(tstr)) {
+                tstr = Snap._.svgTransform2string(tstr);
+            } else {
+                tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || "");
+            }
+            if (is(tstr, "array")) {
+                tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr);
+            }
+            el._.transform = tstr;
+        }
+        var m = Snap._.transform2matrix(tstr, el.getBBox(1));
+        if (doReturn) {
+            return m;
+        } else {
+            el.matrix = m;
+        }
+    }
+    /*\
+     * Element.transform
+     [ method ]
+     **
+     * Gets or sets transformation of the element
+     **
+     - tstr (string) transform string in Snap or SVG format
+     = (Element) the current element
+     * or
+     = (object) transformation descriptor:
+     o {
+     o     string (string) transform string,
+     o     globalMatrix (Matrix) matrix of all transformations applied to element or its parents,
+     o     localMatrix (Matrix) matrix of transformations applied only to the element,
+     o     diffMatrix (Matrix) matrix of difference between global and local transformations,
+     o     global (string) global transformation as string,
+     o     local (string) local transformation as string,
+     o     toString (function) returns `string` property
+     o }
+    \*/
+    elproto.transform = function (tstr) {
+        var _ = this._;
+        if (tstr == null) {
+            var papa = this,
+                global = new Snap.Matrix(this.node.getCTM()),
+                local = extractTransform(this),
+                ms = [local],
+                m = new Snap.Matrix,
+                i,
+                localString = local.toTransformString(),
+                string = Str(local) == Str(this.matrix) ?
+                            Str(_.transform) : localString;
+            while (papa.type != "svg" && (papa = papa.parent())) {
+                ms.push(extractTransform(papa));
+            }
+            i = ms.length;
+            while (i--) {
+                m.add(ms[i]);
+            }
+            return {
+                string: string,
+                globalMatrix: global,
+                totalMatrix: m,
+                localMatrix: local,
+                diffMatrix: global.clone().add(local.invert()),
+                global: global.toTransformString(),
+                total: m.toTransformString(),
+                local: localString,
+                toString: propString
+            };
+        }
+        if (tstr instanceof Snap.Matrix) {
+            this.matrix = tstr;
+            this._.transform = tstr.toTransformString();
+        } else {
+            extractTransform(this, tstr);
+        }
+
+        if (this.node) {
+            if (this.type == "linearGradient" || this.type == "radialGradient") {
+                $(this.node, {gradientTransform: this.matrix});
+            } else if (this.type == "pattern") {
+                $(this.node, {patternTransform: this.matrix});
+            } else {
+                $(this.node, {transform: this.matrix});
+            }
+        }
+
+        return this;
+    };
+    /*\
+     * Element.parent
+     [ method ]
+     **
+     * Returns the element's parent
+     **
+     = (Element) the parent element
+    \*/
+    elproto.parent = function () {
+        return wrap(this.node.parentNode);
+    };
+    /*\
+     * Element.append
+     [ method ]
+     **
+     * Appends the given element to current one
+     **
+     - el (Element|Set) element to append
+     = (Element) the parent element
+    \*/
+    /*\
+     * Element.add
+     [ method ]
+     **
+     * See @Element.append
+    \*/
+    elproto.append = elproto.add = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this;
+                el.forEach(function (el) {
+                    it.add(el);
+                });
+                return this;
+            }
+            el = wrap(el);
+            this.node.appendChild(el.node);
+            el.paper = this.paper;
+        }
+        return this;
+    };
+    /*\
+     * Element.appendTo
+     [ method ]
+     **
+     * Appends the current element to the given one
+     **
+     - el (Element) parent element to append to
+     = (Element) the child element
+    \*/
+    elproto.appendTo = function (el) {
+        if (el) {
+            el = wrap(el);
+            el.append(this);
+        }
+        return this;
+    };
+    /*\
+     * Element.prepend
+     [ method ]
+     **
+     * Prepends the given element to the current one
+     **
+     - el (Element) element to prepend
+     = (Element) the parent element
+    \*/
+    elproto.prepend = function (el) {
+        if (el) {
+            if (el.type == "set") {
+                var it = this,
+                    first;
+                el.forEach(function (el) {
+                    if (first) {
+                        first.after(el);
+                    } else {
+                        it.prepend(el);
+                    }
+                    first = el;
+                });
+                return this;
+            }
+            el = wrap(el);
+            var parent = el.parent();
+            this.node.insertBefore(el.node, this.node.firstChild);
+            this.add && this.add();
+            el.paper = this.paper;
+            this.parent() && this.parent().add();
+            parent && parent.add();
+        }
+        return this;
+    };
+    /*\
+     * Element.prependTo
+     [ method ]
+     **
+     * Prepends the current element to the given one
+     **
+     - el (Element) parent element to prepend to
+     = (Element) the child element
+    \*/
+    elproto.prependTo = function (el) {
+        el = wrap(el);
+        el.prepend(this);
+        return this;
+    };
+    /*\
+     * Element.before
+     [ method ]
+     **
+     * Inserts given element before the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.before = function (el) {
+        if (el.type == "set") {
+            var it = this;
+            el.forEach(function (el) {
+                var parent = el.parent();
+                it.node.parentNode.insertBefore(el.node, it.node);
+                parent && parent.add();
+            });
+            this.parent().add();
+            return this;
+        }
+        el = wrap(el);
+        var parent = el.parent();
+        this.node.parentNode.insertBefore(el.node, this.node);
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.after
+     [ method ]
+     **
+     * Inserts given element after the current one
+     **
+     - el (Element) element to insert
+     = (Element) the parent element
+    \*/
+    elproto.after = function (el) {
+        el = wrap(el);
+        var parent = el.parent();
+        if (this.node.nextSibling) {
+            this.node.parentNode.insertBefore(el.node, this.node.nextSibling);
+        } else {
+            this.node.parentNode.appendChild(el.node);
+        }
+        this.parent() && this.parent().add();
+        parent && parent.add();
+        el.paper = this.paper;
+        return this;
+    };
+    /*\
+     * Element.insertBefore
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertBefore = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.insertAfter
+     [ method ]
+     **
+     * Inserts the element after the given one
+     **
+     - el (Element) element next to whom insert to
+     = (Element) the parent element
+    \*/
+    elproto.insertAfter = function (el) {
+        el = wrap(el);
+        var parent = this.parent();
+        el.node.parentNode.insertBefore(this.node, el.node.nextSibling);
+        this.paper = el.paper;
+        parent && parent.add();
+        el.parent() && el.parent().add();
+        return this;
+    };
+    /*\
+     * Element.remove
+     [ method ]
+     **
+     * Removes element from the DOM
+     = (Element) the detached element
+    \*/
+    elproto.remove = function () {
+        var parent = this.parent();
+        this.node.parentNode && this.node.parentNode.removeChild(this.node);
+        delete this.paper;
+        this.removed = true;
+        parent && parent.add();
+        return this;
+    };
+    /*\
+     * Element.select
+     [ method ]
+     **
+     * Gathers the nested @Element matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Element) result of query selection
+    \*/
+    elproto.select = function (query) {
+        return wrap(this.node.querySelector(query));
+    };
+    /*\
+     * Element.selectAll
+     [ method ]
+     **
+     * Gathers nested @Element objects matching the given set of CSS selectors
+     **
+     - query (string) CSS selector
+     = (Set|array) result of query selection
+    \*/
+    elproto.selectAll = function (query) {
+        var nodelist = this.node.querySelectorAll(query),
+            set = (Snap.set || Array)();
+        for (var i = 0; i < nodelist.length; i++) {
+            set.push(wrap(nodelist[i]));
+        }
+        return set;
+    };
+    /*\
+     * Element.asPX
+     [ method ]
+     **
+     * Returns given attribute of the element as a `px` value (not %, em, etc.)
+     **
+     - attr (string) attribute name
+     - value (string) #optional attribute value
+     = (Element) result of query selection
+    \*/
+    elproto.asPX = function (attr, value) {
+        if (value == null) {
+            value = this.attr(attr);
+        }
+        return +unit2px(this, attr, value);
+    };
+    // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned <use> instantiates. It's a part of SVG with which ordinary web developers may be least familiar.
+    /*\
+     * Element.use
+     [ method ]
+     **
+     * Creates a `<use>` element linked to the current element
+     **
+     = (Element) the `<use>` element
+    \*/
+    elproto.use = function () {
+        var use,
+            id = this.node.id;
+        if (!id) {
+            id = this.id;
+            $(this.node, {
+                id: id
+            });
+        }
+        if (this.type == "linearGradient" || this.type == "radialGradient" ||
+            this.type == "pattern") {
+            use = make(this.type, this.node.parentNode);
+        } else {
+            use = make("use", this.node.parentNode);
+        }
+        $(use.node, {
+            "xlink:href": "#" + id
+        });
+        use.original = this;
+        return use;
+    };
+    function fixids(el) {
+        var els = el.selectAll("*"),
+            it,
+            url = /^\s*url\(("|'|)(.*)\1\)\s*$/,
+            ids = [],
+            uses = {};
+        function urltest(it, name) {
+            var val = $(it.node, name);
+            val = val && val.match(url);
+            val = val && val[2];
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    var attr = {};
+                    attr[name] = URL(id);
+                    $(it.node, attr);
+                });
+            }
+        }
+        function linktest(it) {
+            var val = $(it.node, "xlink:href");
+            if (val && val.charAt() == "#") {
+                val = val.substring(1);
+            } else {
+                return;
+            }
+            if (val) {
+                uses[val] = (uses[val] || []).concat(function (id) {
+                    it.attr("xlink:href", "#" + id);
+                });
+            }
+        }
+        for (var i = 0, ii = els.length; i < ii; i++) {
+            it = els[i];
+            urltest(it, "fill");
+            urltest(it, "stroke");
+            urltest(it, "filter");
+            urltest(it, "mask");
+            urltest(it, "clip-path");
+            linktest(it);
+            var oldid = $(it.node, "id");
+            if (oldid) {
+                $(it.node, {id: it.id});
+                ids.push({
+                    old: oldid,
+                    id: it.id
+                });
+            }
+        }
+        for (i = 0, ii = ids.length; i < ii; i++) {
+            var fs = uses[ids[i].old];
+            if (fs) {
+                for (var j = 0, jj = fs.length; j < jj; j++) {
+                    fs[j](ids[i].id);
+                }
+            }
+        }
+    }
+    /*\
+     * Element.clone
+     [ method ]
+     **
+     * Creates a clone of the element and inserts it after the element
+     **
+     = (Element) the clone
+    \*/
+    elproto.clone = function () {
+        var clone = wrap(this.node.cloneNode(true));
+        if ($(clone.node, "id")) {
+            $(clone.node, {id: clone.id});
+        }
+        fixids(clone);
+        clone.insertAfter(this);
+        return clone;
+    };
+    /*\
+     * Element.toDefs
+     [ method ]
+     **
+     * Moves element to the shared `<defs>` area
+     **
+     = (Element) the element
+    \*/
+    elproto.toDefs = function () {
+        var defs = getSomeDefs(this);
+        defs.appendChild(this.node);
+        return this;
+    };
+    /*\
+     * Element.toPattern
+     [ method ]
+     **
+     * Creates a `<pattern>` element from the current element
+     **
+     * To create a pattern you have to specify the pattern rect:
+     - x (string|number)
+     - y (string|number)
+     - width (string|number)
+     - height (string|number)
+     = (Element) the `<pattern>` element
+     * You can use pattern later on as an argument for `fill` attribute:
+     | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
+     |         fill: "none",
+     |         stroke: "#bada55",
+     |         strokeWidth: 5
+     |     }).pattern(0, 0, 10, 10),
+     |     c = paper.circle(200, 200, 100);
+     | c.attr({
+     |     fill: p
+     | });
+    \*/
+    elproto.pattern = elproto.toPattern = function (x, y, width, height) {
+        var p = make("pattern", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        $(p.node, {
+            x: x,
+            y: y,
+            width: width,
+            height: height,
+            patternUnits: "userSpaceOnUse",
+            id: p.id,
+            viewBox: [x, y, width, height].join(" ")
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+// SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path.
+// SIERRA Element.marker(): I suggest the method should accept default reference point values.  Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where?  Couldn't they also be assigned default values?
+    /*\
+     * Element.marker
+     [ method ]
+     **
+     * Creates a `<marker>` element from the current element
+     **
+     * To create a marker you have to specify the bounding rect and reference point:
+     - x (number)
+     - y (number)
+     - width (number)
+     - height (number)
+     - refX (number)
+     - refY (number)
+     = (Element) the `<marker>` element
+     * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end.
+    \*/
+    // TODO add usage for markers
+    elproto.marker = function (x, y, width, height, refX, refY) {
+        var p = make("marker", getSomeDefs(this));
+        if (x == null) {
+            x = this.getBBox();
+        }
+        if (is(x, "object") && "x" in x) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            refX = x.refX || x.cx;
+            refY = x.refY || x.cy;
+            x = x.x;
+        }
+        $(p.node, {
+            viewBox: [x, y, width, height].join(" "),
+            markerWidth: width,
+            markerHeight: height,
+            orient: "auto",
+            refX: refX || 0,
+            refY: refY || 0,
+            id: p.id
+        });
+        p.node.appendChild(this.node);
+        return p;
+    };
+    // animation
+    function slice(from, to, f) {
+        return function (arr) {
+            var res = arr.slice(from, to);
+            if (res.length == 1) {
+                res = res[0];
+            }
+            return f ? f(res) : res;
+        };
+    }
+    var Animation = function (attr, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        this.attr = attr;
+        this.dur = ms;
+        easing && (this.easing = easing);
+        callback && (this.callback = callback);
+    };
+    Snap._.Animation = Animation;
+    /*\
+     * Snap.animation
+     [ method ]
+     **
+     * Creates an animation object
+     **
+     - attr (object) attributes of final destination
+     - duration (number) duration of the animation, in milliseconds
+     - easing (function) #optional one of easing functions of @mina or custom one
+     - callback (function) #optional callback function that fires when animation ends
+     = (object) animation object
+    \*/
+    Snap.animation = function (attr, ms, easing, callback) {
+        return new Animation(attr, ms, easing, callback);
+    };
+    /*\
+     * Element.inAnim
+     [ method ]
+     **
+     * Returns a set of animations that may be able to manipulate the current element
+     **
+     = (object) in format:
+     o {
+     o     anim (object) animation object,
+     o     mina (object) @mina object,
+     o     curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+    \*/
+    elproto.inAnim = function () {
+        var el = this,
+            res = [];
+        for (var id in el.anims) if (el.anims[has](id)) {
+            (function (a) {
+                res.push({
+                    anim: new Animation(a._attrs, a.dur, a.easing, a._callback),
+                    mina: a,
+                    curStatus: a.status(),
+                    status: function (val) {
+                        return a.status(val);
+                    },
+                    stop: function () {
+                        a.stop();
+                    }
+                });
+            }(el.anims[id]));
+        }
+        return res;
+    };
+    /*\
+     * Snap.animate
+     [ method ]
+     **
+     * Runs generic animation of one number into another with a caring function
+     **
+     - from (number|array) number or array of numbers
+     - to (number|array) number or array of numbers
+     - setter (function) caring function that accepts one number argument
+     - duration (number) duration, in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function to execute when animation ends
+     = (object) animation object in @mina format
+     o {
+     o     id (string) animation id, consider it read-only,
+     o     duration (function) gets or sets the duration of the animation,
+     o     easing (function) easing,
+     o     speed (function) gets or sets the speed of the animation,
+     o     status (function) gets or sets the status of the animation,
+     o     stop (function) stops the animation
+     o }
+     | var rect = Snap().rect(0, 0, 10, 10);
+     | Snap.animate(0, 10, function (val) {
+     |     rect.attr({
+     |         x: val
+     |     });
+     | }, 1000);
+     | // in given context is equivalent to
+     | rect.animate({x: 10}, 1000);
+    \*/
+    Snap.animate = function (from, to, setter, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        var now = mina.time(),
+            anim = mina(from, to, now, now + ms, mina.time, setter, easing);
+        callback && eve.once("mina.finish." + anim.id, callback);
+        return anim;
+    };
+    /*\
+     * Element.stop
+     [ method ]
+     **
+     * Stops all the animations for the current element
+     **
+     = (Element) the current element
+    \*/
+    elproto.stop = function () {
+        var anims = this.inAnim();
+        for (var i = 0, ii = anims.length; i < ii; i++) {
+            anims[i].stop();
+        }
+        return this;
+    };
+    /*\
+     * Element.animate
+     [ method ]
+     **
+     * Animates the given attributes of the element
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     = (Element) the current element
+    \*/
+    elproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = attrs.dur;
+            attrs = attrs.attr;
+        }
+        var fkeys = [], tkeys = [], keys = {}, from, to, f, eq,
+            el = this;
+        for (var key in attrs) if (attrs[has](key)) {
+            if (el.equal) {
+                eq = el.equal(key, Str(attrs[key]));
+                from = eq.from;
+                to = eq.to;
+                f = eq.f;
+            } else {
+                from = +el.attr(key);
+                to = +attrs[key];
+            }
+            var len = is(from, "array") ? from.length : 1;
+            keys[key] = slice(fkeys.length, fkeys.length + len, f);
+            fkeys = fkeys.concat(from);
+            tkeys = tkeys.concat(to);
+        }
+        var now = mina.time(),
+            anim = mina(fkeys, tkeys, now, now + ms, mina.time, function (val) {
+                var attr = {};
+                for (var key in keys) if (keys[has](key)) {
+                    attr[key] = keys[key](val);
+                }
+                el.attr(attr);
+            }, easing);
+        el.anims[anim.id] = anim;
+        anim._attrs = attrs;
+        anim._callback = callback;
+        eve("snap.animcreated." + el.id, anim);
+        eve.once("mina.finish." + anim.id, function () {
+            delete el.anims[anim.id];
+            callback && callback.call(el);
+        });
+        eve.once("mina.stop." + anim.id, function () {
+            delete el.anims[anim.id];
+        });
+        return el;
+    };
+    var eldata = {};
+    /*\
+     * Element.data
+     [ method ]
+     **
+     * Adds or retrieves given value associated with given key. (Don’t confuse
+     * with `data-` attributes)
+     *
+     * See also @Element.removeData
+     - key (string) key to store data
+     - value (any) #optional value to store
+     = (object) @Element
+     * or, if value is not specified:
+     = (any) value
+     > Usage
+     | for (var i = 0, i < 5, i++) {
+     |     paper.circle(10 + 15 * i, 10, 10)
+     |          .attr({fill: "#000"})
+     |          .data("i", i)
+     |          .click(function () {
+     |             alert(this.data("i"));
+     |          });
+     | }
+    \*/
+    elproto.data = function (key, value) {
+        var data = eldata[this.id] = eldata[this.id] || {};
+        if (arguments.length == 0){
+            eve("snap.data.get." + this.id, this, data, null);
+            return data;
+        }
+        if (arguments.length == 1) {
+            if (Snap.is(key, "object")) {
+                for (var i in key) if (key[has](i)) {
+                    this.data(i, key[i]);
+                }
+                return this;
+            }
+            eve("snap.data.get." + this.id, this, data[key], key);
+            return data[key];
+        }
+        data[key] = value;
+        eve("snap.data.set." + this.id, this, value, key);
+        return this;
+    };
+    /*\
+     * Element.removeData
+     [ method ]
+     **
+     * Removes value associated with an element by given key.
+     * If key is not provided, removes all the data of the element.
+     - key (string) #optional key
+     = (object) @Element
+    \*/
+    elproto.removeData = function (key) {
+        if (key == null) {
+            eldata[this.id] = {};
+        } else {
+            eldata[this.id] && delete eldata[this.id][key];
+        }
+        return this;
+    };
+    /*\
+     * Element.outerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element, equivalent to HTML's `outerHTML`.
+     *
+     * See also @Element.innerSVG
+     = (string) SVG code for the element
+    \*/
+    /*\
+     * Element.toString
+     [ method ]
+     **
+     * See @Element.outerSVG
+    \*/
+    elproto.outerSVG = elproto.toString = toString(1);
+    /*\
+     * Element.innerSVG
+     [ method ]
+     **
+     * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML`
+     = (string) SVG code for the element
+    \*/
+    elproto.innerSVG = toString();
+    function toString(type) {
+        return function () {
+            var res = type ? "<" + this.type : "",
+                attr = this.node.attributes,
+                chld = this.node.childNodes;
+            if (type) {
+                for (var i = 0, ii = attr.length; i < ii; i++) {
+                    res += " " + attr[i].name + '="' +
+                            attr[i].value.replace(/"/g, '\\"') + '"';
+                }
+            }
+            if (chld.length) {
+                type && (res += ">");
+                for (i = 0, ii = chld.length; i < ii; i++) {
+                    if (chld[i].nodeType == 3) {
+                        res += chld[i].nodeValue;
+                    } else if (chld[i].nodeType == 1) {
+                        res += wrap(chld[i]).toString();
+                    }
+                }
+                type && (res += "</" + this.type + ">");
+            } else {
+                type && (res += "/>");
+            }
+            return res;
+        };
+    }
+    elproto.toDataURL = function () {
+        if (window && window.btoa) {
+            var bb = this.getBBox(),
+                svg = Snap.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>', {
+                x: +bb.x.toFixed(3),
+                y: +bb.y.toFixed(3),
+                width: +bb.width.toFixed(3),
+                height: +bb.height.toFixed(3),
+                contents: this.outerSVG()
+            });
+            return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
+        }
+    };
+    /*\
+     * Fragment.select
+     [ method ]
+     **
+     * See @Element.select
+    \*/
+    Fragment.prototype.select = elproto.select;
+    /*\
+     * Fragment.selectAll
+     [ method ]
+     **
+     * See @Element.selectAll
+    \*/
+    Fragment.prototype.selectAll = elproto.selectAll;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var objectToString = Object.prototype.toString,
+        Str = String,
+        math = Math,
+        E = "";
+    function Matrix(a, b, c, d, e, f) {
+        if (b == null && objectToString.call(a) == "[object SVGMatrix]") {
+            this.a = a.a;
+            this.b = a.b;
+            this.c = a.c;
+            this.d = a.d;
+            this.e = a.e;
+            this.f = a.f;
+            return;
+        }
+        if (a != null) {
+            this.a = +a;
+            this.b = +b;
+            this.c = +c;
+            this.d = +d;
+            this.e = +e;
+            this.f = +f;
+        } else {
+            this.a = 1;
+            this.b = 0;
+            this.c = 0;
+            this.d = 1;
+            this.e = 0;
+            this.f = 0;
+        }
+    }
+    (function (matrixproto) {
+        /*\
+         * Matrix.add
+         [ method ]
+         **
+         * Adds the given matrix to existing one
+         - a (number)
+         - b (number)
+         - c (number)
+         - d (number)
+         - e (number)
+         - f (number)
+         * or
+         - matrix (object) @Matrix
+        \*/
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
+
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
+
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
+                }
+            }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+            return this;
+        };
+        /*\
+         * Matrix.invert
+         [ method ]
+         **
+         * Returns an inverted version of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        /*\
+         * Matrix.clone
+         [ method ]
+         **
+         * Returns a copy of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        /*\
+         * Matrix.translate
+         [ method ]
+         **
+         * Translate the matrix
+         - x (number) horizontal offset distance
+         - y (number) vertical offset distance
+        \*/
+        matrixproto.translate = function (x, y) {
+            return this.add(1, 0, 0, 1, x, y);
+        };
+        /*\
+         * Matrix.scale
+         [ method ]
+         **
+         * Scales the matrix
+         - x (number) amount to be scaled, with `1` resulting in no change
+         - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.)
+         - cx (number) #optional horizontal origin point from which to scale
+         - cy (number) #optional vertical origin point from which to scale
+         * Default cx, cy is the middle point of the element.
+        \*/
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+            return this;
+        };
+        /*\
+         * Matrix.rotate
+         [ method ]
+         **
+         * Rotates the matrix
+         - a (number) angle of rotation, in degrees
+         - x (number) horizontal origin point from which to rotate
+         - y (number) vertical origin point from which to rotate
+        \*/
+        matrixproto.rotate = function (a, x, y) {
+            a = Snap.rad(a);
+            x = x || 0;
+            y = y || 0;
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            return this.add(1, 0, 0, 1, -x, -y);
+        };
+        /*\
+         * Matrix.x
+         [ method ]
+         **
+         * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y
+         - x (number)
+         - y (number)
+         = (number) x
+        \*/
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        /*\
+         * Matrix.y
+         [ method ]
+         **
+         * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x
+         - x (number)
+         - y (number)
+         = (number) y
+        \*/
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
+        }
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] && (a[0] /= mag);
+            a[1] && (a[1] /= mag);
+        }
+        /*\
+         * Matrix.determinant
+         [ method ]
+         **
+         * Finds determinant of the given matrix.
+         = (number) determinant
+        \*/
+        matrixproto.determinant = function () {
+            return this.a * this.d - this.b * this.c;
+        };
+        /*\
+         * Matrix.split
+         [ method ]
+         **
+         * Splits matrix into primitive transformations
+         = (object) in format:
+         o dx (number) translation by x
+         o dy (number) translation by y
+         o scalex (number) scale by x
+         o scaley (number) scale by y
+         o shear (number) shear
+         o rotate (number) rotation in deg
+         o isSimple (boolean) could it be represented via simple transformations
+        \*/
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
+
+            if (this.determinant() < 0) {
+                out.scalex = -out.scalex;
+            }
+
+            // rotation
+            var sin = -row[0][1],
+                cos = row[1][1];
+            if (cos < 0) {
+                out.rotate = Snap.deg(math.acos(cos));
+                if (sin < 0) {
+                    out.rotate = 360 - out.rotate;
+                }
+            } else {
+                out.rotate = Snap.deg(math.asin(sin));
+            }
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+            return out;
+        };
+        /*\
+         * Matrix.toTransformString
+         [ method ]
+         **
+         * Returns transform string that represents given matrix
+         = (string) transform string
+        \*/
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this.split();
+            if (!+s.shear.toFixed(9)) {
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) +
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E);
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+    /*\
+     * Snap.Matrix
+     [ method ]
+     **
+     * Matrix constructor, extend on your own risk.
+     * To create matrices use @Snap.matrix.
+    \*/
+    Snap.Matrix = Matrix;
+    /*\
+     * Snap.matrix
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns a matrix based on the given parameters
+     - a (number)
+     - b (number)
+     - c (number)
+     - d (number)
+     - e (number)
+     - f (number)
+     * or
+     - svgMatrix (SVGMatrix)
+     = (object) @Matrix
+    \*/
+    Snap.matrix = function (a, b, c, d, e, f) {
+        return new Matrix(a, b, c, d, e, f);
+    };
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var has = "hasOwnProperty",
+        make = Snap._.make,
+        wrap = Snap._.wrap,
+        is = Snap.is,
+        getSomeDefs = Snap._.getSomeDefs,
+        reURLValue = /^url\(#?([^)]+)\)$/,
+        $ = Snap._.$,
+        URL = Snap.url,
+        Str = String,
+        separator = Snap._.separator,
+        E = "";
+    // Attributes event handlers
+    eve.on("snap.util.attr.mask", function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value.type == "mask") {
+                var mask = value;
+            } else {
+                mask = make("mask", getSomeDefs(this));
+                mask.node.appendChild(value.node);
+            }
+            !mask.node.id && $(mask.node, {
+                id: mask.id
+            });
+            $(this.node, {
+                mask: URL(mask.id)
+            });
+        }
+    });
+    (function (clipIt) {
+        eve.on("snap.util.attr.clip", clipIt);
+        eve.on("snap.util.attr.clip-path", clipIt);
+        eve.on("snap.util.attr.clipPath", clipIt);
+    }(function (value) {
+        if (value instanceof Element || value instanceof Fragment) {
+            eve.stop();
+            if (value.type == "clipPath") {
+                var clip = value;
+            } else {
+                clip = make("clipPath", getSomeDefs(this));
+                clip.node.appendChild(value.node);
+                !clip.node.id && $(clip.node, {
+                    id: clip.id
+                });
+            }
+            $(this.node, {
+                "clip-path": URL(clip.node.id || clip.id)
+            });
+        }
+    }));
+    function fillStroke(name) {
+        return function (value) {
+            eve.stop();
+            if (value instanceof Fragment && value.node.childNodes.length == 1 &&
+                (value.node.firstChild.tagName == "radialGradient" ||
+                value.node.firstChild.tagName == "linearGradient" ||
+                value.node.firstChild.tagName == "pattern")) {
+                value = value.node.firstChild;
+                getSomeDefs(this).appendChild(value);
+                value = wrap(value);
+            }
+            if (value instanceof Element) {
+                if (value.type == "radialGradient" || value.type == "linearGradient"
+                   || value.type == "pattern") {
+                    if (!value.node.id) {
+                        $(value.node, {
+                            id: value.id
+                        });
+                    }
+                    var fill = URL(value.node.id);
+                } else {
+                    fill = value.attr(name);
+                }
+            } else {
+                fill = Snap.color(value);
+                if (fill.error) {
+                    var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value);
+                    if (grad) {
+                        if (!grad.node.id) {
+                            $(grad.node, {
+                                id: grad.id
+                            });
+                        }
+                        fill = URL(grad.node.id);
+                    } else {
+                        fill = value;
+                    }
+                } else {
+                    fill = Str(fill);
+                }
+            }
+            var attrs = {};
+            attrs[name] = fill;
+            $(this.node, attrs);
+            this.node.style[name] = E;
+        };
+    }
+    eve.on("snap.util.attr.fill", fillStroke("fill"));
+    eve.on("snap.util.attr.stroke", fillStroke("stroke"));
+    var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i;
+    eve.on("snap.util.grad.parse", function parseGrad(string) {
+        string = Str(string);
+        var tokens = string.match(gradrg);
+        if (!tokens) {
+            return null;
+        }
+        var type = tokens[1],
+            params = tokens[2],
+            stops = tokens[3];
+        params = params.split(/\s*,\s*/).map(function (el) {
+            return +el == el ? +el : el;
+        });
+        if (params.length == 1 && params[0] == 0) {
+            params = [];
+        }
+        stops = stops.split("-");
+        stops = stops.map(function (el) {
+            el = el.split(":");
+            var out = {
+                color: el[0]
+            };
+            if (el[1]) {
+                out.offset = parseFloat(el[1]);
+            }
+            return out;
+        });
+        return {
+            type: type,
+            params: params,
+            stops: stops
+        };
+    });
+
+    eve.on("snap.util.attr.d", function (value) {
+        eve.stop();
+        if (is(value, "array") && is(value[0], "array")) {
+            value = Snap.path.toString.call(value);
+        }
+        value = Str(value);
+        if (value.match(/[ruo]/i)) {
+            value = Snap.path.toAbsolute(value);
+        }
+        $(this.node, {d: value});
+    })(-1);
+    eve.on("snap.util.attr.#text", function (value) {
+        eve.stop();
+        value = Str(value);
+        var txt = glob.doc.createTextNode(value);
+        while (this.node.firstChild) {
+            this.node.removeChild(this.node.firstChild);
+        }
+        this.node.appendChild(txt);
+    })(-1);
+    eve.on("snap.util.attr.path", function (value) {
+        eve.stop();
+        this.attr({d: value});
+    })(-1);
+    eve.on("snap.util.attr.class", function (value) {
+        eve.stop();
+        this.node.className.baseVal = value;
+    })(-1);
+    eve.on("snap.util.attr.viewBox", function (value) {
+        var vb;
+        if (is(value, "object") && "x" in value) {
+            vb = [value.x, value.y, value.width, value.height].join(" ");
+        } else if (is(value, "array")) {
+            vb = value.join(" ");
+        } else {
+            vb = value;
+        }
+        $(this.node, {
+            viewBox: vb
+        });
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.transform", function (value) {
+        this.transform(value);
+        eve.stop();
+    })(-1);
+    eve.on("snap.util.attr.r", function (value) {
+        if (this.type == "rect") {
+            eve.stop();
+            $(this.node, {
+                rx: value,
+                ry: value
+            });
+        }
+    })(-1);
+    eve.on("snap.util.attr.textpath", function (value) {
+        eve.stop();
+        if (this.type == "text") {
+            var id, tp, node;
+            if (!value && this.textPath) {
+                tp = this.textPath;
+                while (tp.node.firstChild) {
+                    this.node.appendChild(tp.node.firstChild);
+                }
+                tp.remove();
+                delete this.textPath;
+                return;
+            }
+            if (is(value, "string")) {
+                var defs = getSomeDefs(this),
+                    path = wrap(defs.parentNode).path(value);
+                defs.appendChild(path.node);
+                id = path.id;
+                path.attr({id: id});
+            } else {
+                value = wrap(value);
+                if (value instanceof Element) {
+                    id = value.attr("id");
+                    if (!id) {
+                        id = value.id;
+                        value.attr({id: id});
+                    }
+                }
+            }
+            if (id) {
+                tp = this.textPath;
+                node = this.node;
+                if (tp) {
+                    tp.attr({"xlink:href": "#" + id});
+                } else {
+                    tp = $("textPath", {
+                        "xlink:href": "#" + id
+                    });
+                    while (node.firstChild) {
+                        tp.appendChild(node.firstChild);
+                    }
+                    node.appendChild(tp);
+                    this.textPath = wrap(tp);
+                }
+            }
+        }
+    })(-1);
+    eve.on("snap.util.attr.text", function (value) {
+        if (this.type == "text") {
+            var i = 0,
+                node = this.node,
+                tuner = function (chunk) {
+                    var out = $("tspan");
+                    if (is(chunk, "array")) {
+                        for (var i = 0; i < chunk.length; i++) {
+                            out.appendChild(tuner(chunk[i]));
+                        }
+                    } else {
+                        out.appendChild(glob.doc.createTextNode(chunk));
+                    }
+                    out.normalize && out.normalize();
+                    return out;
+                };
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            var tuned = tuner(value);
+            while (tuned.firstChild) {
+                node.appendChild(tuned.firstChild);
+            }
+        }
+        eve.stop();
+    })(-1);
+    function setFontSize(value) {
+        eve.stop();
+        if (value == +value) {
+            value += "px";
+        }
+        this.node.style.fontSize = value;
+    }
+    eve.on("snap.util.attr.fontSize", setFontSize)(-1);
+    eve.on("snap.util.attr.font-size", setFontSize)(-1);
+
+
+    eve.on("snap.util.getattr.transform", function () {
+        eve.stop();
+        return this.transform();
+    })(-1);
+    eve.on("snap.util.getattr.textpath", function () {
+        eve.stop();
+        return this.textPath;
+    })(-1);
+    // Markers
+    (function () {
+        function getter(end) {
+            return function () {
+                eve.stop();
+                var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end);
+                if (style == "none") {
+                    return style;
+                } else {
+                    return Snap(glob.doc.getElementById(style.match(reURLValue)[1]));
+                }
+            };
+        }
+        function setter(end) {
+            return function (value) {
+                eve.stop();
+                var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1);
+                if (value == "" || !value) {
+                    this.node.style[name] = "none";
+                    return;
+                }
+                if (value.type == "marker") {
+                    var id = value.node.id;
+                    if (!id) {
+                        $(value.node, {id: value.id});
+                    }
+                    this.node.style[name] = URL(id);
+                    return;
+                }
+            };
+        }
+        eve.on("snap.util.getattr.marker-end", getter("end"))(-1);
+        eve.on("snap.util.getattr.markerEnd", getter("end"))(-1);
+        eve.on("snap.util.getattr.marker-start", getter("start"))(-1);
+        eve.on("snap.util.getattr.markerStart", getter("start"))(-1);
+        eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1);
+        eve.on("snap.util.getattr.markerMid", getter("mid"))(-1);
+        eve.on("snap.util.attr.marker-end", setter("end"))(-1);
+        eve.on("snap.util.attr.markerEnd", setter("end"))(-1);
+        eve.on("snap.util.attr.marker-start", setter("start"))(-1);
+        eve.on("snap.util.attr.markerStart", setter("start"))(-1);
+        eve.on("snap.util.attr.marker-mid", setter("mid"))(-1);
+        eve.on("snap.util.attr.markerMid", setter("mid"))(-1);
+    }());
+    eve.on("snap.util.getattr.r", function () {
+        if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
+            eve.stop();
+            return $(this.node, "rx");
+        }
+    })(-1);
+    function textExtract(node) {
+        var out = [];
+        var children = node.childNodes;
+        for (var i = 0, ii = children.length; i < ii; i++) {
+            var chi = children[i];
+            if (chi.nodeType == 3) {
+                out.push(chi.nodeValue);
+            }
+            if (chi.tagName == "tspan") {
+                if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) {
+                    out.push(chi.firstChild.nodeValue);
+                } else {
+                    out.push(textExtract(chi));
+                }
+            }
+        }
+        return out;
+    }
+    eve.on("snap.util.getattr.text", function () {
+        if (this.type == "text" || this.type == "tspan") {
+            eve.stop();
+            var out = textExtract(this.node);
+            return out.length == 1 ? out[0] : out;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.#text", function () {
+        return this.node.textContent;
+    })(-1);
+    eve.on("snap.util.getattr.viewBox", function () {
+        eve.stop();
+        var vb = $(this.node, "viewBox");
+        if (vb) {
+            vb = vb.split(separator);
+            return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.points", function () {
+        var p = $(this.node, "points");
+        eve.stop();
+        if (p) {
+            return p.split(separator);
+        } else {
+            return;
+        }
+    })(-1);
+    eve.on("snap.util.getattr.path", function () {
+        var p = $(this.node, "d");
+        eve.stop();
+        return p;
+    })(-1);
+    eve.on("snap.util.getattr.class", function () {
+        return this.node.className.baseVal;
+    })(-1);
+    function getFontSize() {
+        eve.stop();
+        return this.node.style.fontSize;
+    }
+    eve.on("snap.util.getattr.fontSize", getFontSize)(-1);
+    eve.on("snap.util.getattr.font-size", getFontSize)(-1);
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var rgNotSpace = /\S+/g,
+        rgBadSpace = /[\t\r\n\f]/g,
+        rgTrim = /(^\s+|\s+$)/g,
+        Str = String,
+        elproto = Element.prototype;
+    /*\
+     * Element.addClass
+     [ method ]
+     **
+     * Adds given class name or list of class names to the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.addClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+
+        if (classes.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (!~pos) {
+                    curClasses.push(clazz);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.removeClass
+     [ method ]
+     **
+     * Removes given class name or list of class names from the element.
+     - value (string) class name or space separated list of class names
+     **
+     = (Element) original element.
+    \*/
+    elproto.removeClass = function (value) {
+        var classes = Str(value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        if (curClasses.length) {
+            j = 0;
+            while ((clazz = classes[j++])) {
+                pos = curClasses.indexOf(clazz);
+                if (~pos) {
+                    curClasses.splice(pos, 1);
+                }
+            }
+
+            finalValue = curClasses.join(" ");
+            if (className != finalValue) {
+                elem.className.baseVal = finalValue;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.hasClass
+     [ method ]
+     **
+     * Checks if the element has a given class name in the list of class names applied to it.
+     - value (string) class name
+     **
+     = (boolean) `true` if the element has given class
+    \*/
+    elproto.hasClass = function (value) {
+        var elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [];
+        return !!~curClasses.indexOf(value);
+    };
+    /*\
+     * Element.toggleClass
+     [ method ]
+     **
+     * Add or remove one or more classes from the element, depending on either
+     * the class’s presence or the value of the `flag` argument.
+     - value (string) class name or space separated list of class names
+     - flag (boolean) value to determine whether the class should be added or removed
+     **
+     = (Element) original element.
+    \*/
+    elproto.toggleClass = function (value, flag) {
+        if (flag != null) {
+            if (flag) {
+                return this.addClass(value);
+            } else {
+                return this.removeClass(value);
+            }
+        }
+        var classes = (value || "").match(rgNotSpace) || [],
+            elem = this.node,
+            className = elem.className.baseVal,
+            curClasses = className.match(rgNotSpace) || [],
+            j,
+            pos,
+            clazz,
+            finalValue;
+        j = 0;
+        while ((clazz = classes[j++])) {
+            pos = curClasses.indexOf(clazz);
+            if (~pos) {
+                curClasses.splice(pos, 1);
+            } else {
+                curClasses.push(clazz);
+            }
+        }
+
+        finalValue = curClasses.join(" ");
+        if (className != finalValue) {
+            elem.className.baseVal = finalValue;
+        }
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var operators = {
+            "+": function (x, y) {
+                    return x + y;
+                },
+            "-": function (x, y) {
+                    return x - y;
+                },
+            "/": function (x, y) {
+                    return x / y;
+                },
+            "*": function (x, y) {
+                    return x * y;
+                }
+        },
+        Str = String,
+        reUnit = /[a-z]+$/i,
+        reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    eve.on("snap.util.attr", function (val) {
+        var plus = Str(val).match(reAddon);
+        if (plus) {
+            var evnt = eve.nt(),
+                name = evnt.substring(evnt.lastIndexOf(".") + 1),
+                a = this.attr(name),
+                atr = {};
+            eve.stop();
+            var unit = plus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[plus[1]];
+            if (aUnit && aUnit == unit) {
+                val = op(parseFloat(a), +plus[2]);
+            } else {
+                a = this.asPX(name);
+                val = op(this.asPX(name), this.asPX(name, plus[2] + unit));
+            }
+            if (isNaN(a) || isNaN(val)) {
+                return;
+            }
+            atr[name] = val;
+            this.attr(atr);
+        }
+    })(-10);
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this,
+            bplus = Str(b).match(reAddon);
+        if (bplus) {
+            eve.stop();
+            var unit = bplus[3] || "",
+                aUnit = a.match(reUnit),
+                op = operators[bplus[1]];
+            if (aUnit && aUnit == unit) {
+                return {
+                    from: parseFloat(a),
+                    to: op(parseFloat(a), +bplus[2]),
+                    f: getUnit(aUnit)
+                };
+            } else {
+                a = this.asPX(name);
+                return {
+                    from: a,
+                    to: op(a, this.asPX(name, bplus[2] + unit)),
+                    f: getNumber
+                };
+            }
+        }
+    })(-10);
+});
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var proto = Paper.prototype,
+        is = Snap.is;
+    /*\
+     * Paper.rect
+     [ method ]
+     *
+     * Draws a rectangle
+     **
+     - x (number) x coordinate of the top left corner
+     - y (number) y coordinate of the top left corner
+     - width (number) width
+     - height (number) height
+     - rx (number) #optional horizontal radius for rounded corners, default is 0
+     - ry (number) #optional vertical radius for rounded corners, default is rx or 0
+     = (object) the `rect` element
+     **
+     > Usage
+     | // regular rectangle
+     | var c = paper.rect(10, 10, 50, 50);
+     | // rectangle with rounded corners
+     | var c = paper.rect(40, 40, 50, 50, 10);
+    \*/
+    proto.rect = function (x, y, w, h, rx, ry) {
+        var attr;
+        if (ry == null) {
+            ry = rx;
+        }
+        if (is(x, "object") && x == "[object Object]") {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                width: w,
+                height: h
+            };
+            if (rx != null) {
+                attr.rx = rx;
+                attr.ry = ry;
+            }
+        }
+        return this.el("rect", attr);
+    };
+    /*\
+     * Paper.circle
+     [ method ]
+     **
+     * Draws a circle
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - r (number) radius
+     = (object) the `circle` element
+     **
+     > Usage
+     | var c = paper.circle(50, 50, 40);
+    \*/
+    proto.circle = function (cx, cy, r) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr = {
+                cx: cx,
+                cy: cy,
+                r: r
+            };
+        }
+        return this.el("circle", attr);
+    };
+
+    var preload = (function () {
+        function onerror() {
+            this.parentNode.removeChild(this);
+        }
+        return function (src, f) {
+            var img = glob.doc.createElement("img"),
+                body = glob.doc.body;
+            img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+            img.onload = function () {
+                f.call(img);
+                img.onload = img.onerror = null;
+                body.removeChild(img);
+            };
+            img.onerror = onerror;
+            body.appendChild(img);
+            img.src = src;
+        };
+    }());
+
+    /*\
+     * Paper.image
+     [ method ]
+     **
+     * Places an image on the surface
+     **
+     - src (string) URI of the source image
+     - x (number) x offset position
+     - y (number) y offset position
+     - width (number) width of the image
+     - height (number) height of the image
+     = (object) the `image` element
+     * or
+     = (object) Snap element object with type `image`
+     **
+     > Usage
+     | var c = paper.image("apple.png", 10, 10, 80, 80);
+    \*/
+    proto.image = function (src, x, y, width, height) {
+        var el = this.el("image");
+        if (is(src, "object") && "src" in src) {
+            el.attr(src);
+        } else if (src != null) {
+            var set = {
+                "xlink:href": src,
+                preserveAspectRatio: "none"
+            };
+            if (x != null && y != null) {
+                set.x = x;
+                set.y = y;
+            }
+            if (width != null && height != null) {
+                set.width = width;
+                set.height = height;
+            } else {
+                preload(src, function () {
+                    Snap._.$(el.node, {
+                        width: this.offsetWidth,
+                        height: this.offsetHeight
+                    });
+                });
+            }
+            Snap._.$(el.node, set);
+        }
+        return el;
+    };
+    /*\
+     * Paper.ellipse
+     [ method ]
+     **
+     * Draws an ellipse
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - rx (number) horizontal radius
+     - ry (number) vertical radius
+     = (object) the `ellipse` element
+     **
+     > Usage
+     | var c = paper.ellipse(50, 50, 40, 20);
+    \*/
+    proto.ellipse = function (cx, cy, rx, ry) {
+        var attr;
+        if (is(cx, "object") && cx == "[object Object]") {
+            attr = cx;
+        } else if (cx != null) {
+            attr ={
+                cx: cx,
+                cy: cy,
+                rx: rx,
+                ry: ry
+            };
+        }
+        return this.el("ellipse", attr);
+    };
+    // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier.
+    /*\
+     * Paper.path
+     [ method ]
+     **
+     * Creates a `<path>` element using the given string as the path's definition
+     - pathString (string) #optional path string in SVG format
+     * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:
+     | "M10,20L30,40"
+     * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.
+     *
+     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
+     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
+     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
+     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
+     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
+     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
+     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
+     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
+     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
+     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
+     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
+     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
+     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
+     * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier.
+     * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point.
+     > Usage
+     | var c = paper.path("M10 10L90 90");
+     | // draw a diagonal line:
+     | // move to 10,10, line to 90,90
+    \*/
+    proto.path = function (d) {
+        var attr;
+        if (is(d, "object") && !is(d, "array")) {
+            attr = d;
+        } else if (d) {
+            attr = {d: d};
+        }
+        return this.el("path", attr);
+    };
+    /*\
+     * Paper.g
+     [ method ]
+     **
+     * Creates a group element
+     **
+     - varargs (…) #optional elements to nest within the group
+     = (object) the `g` element
+     **
+     > Usage
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g(c2, c1); // note that the order of elements is different
+     * or
+     | var c1 = paper.circle(),
+     |     c2 = paper.rect(),
+     |     g = paper.g();
+     | g.add(c2, c1);
+    \*/
+    /*\
+     * Paper.group
+     [ method ]
+     **
+     * See @Paper.g
+    \*/
+    proto.group = proto.g = function (first) {
+        var attr,
+            el = this.el("g");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.svg
+     [ method ]
+     **
+     * Creates a nested SVG element.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `svg` element
+     **
+    \*/
+    proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) {
+        var attrs = {};
+        if (is(x, "object") && y == null) {
+            attrs = x;
+        } else {
+            if (x != null) {
+                attrs.x = x;
+            }
+            if (y != null) {
+                attrs.y = y;
+            }
+            if (width != null) {
+                attrs.width = width;
+            }
+            if (height != null) {
+                attrs.height = height;
+            }
+            if (vbx != null && vby != null && vbw != null && vbh != null) {
+                attrs.viewBox = [vbx, vby, vbw, vbh];
+            }
+        }
+        return this.el("svg", attrs);
+    };
+    /*\
+     * Paper.mask
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a mask.
+     **
+     = (object) the `mask` element
+     **
+    \*/
+    proto.mask = function (first) {
+        var attr,
+            el = this.el("mask");
+        if (arguments.length == 1 && first && !first.type) {
+            el.attr(first);
+        } else if (arguments.length) {
+            el.add(Array.prototype.slice.call(arguments, 0));
+        }
+        return el;
+    };
+    /*\
+     * Paper.ptrn
+     [ method ]
+     **
+     * Equivalent in behaviour to @Paper.g, except it’s a pattern.
+     - x (number) @optional X of the element
+     - y (number) @optional Y of the element
+     - width (number) @optional width of the element
+     - height (number) @optional height of the element
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     **
+     = (object) the `pattern` element
+     **
+    \*/
+    proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) {
+        if (is(x, "object")) {
+            var attr = x;
+        } else {
+            attr = {patternUnits: "userSpaceOnUse"};
+            if (x) {
+                attr.x = x;
+            }
+            if (y) {
+                attr.y = y;
+            }
+            if (width != null) {
+                attr.width = width;
+            }
+            if (height != null) {
+                attr.height = height;
+            }
+            if (vx != null && vy != null && vw != null && vh != null) {
+                attr.viewBox = [vx, vy, vw, vh];
+            } else {
+                attr.viewBox = [x || 0, y || 0, width || 0, height || 0];
+            }
+        }
+        return this.el("pattern", attr);
+    };
+    /*\
+     * Paper.use
+     [ method ]
+     **
+     * Creates a <use> element.
+     - id (string) @optional id of element to link
+     * or
+     - id (Element) @optional element to link
+     **
+     = (object) the `use` element
+     **
+    \*/
+    proto.use = function (id) {
+        if (id != null) {
+            if (id instanceof Element) {
+                if (!id.attr("id")) {
+                    id.attr({id: Snap._.id(id)});
+                }
+                id = id.attr("id");
+            }
+            if (String(id).charAt() == "#") {
+                id = id.substring(1);
+            }
+            return this.el("use", {"xlink:href": "#" + id});
+        } else {
+            return Element.prototype.use.call(this);
+        }
+    };
+    /*\
+     * Paper.symbol
+     [ method ]
+     **
+     * Creates a <symbol> element.
+     - vbx (number) @optional viewbox X
+     - vby (number) @optional viewbox Y
+     - vbw (number) @optional viewbox width
+     - vbh (number) @optional viewbox height
+     = (object) the `symbol` element
+     **
+    \*/
+    proto.symbol = function (vx, vy, vw, vh) {
+        var attr = {};
+        if (vx != null && vy != null && vw != null && vh != null) {
+            attr.viewBox = [vx, vy, vw, vh];
+        }
+
+        return this.el("symbol", attr);
+    };
+    /*\
+     * Paper.text
+     [ method ]
+     **
+     * Draws a text string
+     **
+     - x (number) x coordinate position
+     - y (number) y coordinate position
+     - text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
+     = (object) the `text` element
+     **
+     > Usage
+     | var t1 = paper.text(50, 50, "Snap");
+     | var t2 = paper.text(50, 50, ["S","n","a","p"]);
+     | // Text path usage
+     | t1.attr({textpath: "M10,10L100,100"});
+     | // or
+     | var pth = paper.path("M10,10L100,100");
+     | t1.attr({textpath: pth});
+    \*/
+    proto.text = function (x, y, text) {
+        var attr = {};
+        if (is(x, "object")) {
+            attr = x;
+        } else if (x != null) {
+            attr = {
+                x: x,
+                y: y,
+                text: text || ""
+            };
+        }
+        return this.el("text", attr);
+    };
+    /*\
+     * Paper.line
+     [ method ]
+     **
+     * Draws a line
+     **
+     - x1 (number) x coordinate position of the start
+     - y1 (number) y coordinate position of the start
+     - x2 (number) x coordinate position of the end
+     - y2 (number) y coordinate position of the end
+     = (object) the `line` element
+     **
+     > Usage
+     | var t1 = paper.line(50, 50, 100, 100);
+    \*/
+    proto.line = function (x1, y1, x2, y2) {
+        var attr = {};
+        if (is(x1, "object")) {
+            attr = x1;
+        } else if (x1 != null) {
+            attr = {
+                x1: x1,
+                x2: x2,
+                y1: y1,
+                y2: y2
+            };
+        }
+        return this.el("line", attr);
+    };
+    /*\
+     * Paper.polyline
+     [ method ]
+     **
+     * Draws a polyline
+     **
+     - points (array) array of points
+     * or
+     - varargs (…) points
+     = (object) the `polyline` element
+     **
+     > Usage
+     | var p1 = paper.polyline([10, 10, 100, 100]);
+     | var p2 = paper.polyline(10, 10, 100, 100);
+    \*/
+    proto.polyline = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polyline", attr);
+    };
+    /*\
+     * Paper.polygon
+     [ method ]
+     **
+     * Draws a polygon. See @Paper.polyline
+    \*/
+    proto.polygon = function (points) {
+        if (arguments.length > 1) {
+            points = Array.prototype.slice.call(arguments, 0);
+        }
+        var attr = {};
+        if (is(points, "object") && !is(points, "array")) {
+            attr = points;
+        } else if (points != null) {
+            attr = {points: points};
+        }
+        return this.el("polygon", attr);
+    };
+    // gradients
+    (function () {
+        var $ = Snap._.$;
+        // gradients' helpers
+        function Gstops() {
+            return this.selectAll("stop");
+        }
+        function GaddStop(color, offset) {
+            var stop = $("stop"),
+                attr = {
+                    offset: +offset + "%"
+                };
+            color = Snap.color(color);
+            attr["stop-color"] = color.hex;
+            if (color.opacity < 1) {
+                attr["stop-opacity"] = color.opacity;
+            }
+            $(stop, attr);
+            this.node.appendChild(stop);
+            return this;
+        }
+        function GgetBBox() {
+            if (this.type == "linearGradient") {
+                var x1 = $(this.node, "x1") || 0,
+                    x2 = $(this.node, "x2") || 1,
+                    y1 = $(this.node, "y1") || 0,
+                    y2 = $(this.node, "y2") || 0;
+                return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1));
+            } else {
+                var cx = this.node.cx || .5,
+                    cy = this.node.cy || .5,
+                    r = this.node.r || 0;
+                return Snap._.box(cx - r, cy - r, r * 2, r * 2);
+            }
+        }
+        function gradient(defs, str) {
+            var grad = eve("snap.util.grad.parse", null, str).firstDefined(),
+                el;
+            if (!grad) {
+                return null;
+            }
+            grad.params.unshift(defs);
+            if (grad.type.toLowerCase() == "l") {
+                el = gradientLinear.apply(0, grad.params);
+            } else {
+                el = gradientRadial.apply(0, grad.params);
+            }
+            if (grad.type != grad.type.toLowerCase()) {
+                $(el.node, {
+                    gradientUnits: "userSpaceOnUse"
+                });
+            }
+            var stops = grad.stops,
+                len = stops.length,
+                start = 0,
+                j = 0;
+            function seed(i, end) {
+                var step = (end - start) / (i - j);
+                for (var k = j; k < i; k++) {
+                    stops[k].offset = +(+start + step * (k - j)).toFixed(2);
+                }
+                j = i;
+                start = end;
+            }
+            len--;
+            for (var i = 0; i < len; i++) if ("offset" in stops[i]) {
+                seed(i, stops[i].offset);
+            }
+            stops[len].offset = stops[len].offset || 100;
+            seed(len, stops[len].offset);
+            for (i = 0; i <= len; i++) {
+                var stop = stops[i];
+                el.addStop(stop.color, stop.offset);
+            }
+            return el;
+        }
+        function gradientLinear(defs, x1, y1, x2, y2) {
+            var el = Snap._.make("linearGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (x1 != null) {
+                $(el.node, {
+                    x1: x1,
+                    y1: y1,
+                    x2: x2,
+                    y2: y2
+                });
+            }
+            return el;
+        }
+        function gradientRadial(defs, cx, cy, r, fx, fy) {
+            var el = Snap._.make("radialGradient", defs);
+            el.stops = Gstops;
+            el.addStop = GaddStop;
+            el.getBBox = GgetBBox;
+            if (cx != null) {
+                $(el.node, {
+                    cx: cx,
+                    cy: cy,
+                    r: r
+                });
+            }
+            if (fx != null && fy != null) {
+                $(el.node, {
+                    fx: fx,
+                    fy: fy
+                });
+            }
+            return el;
+        }
+        /*\
+         * Paper.gradient
+         [ method ]
+         **
+         * Creates a gradient element
+         **
+         - gradient (string) gradient descriptor
+         > Gradient Descriptor
+         * The gradient descriptor is an expression formatted as
+         * follows: `<type>(<coords>)<colors>`.  The `<type>` can be
+         * either linear or radial.  The uppercase `L` or `R` letters
+         * indicate absolute coordinates offset from the SVG surface.
+         * Lowercase `l` or `r` letters indicate coordinates
+         * calculated relative to the element to which the gradient is
+         * applied.  Coordinates specify a linear gradient vector as
+         * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`,
+         * `r` and optional `fx`, `fy` specifying a focal point away
+         * from the center of the circle. Specify `<colors>` as a list
+         * of dash-separated CSS color values.  Each color may be
+         * followed by a custom offset value, separated with a colon
+         * character.
+         > Examples
+         * Linear gradient, relative from top-left corner to bottom-right
+         * corner, from black through red to white:
+         | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
+         * Linear gradient, absolute from (0, 0) to (100, 100), from black
+         * through red at 25% to white:
+         | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");
+         * Radial gradient, relative from the center of the element with radius
+         * half the width, from black to white:
+         | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
+         * To apply the gradient:
+         | paper.circle(50, 50, 40).attr({
+         |     fill: g
+         | });
+         = (object) the `gradient` element
+        \*/
+        proto.gradient = function (str) {
+            return gradient(this.defs, str);
+        };
+        proto.gradientLinear = function (x1, y1, x2, y2) {
+            return gradientLinear(this.defs, x1, y1, x2, y2);
+        };
+        proto.gradientRadial = function (cx, cy, r, fx, fy) {
+            return gradientRadial(this.defs, cx, cy, r, fx, fy);
+        };
+        /*\
+         * Paper.toString
+         [ method ]
+         **
+         * Returns SVG code for the @Paper
+         = (string) SVG code for the @Paper
+        \*/
+        proto.toString = function () {
+            var doc = this.node.ownerDocument,
+                f = doc.createDocumentFragment(),
+                d = doc.createElement("div"),
+                svg = this.node.cloneNode(true),
+                res;
+            f.appendChild(d);
+            d.appendChild(svg);
+            Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"});
+            res = d.innerHTML;
+            f.removeChild(f.firstChild);
+            return res;
+        };
+        /*\
+         * Paper.toDataURL
+         [ method ]
+         **
+         * Returns SVG code for the @Paper as Data URI string.
+         = (string) Data URI string
+        \*/
+        proto.toDataURL = function () {
+            if (window && window.btoa) {
+                return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this)));
+            }
+        };
+        /*\
+         * Paper.clear
+         [ method ]
+         **
+         * Removes all child nodes of the paper, except <defs>.
+        \*/
+        proto.clear = function () {
+            var node = this.node.firstChild,
+                next;
+            while (node) {
+                next = node.nextSibling;
+                if (node.tagName != "defs") {
+                    node.parentNode.removeChild(node);
+                } else {
+                    proto.clear.call({node: node});
+                }
+                node = next;
+            }
+        };
+    }());
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        is = Snap.is,
+        clone = Snap._.clone,
+        has = "hasOwnProperty",
+        p2s = /,?([a-z]),?/gi,
+        toFloat = parseFloat,
+        math = Math,
+        PI = math.PI,
+        mmin = math.min,
+        mmax = math.max,
+        pow = math.pow,
+        abs = math.abs;
+    function paths(ps) {
+        var p = paths.ps = paths.ps || {};
+        if (p[ps]) {
+            p[ps].sleep = 100;
+        } else {
+            p[ps] = {
+                sleep: 100
+            };
+        }
+        setTimeout(function () {
+            for (var key in p) if (p[has](key) && key != ps) {
+                p[key].sleep--;
+                !p[key].sleep && delete p[key];
+            }
+        });
+        return p[ps];
+    }
+    function box(x, y, width, height) {
+        if (x == null) {
+            x = y = width = height = 0;
+        }
+        if (y == null) {
+            y = x.y;
+            width = x.width;
+            height = x.height;
+            x = x.x;
+        }
+        return {
+            x: x,
+            y: y,
+            width: width,
+            w: width,
+            height: height,
+            h: height,
+            x2: x + width,
+            y2: y + height,
+            cx: x + width / 2,
+            cy: y + height / 2,
+            r1: math.min(width, height) / 2,
+            r2: math.max(width, height) / 2,
+            r0: math.sqrt(width * width + height * height) / 2,
+            path: rectPath(x, y, width, height),
+            vb: [x, y, width, height].join(" ")
+        };
+    }
+    function toString() {
+        return this.join(",").replace(p2s, "$1");
+    }
+    function pathClone(pathArray) {
+        var res = clone(pathArray);
+        res.toString = toString;
+        return res;
+    }
+    function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        if (length == null) {
+            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+        } else {
+            return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
+                getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+        }
+    }
+    function getLengthFactory(istotal, subpath) {
+        function O(val) {
+            return +(+val).toFixed(3);
+        }
+        return Snap._.cacher(function (path, length, onlystart) {
+            if (path instanceof Element) {
+                path = path.attr("d");
+            }
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            sp += [
+                                "C" + O(point.start.x),
+                                O(point.start.y),
+                                O(point.m.x),
+                                O(point.m.y),
+                                O(point.x),
+                                O(point.y)
+                            ];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = [
+                                "M" + O(point.x),
+                                O(point.y) + "C" + O(point.n.x),
+                                O(point.n.y),
+                                O(point.end.x),
+                                O(point.end.y),
+                                O(p[5]),
+                                O(p[6])
+                            ].join();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            return point;
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p.shift() + p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+            return point;
+        }, null, Snap._.clone);
+    }
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            t13 = pow(t1, 3),
+            t12 = pow(t1, 2),
+            t2 = t * t,
+            t3 = t2 * t,
+            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+            ax = t1 * p1x + t * c1x,
+            ay = t1 * p1y + t * c1y,
+            cx = t1 * c2x + t * p2x,
+            cy = t1 * c2y + t * p2y,
+            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+        // (mx > nx || my < ny) && (alpha += 180);
+        return {
+            x: x,
+            y: y,
+            m: {x: mx, y: my},
+            n: {x: nx, y: ny},
+            start: {x: ax, y: ay},
+            end: {x: cx, y: cy},
+            alpha: alpha
+        };
+    }
+    function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        if (!Snap.is(p1x, "array")) {
+            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+        }
+        var bbox = curveDim.apply(null, p1x);
+        return box(
+            bbox.min.x,
+            bbox.min.y,
+            bbox.max.x - bbox.min.x,
+            bbox.max.y - bbox.min.y
+        );
+    }
+    function isPointInsideBBox(bbox, x, y) {
+        return  x >= bbox.x &&
+                x <= bbox.x + bbox.width &&
+                y >= bbox.y &&
+                y <= bbox.y + bbox.height;
+    }
+    function isBBoxIntersect(bbox1, bbox2) {
+        bbox1 = box(bbox1);
+        bbox2 = box(bbox2);
+        return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
+            || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
+            || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y)
+            || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2)
+            || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2)
+            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
+                || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
+                || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+    }
+    function base3(t, p1, p2, p3, p4) {
+        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+        return t * t2 - 3 * p1 + 3 * p2;
+    }
+    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+        if (z == null) {
+            z = 1;
+        }
+        z = z > 1 ? 1 : z < 0 ? 0 : z;
+        var z2 = z / 2,
+            n = 12,
+            Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
+            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
+            sum = 0;
+        for (var i = 0; i < n; i++) {
+            var ct = z2 * Tvalues[i] + z2,
+                xbase = base3(ct, x1, x2, x3, x4),
+                ybase = base3(ct, y1, y2, y3, y4),
+                comb = xbase * xbase + ybase * ybase;
+            sum += Cvalues[i] * math.sqrt(comb);
+        }
+        return z2 * sum;
+    }
+    function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+            return;
+        }
+        var t = 1,
+            step = t / 2,
+            t2 = t - step,
+            l,
+            e = .01;
+        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        while (abs(l - ll) > e) {
+            step /= 2;
+            t2 += (l < ll ? 1 : -1) * step;
+            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        }
+        return t2;
+    }
+    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+        if (
+            mmax(x1, x2) < mmin(x3, x4) ||
+            mmin(x1, x2) > mmax(x3, x4) ||
+            mmax(y1, y2) < mmin(y3, y4) ||
+            mmin(y1, y2) > mmax(y3, y4)
+        ) {
+            return;
+        }
+        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+        if (!denominator) {
+            return;
+        }
+        var px = nx / denominator,
+            py = ny / denominator,
+            px2 = +px.toFixed(2),
+            py2 = +py.toFixed(2);
+        if (
+            px2 < +mmin(x1, x2).toFixed(2) ||
+            px2 > +mmax(x1, x2).toFixed(2) ||
+            px2 < +mmin(x3, x4).toFixed(2) ||
+            px2 > +mmax(x3, x4).toFixed(2) ||
+            py2 < +mmin(y1, y2).toFixed(2) ||
+            py2 > +mmax(y1, y2).toFixed(2) ||
+            py2 < +mmin(y3, y4).toFixed(2) ||
+            py2 > +mmax(y3, y4).toFixed(2)
+        ) {
+            return;
+        }
+        return {x: px, y: py};
+    }
+    function inter(bez1, bez2) {
+        return interHelper(bez1, bez2);
+    }
+    function interCount(bez1, bez2) {
+        return interHelper(bez1, bez2, 1);
+    }
+    function interHelper(bez1, bez2, justCount) {
+        var bbox1 = bezierBBox(bez1),
+            bbox2 = bezierBBox(bez2);
+        if (!isBBoxIntersect(bbox1, bbox2)) {
+            return justCount ? 0 : [];
+        }
+        var l1 = bezlen.apply(0, bez1),
+            l2 = bezlen.apply(0, bez2),
+            n1 = ~~(l1 / 8),
+            n2 = ~~(l2 / 8),
+            dots1 = [],
+            dots2 = [],
+            xy = {},
+            res = justCount ? 0 : [];
+        for (var i = 0; i < n1 + 1; i++) {
+            var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
+            dots1.push({x: p.x, y: p.y, t: i / n1});
+        }
+        for (i = 0; i < n2 + 1; i++) {
+            p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
+            dots2.push({x: p.x, y: p.y, t: i / n2});
+        }
+        for (i = 0; i < n1; i++) {
+            for (var j = 0; j < n2; j++) {
+                var di = dots1[i],
+                    di1 = dots1[i + 1],
+                    dj = dots2[j],
+                    dj1 = dots2[j + 1],
+                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+                if (is) {
+                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+                        continue;
+                    }
+                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
+                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+                        if (justCount) {
+                            res++;
+                        } else {
+                            res.push({
+                                x: is.x,
+                                y: is.y,
+                                t1: t1,
+                                t2: t2
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function pathIntersection(path1, path2) {
+        return interPathHelper(path1, path2);
+    }
+    function pathIntersectionNumber(path1, path2) {
+        return interPathHelper(path1, path2, 1);
+    }
+    function interPathHelper(path1, path2, justCount) {
+        path1 = path2curve(path1);
+        path2 = path2curve(path2);
+        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+            res = justCount ? 0 : [];
+        for (var i = 0, ii = path1.length; i < ii; i++) {
+            var pi = path1[i];
+            if (pi[0] == "M") {
+                x1 = x1m = pi[1];
+                y1 = y1m = pi[2];
+            } else {
+                if (pi[0] == "C") {
+                    bez1 = [x1, y1].concat(pi.slice(1));
+                    x1 = bez1[6];
+                    y1 = bez1[7];
+                } else {
+                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+                    x1 = x1m;
+                    y1 = y1m;
+                }
+                for (var j = 0, jj = path2.length; j < jj; j++) {
+                    var pj = path2[j];
+                    if (pj[0] == "M") {
+                        x2 = x2m = pj[1];
+                        y2 = y2m = pj[2];
+                    } else {
+                        if (pj[0] == "C") {
+                            bez2 = [x2, y2].concat(pj.slice(1));
+                            x2 = bez2[6];
+                            y2 = bez2[7];
+                        } else {
+                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+                            x2 = x2m;
+                            y2 = y2m;
+                        }
+                        var intr = interHelper(bez1, bez2, justCount);
+                        if (justCount) {
+                            res += intr;
+                        } else {
+                            for (var k = 0, kk = intr.length; k < kk; k++) {
+                                intr[k].segment1 = i;
+                                intr[k].segment2 = j;
+                                intr[k].bez1 = bez1;
+                                intr[k].bez2 = bez2;
+                            }
+                            res = res.concat(intr);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    function isPointInsidePath(path, x, y) {
+        var bbox = pathBBox(path);
+        return isPointInsideBBox(bbox, x, y) &&
+               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+    }
+    function pathBBox(path) {
+        var pth = paths(path);
+        if (pth.bbox) {
+            return clone(pth.bbox);
+        }
+        if (!path) {
+            return box();
+        }
+        path = path2curve(path);
+        var x = 0,
+            y = 0,
+            X = [],
+            Y = [],
+            p;
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            p = path[i];
+            if (p[0] == "M") {
+                x = p[1];
+                y = p[2];
+                X.push(x);
+                Y.push(y);
+            } else {
+                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                X = X.concat(dim.min.x, dim.max.x);
+                Y = Y.concat(dim.min.y, dim.max.y);
+                x = p[5];
+                y = p[6];
+            }
+        }
+        var xmin = mmin.apply(0, X),
+            ymin = mmin.apply(0, Y),
+            xmax = mmax.apply(0, X),
+            ymax = mmax.apply(0, Y),
+            bb = box(xmin, ymin, xmax - xmin, ymax - ymin);
+        pth.bbox = clone(bb);
+        return bb;
+    }
+    function rectPath(x, y, w, h, r) {
+        if (r) {
+            return [
+                ["M", +x + (+r), y],
+                ["l", w - r * 2, 0],
+                ["a", r, r, 0, 0, 1, r, r],
+                ["l", 0, h - r * 2],
+                ["a", r, r, 0, 0, 1, -r, r],
+                ["l", r * 2 - w, 0],
+                ["a", r, r, 0, 0, 1, -r, -r],
+                ["l", 0, r * 2 - h],
+                ["a", r, r, 0, 0, 1, r, -r],
+                ["z"]
+            ];
+        }
+        var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+        res.toString = toString;
+        return res;
+    }
+    function ellipsePath(x, y, rx, ry, a) {
+        if (a == null && ry == null) {
+            ry = rx;
+        }
+        x = +x;
+        y = +y;
+        rx = +rx;
+        ry = +ry;
+        if (a != null) {
+            var rad = Math.PI / 180,
+                x1 = x + rx * Math.cos(-ry * rad),
+                x2 = x + rx * Math.cos(-a * rad),
+                y1 = y + rx * Math.sin(-ry * rad),
+                y2 = y + rx * Math.sin(-a * rad),
+                res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]];
+        } else {
+            res = [
+                ["M", x, y],
+                ["m", 0, -ry],
+                ["a", rx, ry, 0, 1, 1, 0, 2 * ry],
+                ["a", rx, ry, 0, 1, 1, 0, -2 * ry],
+                ["z"]
+            ];
+        }
+        res.toString = toString;
+        return res;
+    }
+    var unit2px = Snap._unit2px,
+        getPath = {
+        path: function (el) {
+            return el.attr("path");
+        },
+        circle: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx, attr.cy, attr.r);
+        },
+        ellipse: function (el) {
+            var attr = unit2px(el);
+            return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry);
+        },
+        rect: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry);
+        },
+        image: function (el) {
+            var attr = unit2px(el);
+            return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height);
+        },
+        line: function (el) {
+            return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")];
+        },
+        polyline: function (el) {
+            return "M" + el.attr("points");
+        },
+        polygon: function (el) {
+            return "M" + el.attr("points") + "z";
+        },
+        deflt: function (el) {
+            var bbox = el.node.getBBox();
+            return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+        }
+    };
+    function pathToRelative(pathArray) {
+        var pth = paths(pathArray),
+            lowerCase = String.prototype.toLowerCase;
+        if (pth.rel) {
+            return pathClone(pth.rel);
+        }
+        if (!Snap.is(pathArray, "array") || !Snap.is(pathArray && pathArray[0], "array")) {
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0;
+        if (pathArray[0][0] == "M") {
+            x = pathArray[0][1];
+            y = pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res.push(["M", x, y]);
+        }
+        for (var i = start, ii = pathArray.length; i < ii; i++) {
+            var r = res[i] = [],
+                pa = pathArray[i];
+            if (pa[0] != lowerCase.call(pa[0])) {
+                r[0] = lowerCase.call(pa[0]);
+                switch (r[0]) {
+                    case "a":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +(pa[6] - x).toFixed(3);
+                        r[7] = +(pa[7] - y).toFixed(3);
+                        break;
+                    case "v":
+                        r[1] = +(pa[1] - y).toFixed(3);
+                        break;
+                    case "m":
+                        mx = pa[1];
+                        my = pa[2];
+                    default:
+                        for (var j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+                        }
+                }
+            } else {
+                r = res[i] = [];
+                if (pa[0] == "m") {
+                    mx = pa[1] + x;
+                    my = pa[2] + y;
+                }
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    res[i][k] = pa[k];
+                }
+            }
+            var len = res[i].length;
+            switch (res[i][0]) {
+                case "z":
+                    x = mx;
+                    y = my;
+                    break;
+                case "h":
+                    x += +res[i][len - 1];
+                    break;
+                case "v":
+                    y += +res[i][len - 1];
+                    break;
+                default:
+                    x += +res[i][len - 2];
+                    y += +res[i][len - 1];
+            }
+        }
+        res.toString = toString;
+        pth.rel = pathClone(res);
+        return res;
+    }
+    function pathToAbsolute(pathArray) {
+        var pth = paths(pathArray);
+        if (pth.abs) {
+            return pathClone(pth.abs);
+        }
+        if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
+            pathArray = Snap.parsePathString(pathArray);
+        }
+        if (!pathArray || !pathArray.length) {
+            return [["M", 0, 0]];
+        }
+        var res = [],
+            x = 0,
+            y = 0,
+            mx = 0,
+            my = 0,
+            start = 0,
+            pa0;
+        if (pathArray[0][0] == "M") {
+            x = +pathArray[0][1];
+            y = +pathArray[0][2];
+            mx = x;
+            my = y;
+            start++;
+            res[0] = ["M", x, y];
+        }
+        var crz = pathArray.length == 3 &&
+            pathArray[0][0] == "M" &&
+            pathArray[1][0].toUpperCase() == "R" &&
+            pathArray[2][0].toUpperCase() == "Z";
+        for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+            res.push(r = []);
+            pa = pathArray[i];
+            pa0 = pa[0];
+            if (pa0 != pa0.toUpperCase()) {
+                r[0] = pa0.toUpperCase();
+                switch (r[0]) {
+                    case "A":
+                        r[1] = pa[1];
+                        r[2] = pa[2];
+                        r[3] = pa[3];
+                        r[4] = pa[4];
+                        r[5] = pa[5];
+                        r[6] = +pa[6] + x;
+                        r[7] = +pa[7] + y;
+                        break;
+                    case "V":
+                        r[1] = +pa[1] + y;
+                        break;
+                    case "H":
+                        r[1] = +pa[1] + x;
+                        break;
+                    case "R":
+                        var dots = [x, y].concat(pa.slice(1));
+                        for (var j = 2, jj = dots.length; j < jj; j++) {
+                            dots[j] = +dots[j] + x;
+                            dots[++j] = +dots[j] + y;
+                        }
+                        res.pop();
+                        res = res.concat(catmullRom2bezier(dots, crz));
+                        break;
+                    case "O":
+                        res.pop();
+                        dots = ellipsePath(x, y, pa[1], pa[2]);
+                        dots.push(dots[0]);
+                        res = res.concat(dots);
+                        break;
+                    case "U":
+                        res.pop();
+                        res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                        r = ["U"].concat(res[res.length - 1].slice(-2));
+                        break;
+                    case "M":
+                        mx = +pa[1] + x;
+                        my = +pa[2] + y;
+                    default:
+                        for (j = 1, jj = pa.length; j < jj; j++) {
+                            r[j] = +pa[j] + ((j % 2) ? x : y);
+                        }
+                }
+            } else if (pa0 == "R") {
+                dots = [x, y].concat(pa.slice(1));
+                res.pop();
+                res = res.concat(catmullRom2bezier(dots, crz));
+                r = ["R"].concat(pa.slice(-2));
+            } else if (pa0 == "O") {
+                res.pop();
+                dots = ellipsePath(x, y, pa[1], pa[2]);
+                dots.push(dots[0]);
+                res = res.concat(dots);
+            } else if (pa0 == "U") {
+                res.pop();
+                res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
+                r = ["U"].concat(res[res.length - 1].slice(-2));
+            } else {
+                for (var k = 0, kk = pa.length; k < kk; k++) {
+                    r[k] = pa[k];
+                }
+            }
+            pa0 = pa0.toUpperCase();
+            if (pa0 != "O") {
+                switch (r[0]) {
+                    case "Z":
+                        x = +mx;
+                        y = +my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
+                    default:
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
+                }
+            }
+        }
+        res.toString = toString;
+        pth.abs = pathClone(res);
+        return res;
+    }
+    function l2c(x1, y1, x2, y2) {
+        return [x1, y1, x2, y2, x2, y2];
+    }
+    function q2c(x1, y1, ax, ay, x2, y2) {
+        var _13 = 1 / 3,
+            _23 = 2 / 3;
+        return [
+                _13 * x1 + _23 * ax,
+                _13 * y1 + _23 * ay,
+                _13 * x2 + _23 * ax,
+                _13 * y2 + _23 * ay,
+                x2,
+                y2
+            ];
+    }
+    function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+        // for more information of where this math came from visit:
+        // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+        var _120 = PI * 120 / 180,
+            rad = PI / 180 * (+angle || 0),
+            res = [],
+            xy,
+            rotate = Snap._.cacher(function (x, y, rad) {
+                var X = x * math.cos(rad) - y * math.sin(rad),
+                    Y = x * math.sin(rad) + y * math.cos(rad);
+                return {x: X, y: Y};
+            });
+        if (!recursive) {
+            xy = rotate(x1, y1, -rad);
+            x1 = xy.x;
+            y1 = xy.y;
+            xy = rotate(x2, y2, -rad);
+            x2 = xy.x;
+            y2 = xy.y;
+            var cos = math.cos(PI / 180 * angle),
+                sin = math.sin(PI / 180 * angle),
+                x = (x1 - x2) / 2,
+                y = (y1 - y2) / 2;
+            var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+            if (h > 1) {
+                h = math.sqrt(h);
+                rx = h * rx;
+                ry = h * ry;
+            }
+            var rx2 = rx * rx,
+                ry2 = ry * ry,
+                k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                    math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                cx = k * rx * y / ry + (x1 + x2) / 2,
+                cy = k * -ry * x / rx + (y1 + y2) / 2,
+                f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+                f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+            f1 = x1 < cx ? PI - f1 : f1;
+            f2 = x2 < cx ? PI - f2 : f2;
+            f1 < 0 && (f1 = PI * 2 + f1);
+            f2 < 0 && (f2 = PI * 2 + f2);
+            if (sweep_flag && f1 > f2) {
+                f1 = f1 - PI * 2;
+            }
+            if (!sweep_flag && f2 > f1) {
+                f2 = f2 - PI * 2;
+            }
+        } else {
+            f1 = recursive[0];
+            f2 = recursive[1];
+            cx = recursive[2];
+            cy = recursive[3];
+        }
+        var df = f2 - f1;
+        if (abs(df) > _120) {
+            var f2old = f2,
+                x2old = x2,
+                y2old = y2;
+            f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+            x2 = cx + rx * math.cos(f2);
+            y2 = cy + ry * math.sin(f2);
+            res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+        }
+        df = f2 - f1;
+        var c1 = math.cos(f1),
+            s1 = math.sin(f1),
+            c2 = math.cos(f2),
+            s2 = math.sin(f2),
+            t = math.tan(df / 4),
+            hx = 4 / 3 * rx * t,
+            hy = 4 / 3 * ry * t,
+            m1 = [x1, y1],
+            m2 = [x1 + hx * s1, y1 - hy * c1],
+            m3 = [x2 + hx * s2, y2 - hy * c2],
+            m4 = [x2, y2];
+        m2[0] = 2 * m1[0] - m2[0];
+        m2[1] = 2 * m1[1] - m2[1];
+        if (recursive) {
+            return [m2, m3, m4].concat(res);
+        } else {
+            res = [m2, m3, m4].concat(res).join().split(",");
+            var newres = [];
+            for (var i = 0, ii = res.length; i < ii; i++) {
+                newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+            }
+            return newres;
+        }
+    }
+    function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t;
+        return {
+            x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+            y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+        };
+    }
+
+    // Returns bounding box of cubic bezier curve.
+    // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+    // Original version: NISHIO Hirokazu
+    // Modifications: https://github.com/timo22345
+    function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) {
+        var tvalues = [],
+            bounds = [[], []],
+            a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+        for (var i = 0; i < 2; ++i) {
+            if (i == 0) {
+                b = 6 * x0 - 12 * x1 + 6 * x2;
+                a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+                c = 3 * x1 - 3 * x0;
+            } else {
+                b = 6 * y0 - 12 * y1 + 6 * y2;
+                a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+                c = 3 * y1 - 3 * y0;
+            }
+            if (abs(a) < 1e-12) {
+                if (abs(b) < 1e-12) {
+                    continue;
+                }
+                t = -c / b;
+                if (0 < t && t < 1) {
+                    tvalues.push(t);
+                }
+                continue;
+            }
+            b2ac = b * b - 4 * c * a;
+            sqrtb2ac = math.sqrt(b2ac);
+            if (b2ac < 0) {
+                continue;
+            }
+            t1 = (-b + sqrtb2ac) / (2 * a);
+            if (0 < t1 && t1 < 1) {
+                tvalues.push(t1);
+            }
+            t2 = (-b - sqrtb2ac) / (2 * a);
+            if (0 < t2 && t2 < 1) {
+                tvalues.push(t2);
+            }
+        }
+
+        var x, y, j = tvalues.length,
+            jlen = j,
+            mt;
+        while (j--) {
+            t = tvalues[j];
+            mt = 1 - t;
+            bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
+            bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
+        }
+
+        bounds[0][jlen] = x0;
+        bounds[1][jlen] = y0;
+        bounds[0][jlen + 1] = x3;
+        bounds[1][jlen + 1] = y3;
+        bounds[0].length = bounds[1].length = jlen + 2;
+
+
+        return {
+          min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])},
+          max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])}
+        };
+    }
+
+    function path2curve(path, path2) {
+        var pth = !path2 && paths(path);
+        if (!path2 && pth.curve) {
+            return pathClone(pth.curve);
+        }
+        var p = pathToAbsolute(path),
+            p2 = path2 && pathToAbsolute(path2),
+            attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+            processPath = function (path, d, pcom) {
+                var nx, ny;
+                if (!path) {
+                    return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+                }
+                !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
+                switch (path[0]) {
+                    case "M":
+                        d.X = path[1];
+                        d.Y = path[2];
+                        break;
+                    case "A":
+                        path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
+                        break;
+                    case "S":
+                        if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
+                            nx = d.x * 2 - d.bx;          // And reflect the previous
+                            ny = d.y * 2 - d.by;          // command's control point relative to the current point.
+                        }
+                        else {                            // or some else or nothing
+                            nx = d.x;
+                            ny = d.y;
+                        }
+                        path = ["C", nx, ny].concat(path.slice(1));
+                        break;
+                    case "T":
+                        if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
+                            d.qx = d.x * 2 - d.qx;        // And make a reflection similar
+                            d.qy = d.y * 2 - d.qy;        // to case "S".
+                        }
+                        else {                            // or something else or nothing
+                            d.qx = d.x;
+                            d.qy = d.y;
+                        }
+                        path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+                        break;
+                    case "Q":
+                        d.qx = path[1];
+                        d.qy = path[2];
+                        path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                        break;
+                    case "L":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
+                        break;
+                    case "H":
+                        path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
+                        break;
+                    case "V":
+                        path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
+                        break;
+                    case "Z":
+                        path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
+                        break;
+                }
+                return path;
+            },
+            fixArc = function (pp, i) {
+                if (pp[i].length > 7) {
+                    pp[i].shift();
+                    var pi = pp[i];
+                    while (pi.length) {
+                        pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved
+                        p2 && (pcoms2[i] = "A"); // the same as above
+                        pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
+                    }
+                    pp.splice(i, 1);
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            fixM = function (path1, path2, a1, a2, i) {
+                if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                    path2.splice(i, 0, ["M", a2.x, a2.y]);
+                    a1.bx = 0;
+                    a1.by = 0;
+                    a1.x = path1[i][1];
+                    a1.y = path1[i][2];
+                    ii = mmax(p.length, p2 && p2.length || 0);
+                }
+            },
+            pcoms1 = [], // path commands of original path p
+            pcoms2 = [], // path commands of original path p2
+            pfirst = "", // temporary holder for original path command
+            pcom = ""; // holder for previous path command of original path
+        for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+            p[i] && (pfirst = p[i][0]); // save current path command
+
+            if (pfirst != "C") // C is not saved yet, because it may be result of conversion
+            {
+                pcoms1[i] = pfirst; // Save current path command
+                i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom
+            }
+            p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
+
+            if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command
+            // which may produce multiple C:s
+            // so we have to make sure that C is also C in original path
+
+            fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
+
+            if (p2) { // the same procedures is done to p2
+                p2[i] && (pfirst = p2[i][0]);
+                if (pfirst != "C") {
+                    pcoms2[i] = pfirst;
+                    i && (pcom = pcoms2[i - 1]);
+                }
+                p2[i] = processPath(p2[i], attrs2, pcom);
+
+                if (pcoms2[i] != "A" && pfirst == "C") {
+                    pcoms2[i] = "C";
+                }
+
+                fixArc(p2, i);
+            }
+            fixM(p, p2, attrs, attrs2, i);
+            fixM(p2, p, attrs2, attrs, i);
+            var seg = p[i],
+                seg2 = p2 && p2[i],
+                seglen = seg.length,
+                seg2len = p2 && seg2.length;
+            attrs.x = seg[seglen - 2];
+            attrs.y = seg[seglen - 1];
+            attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+            attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+            attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+            attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+            attrs2.x = p2 && seg2[seg2len - 2];
+            attrs2.y = p2 && seg2[seg2len - 1];
+        }
+        if (!p2) {
+            pth.curve = pathClone(p);
+        }
+        return p2 ? [p, p2] : p;
+    }
+    function mapPath(path, matrix) {
+        if (!matrix) {
+            return path;
+        }
+        var x, y, i, j, ii, jj, pathi;
+        path = path2curve(path);
+        for (i = 0, ii = path.length; i < ii; i++) {
+            pathi = path[i];
+            for (j = 1, jj = pathi.length; j < jj; j += 2) {
+                x = matrix.x(pathi[j], pathi[j + 1]);
+                y = matrix.y(pathi[j], pathi[j + 1]);
+                pathi[j] = x;
+                pathi[j + 1] = y;
+            }
+        }
+        return path;
+    }
+
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp, z) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+            var p = [
+                        {x: +crp[i - 2], y: +crp[i - 1]},
+                        {x: +crp[i],     y: +crp[i + 1]},
+                        {x: +crp[i + 2], y: +crp[i + 3]},
+                        {x: +crp[i + 4], y: +crp[i + 5]}
+                    ];
+            if (z) {
+                if (!i) {
+                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+                } else if (iLen - 4 == i) {
+                    p[3] = {x: +crp[0], y: +crp[1]};
+                } else if (iLen - 2 == i) {
+                    p[2] = {x: +crp[0], y: +crp[1]};
+                    p[3] = {x: +crp[2], y: +crp[3]};
+                }
+            } else {
+                if (iLen - 4 == i) {
+                    p[3] = p[2];
+                } else if (!i) {
+                    p[0] = {x: +crp[i], y: +crp[i + 1]};
+                }
+            }
+            d.push(["C",
+                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                  (p[1].y + 6*p[2].y - p[3].y) / 6,
+                  p[2].x,
+                  p[2].y
+            ]);
+        }
+
+        return d;
+    }
+
+    // export
+    Snap.path = paths;
+
+    /*\
+     * Snap.path.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the given path in pixels
+     **
+     - path (string) SVG path string
+     **
+     = (number) length
+    \*/
+    Snap.path.getTotalLength = getTotalLength;
+    /*\
+     * Snap.path.getPointAtLength
+     [ method ]
+     **
+     * Returns the coordinates of the point located at the given length along the given path
+     **
+     - path (string) SVG path string
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    Snap.path.getPointAtLength = getPointAtLength;
+    /*\
+     * Snap.path.getSubpath
+     [ method ]
+     **
+     * Returns the subpath of a given path between given start and end lengths
+     **
+     - path (string) SVG path string
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    Snap.path.getSubpath = function (path, from, to) {
+        if (this.getTotalLength(path) - to < 1e-6) {
+            return getSubpathsAtLength(path, from).end;
+        }
+        var a = getSubpathsAtLength(path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+    /*\
+     * Element.getTotalLength
+     [ method ]
+     **
+     * Returns the length of the path in pixels (only works for `path` elements)
+     = (number) length
+    \*/
+    elproto.getTotalLength = function () {
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+    };
+    // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a <path> is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length?
+    /*\
+     * Element.getPointAtLength
+     [ method ]
+     **
+     * Returns coordinates of the point located at the given length on the given path (only works for `path` elements)
+     **
+     - length (number) length, in pixels, from the start of the path, excluding non-rendering jumps
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate,
+     o     y: (number) y coordinate,
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    elproto.getPointAtLength = function (length) {
+        return getPointAtLength(this.attr("d"), length);
+    };
+    // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear.
+    /*\
+     * Element.getSubpath
+     [ method ]
+     **
+     * Returns subpath of a given element from given start and end lengths (only works for `path` elements)
+     **
+     - from (number) length, in pixels, from the start of the path to the start of the segment
+     - to (number) length, in pixels, from the start of the path to the end of the segment
+     **
+     = (string) path string definition for the segment
+    \*/
+    elproto.getSubpath = function (from, to) {
+        return Snap.path.getSubpath(this.attr("d"), from, to);
+    };
+    Snap._.box = box;
+    /*\
+     * Snap.path.findDotsAtSegment
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds dot coordinates on the given cubic beziér curve at the given t
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     - t (number) position on the curve (0..1)
+     = (object) point information in format:
+     o {
+     o     x: (number) x coordinate of the point,
+     o     y: (number) y coordinate of the point,
+     o     m: {
+     o         x: (number) x coordinate of the left anchor,
+     o         y: (number) y coordinate of the left anchor
+     o     },
+     o     n: {
+     o         x: (number) x coordinate of the right anchor,
+     o         y: (number) y coordinate of the right anchor
+     o     },
+     o     start: {
+     o         x: (number) x coordinate of the start of the curve,
+     o         y: (number) y coordinate of the start of the curve
+     o     },
+     o     end: {
+     o         x: (number) x coordinate of the end of the curve,
+     o         y: (number) y coordinate of the end of the curve
+     o     },
+     o     alpha: (number) angle of the curve derivative at the point
+     o }
+    \*/
+    Snap.path.findDotsAtSegment = findDotsAtSegment;
+    /*\
+     * Snap.path.bezierBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given cubic beziér curve
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     * or
+     - bez (array) array of six points for beziér curve
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.bezierBBox = bezierBBox;
+    /*\
+     * Snap.path.isPointInsideBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside bounding box
+     - bbox (string) bounding box
+     - x (string) x coordinate of the point
+     - y (string) y coordinate of the point
+     = (boolean) `true` if point is inside
+    \*/
+    Snap.path.isPointInsideBBox = isPointInsideBBox;
+    Snap.closest = function (x, y, X, Y) {
+        var r = 100,
+            b = box(x - r / 2, y - r / 2, r, r),
+            inside = [],
+            getter = X[0].hasOwnProperty("x") ? function (i) {
+                return {
+                    x: X[i].x,
+                    y: X[i].y
+                };
+            } : function (i) {
+                return {
+                    x: X[i],
+                    y: Y[i]
+                };
+            },
+            found = 0;
+        while (r <= 1e6 && !found) {
+            for (var i = 0, ii = X.length; i < ii; i++) {
+                var xy = getter(i);
+                if (isPointInsideBBox(b, xy.x, xy.y)) {
+                    found++;
+                    inside.push(xy);
+                    break;
+                }
+            }
+            if (!found) {
+                r *= 2;
+                b = box(x - r / 2, y - r / 2, r, r)
+            }
+        }
+        if (r == 1e6) {
+            return;
+        }
+        var len = Infinity,
+            res;
+        for (i = 0, ii = inside.length; i < ii; i++) {
+            var l = Snap.len(x, y, inside[i].x, inside[i].y);
+            if (len > l) {
+                len = l;
+                inside[i].len = l;
+                res = inside[i];
+            }
+        }
+        return res;
+    };
+    /*\
+     * Snap.path.isBBoxIntersect
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if two bounding boxes intersect
+     - bbox1 (string) first bounding box
+     - bbox2 (string) second bounding box
+     = (boolean) `true` if bounding boxes intersect
+    \*/
+    Snap.path.isBBoxIntersect = isBBoxIntersect;
+    /*\
+     * Snap.path.intersection
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds intersections of two paths
+     - path1 (string) path string
+     - path2 (string) path string
+     = (array) dots of intersection
+     o [
+     o     {
+     o         x: (number) x coordinate of the point,
+     o         y: (number) y coordinate of the point,
+     o         t1: (number) t value for segment of path1,
+     o         t2: (number) t value for segment of path2,
+     o         segment1: (number) order number for segment of path1,
+     o         segment2: (number) order number for segment of path2,
+     o         bez1: (array) eight coordinates representing beziér curve for the segment of path1,
+     o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
+     o     }
+     o ]
+    \*/
+    Snap.path.intersection = pathIntersection;
+    Snap.path.intersectionNumber = pathIntersectionNumber;
+    /*\
+     * Snap.path.isPointInside
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside a given closed path.
+     *
+     * Note: fill mode doesn’t affect the result of this method.
+     - path (string) path string
+     - x (number) x of the point
+     - y (number) y of the point
+     = (boolean) `true` if point is inside the path
+    \*/
+    Snap.path.isPointInside = isPointInsidePath;
+    /*\
+     * Snap.path.getBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns the bounding box of a given path
+     - path (string) path string
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box,
+     o     y: (number) y coordinate of the left top point of the box,
+     o     x2: (number) x coordinate of the right bottom point of the box,
+     o     y2: (number) y coordinate of the right bottom point of the box,
+     o     width: (number) width of the box,
+     o     height: (number) height of the box
+     o }
+    \*/
+    Snap.path.getBBox = pathBBox;
+    Snap.path.get = getPath;
+    /*\
+     * Snap.path.toRelative
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into relative values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toRelative = pathToRelative;
+    /*\
+     * Snap.path.toAbsolute
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path coordinates into absolute values
+     - path (string) path string
+     = (array) path string
+    \*/
+    Snap.path.toAbsolute = pathToAbsolute;
+    /*\
+     * Snap.path.toCubic
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path to a new path where all segments are cubic beziér curves
+     - pathString (string|array) path string or array of segments
+     = (array) array of segments
+    \*/
+    Snap.path.toCubic = path2curve;
+    /*\
+     * Snap.path.map
+     [ method ]
+     **
+     * Transform the path string with the given matrix
+     - path (string) path string
+     - matrix (object) see @Matrix
+     = (string) transformed path string
+    \*/
+    Snap.path.map = mapPath;
+    Snap.path.toString = toString;
+    Snap.path.clone = pathClone;
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var mmax = Math.max,
+        mmin = Math.min;
+
+    // Set
+    var Set = function (items) {
+        this.items = [];
+	this.bindings = {};
+        this.length = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items.length; i < ii; i++) {
+                if (items[i]) {
+                    this[this.items.length] = this.items[this.items.length] = items[i];
+                    this.length++;
+                }
+            }
+        }
+    },
+    setproto = Set.prototype;
+    /*\
+     * Set.push
+     [ method ]
+     **
+     * Adds each argument to the current set
+     = (object) original element
+    \*/
+    setproto.push = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments.length; i < ii; i++) {
+            item = arguments[i];
+            if (item) {
+                len = this.items.length;
+                this[len] = this.items[len] = item;
+                this.length++;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.pop
+     [ method ]
+     **
+     * Removes last element and returns it
+     = (object) element
+    \*/
+    setproto.pop = function () {
+        this.length && delete this[this.length--];
+        return this.items.pop();
+    };
+    /*\
+     * Set.forEach
+     [ method ]
+     **
+     * Executes given function for each element in the set
+     *
+     * If the function returns `false`, the loop stops running.
+     **
+     - callback (function) function to run
+     - thisArg (object) context object for the callback
+     = (object) Set object
+    \*/
+    setproto.forEach = function (callback, thisArg) {
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
+                return this;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.animate
+     [ method ]
+     **
+     * Animates each element in set in sync.
+     *
+     **
+     - attrs (object) key-value pairs of destination attributes
+     - duration (number) duration of the animation in milliseconds
+     - easing (function) #optional easing function from @mina or custom
+     - callback (function) #optional callback function that executes when the animation ends
+     * or
+     - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]`
+     > Usage
+     | // animate all elements in set to radius 10
+     | set.animate({r: 10}, 500, mina.easein);
+     | // or
+     | // animate first element to radius 10, but second to radius 20 and in different time
+     | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]);
+     = (Element) the current element
+    \*/
+    setproto.animate = function (attrs, ms, easing, callback) {
+        if (typeof easing == "function" && !easing.length) {
+            callback = easing;
+            easing = mina.linear;
+        }
+        if (attrs instanceof Snap._.Animation) {
+            callback = attrs.callback;
+            easing = attrs.easing;
+            ms = easing.dur;
+            attrs = attrs.attr;
+        }
+        var args = arguments;
+        if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) {
+            var each = true;
+        }
+        var begin,
+            handler = function () {
+                if (begin) {
+                    this.b = begin;
+                } else {
+                    begin = this.b;
+                }
+            },
+            cb = 0,
+            set = this,
+            callbacker = callback && function () {
+                if (++cb == set.length) {
+                    callback.call(this);
+                }
+            };
+        return this.forEach(function (el, i) {
+            eve.once("snap.animcreated." + el.id, handler);
+            if (each) {
+                args[i] && el.animate.apply(el, args[i]);
+            } else {
+                el.animate(attrs, ms, easing, callbacker);
+            }
+        });
+    };
+    setproto.remove = function () {
+        while (this.length) {
+            this.pop().remove();
+        }
+        return this;
+    };
+    /*\
+     * Set.bind
+     [ method ]
+     **
+     * Specifies how to handle a specific attribute when applied
+     * to a set.
+     *
+     **
+     - attr (string) attribute name
+     - callback (function) function to run
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     * or
+     - attr (string) attribute name
+     - element (Element) specific element in the set to apply the attribute to
+     - eattr (string) attribute on the element to bind the attribute to
+     = (object) Set object
+    \*/
+    setproto.bind = function (attr, a, b) {
+        var data = {};
+        if (typeof a == "function") {
+            this.bindings[attr] = a;
+        } else {
+            var aname = b || attr;
+            this.bindings[attr] = function (v) {
+                data[aname] = v;
+                a.attr(data);
+            };
+        }
+        return this;
+    };
+    setproto.attr = function (value) {
+        var unbound = {};
+        for (var k in value) {
+            if (this.bindings[k]) {
+                this.bindings[k](value[k]);
+            } else {
+                unbound[k] = value[k];
+            }
+        }
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            this.items[i].attr(unbound);
+        }
+        return this;
+    };
+    /*\
+     * Set.clear
+     [ method ]
+     **
+     * Removes all elements from the set
+    \*/
+    setproto.clear = function () {
+        while (this.length) {
+            this.pop();
+        }
+    };
+    /*\
+     * Set.splice
+     [ method ]
+     **
+     * Removes range of elements from the set
+     **
+     - index (number) position of the deletion
+     - count (number) number of element to remove
+     - insertion… (object) #optional elements to insert
+     = (object) set elements that were deleted
+    \*/
+    setproto.splice = function (index, count, insertion) {
+        index = index < 0 ? mmax(this.length + index, 0) : index;
+        count = mmax(0, mmin(this.length - index, count));
+        var tail = [],
+            todel = [],
+            args = [],
+            i;
+        for (i = 2; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        }
+        for (i = 0; i < count; i++) {
+            todel.push(this[index + i]);
+        }
+        for (; i < this.length - index; i++) {
+            tail.push(this[index + i]);
+        }
+        var arglen = args.length;
+        for (i = 0; i < arglen + tail.length; i++) {
+            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+        }
+        i = this.items.length = this.length -= count - arglen;
+        while (this[i]) {
+            delete this[i++];
+        }
+        return new Set(todel);
+    };
+    /*\
+     * Set.exclude
+     [ method ]
+     **
+     * Removes given element from the set
+     **
+     - element (object) element to remove
+     = (boolean) `true` if object was found and removed from the set
+    \*/
+    setproto.exclude = function (el) {
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
+            return true;
+        }
+        return false;
+    };
+    setproto.insertAfter = function (el) {
+        var i = this.items.length;
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    setproto.getBBox = function () {
+        var x = [],
+            y = [],
+            x2 = [],
+            y2 = [];
+        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+            var box = this.items[i].getBBox();
+            x.push(box.x);
+            y.push(box.y);
+            x2.push(box.x + box.width);
+            y2.push(box.y + box.height);
+        }
+        x = mmin.apply(0, x);
+        y = mmin.apply(0, y);
+        x2 = mmax.apply(0, x2);
+        y2 = mmax.apply(0, y2);
+        return {
+            x: x,
+            y: y,
+            x2: x2,
+            y2: y2,
+            width: x2 - x,
+            height: y2 - y,
+            cx: x + (x2 - x) / 2,
+            cy: y + (y2 - y) / 2
+        };
+    };
+    setproto.clone = function (s) {
+        s = new Set;
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            s.push(this.items[i].clone());
+        }
+        return s;
+    };
+    setproto.toString = function () {
+        return "Snap\u2018s set";
+    };
+    setproto.type = "set";
+    // export
+    Snap.Set = Set;
+    Snap.set = function () {
+        var set = new Set;
+        if (arguments.length) {
+            set.push.apply(set, Array.prototype.slice.call(arguments, 0));
+        }
+        return set;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var names = {},
+        reUnit = /[a-z]+$/i,
+        Str = String;
+    names.stroke = names.fill = "colour";
+    function getEmpty(item) {
+        var l = item[0];
+        switch (l.toLowerCase()) {
+            case "t": return [l, 0, 0];
+            case "m": return [l, 1, 0, 0, 1, 0, 0];
+            case "r": if (item.length == 4) {
+                return [l, 0, item[2], item[3]];
+            } else {
+                return [l, 0];
+            }
+            case "s": if (item.length == 5) {
+                return [l, 1, 1, item[3], item[4]];
+            } else if (item.length == 3) {
+                return [l, 1, 1];
+            } else {
+                return [l, 1];
+            }
+        }
+    }
+    function equaliseTransform(t1, t2, getBBox) {
+        t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+        t1 = Snap.parseTransformString(t1) || [];
+        t2 = Snap.parseTransformString(t2) || [];
+        var maxlength = Math.max(t1.length, t2.length),
+            from = [],
+            to = [],
+            i = 0, j, jj,
+            tt1, tt2;
+        for (; i < maxlength; i++) {
+            tt1 = t1[i] || getEmpty(t2[i]);
+            tt2 = t2[i] || getEmpty(tt1);
+            if ((tt1[0] != tt2[0]) ||
+                (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                ) {
+                    t1 = Snap._.transform2matrix(t1, getBBox());
+                    t2 = Snap._.transform2matrix(t2, getBBox());
+                    from = [["m", t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]];
+                    to = [["m", t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]];
+                    break;
+            }
+            from[i] = [];
+            to[i] = [];
+            for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) {
+                j in tt1 && (from[i][j] = tt1[j]);
+                j in tt2 && (to[i][j] = tt2[j]);
+            }
+        }
+        return {
+            from: path2array(from),
+            to: path2array(to),
+            f: getPath(from)
+        };
+    }
+    function getNumber(val) {
+        return val;
+    }
+    function getUnit(unit) {
+        return function (val) {
+            return +val.toFixed(3) + unit;
+        };
+    }
+    function getViewBox(val) {
+        return val.join(" ");
+    }
+    function getColour(clr) {
+        return Snap.rgb(clr[0], clr[1], clr[2]);
+    }
+    function getPath(path) {
+        var k = 0, i, ii, j, jj, out, a, b = [];
+        for (i = 0, ii = path.length; i < ii; i++) {
+            out = "[";
+            a = ['"' + path[i][0] + '"'];
+            for (j = 1, jj = path[i].length; j < jj; j++) {
+                a[j] = "val[" + (k++) + "]";
+            }
+            out += a + "]";
+            b[i] = out;
+        }
+        return Function("val", "return Snap.path.toString.call([" + b + "])");
+    }
+    function path2array(path) {
+        var out = [];
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            for (var j = 1, jj = path[i].length; j < jj; j++) {
+                out.push(path[i][j]);
+            }
+        }
+        return out;
+    }
+    function isNumeric(obj) {
+        return isFinite(parseFloat(obj));
+    }
+    function arrayEqual(arr1, arr2) {
+        if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) {
+            return false;
+        }
+        return arr1.toString() == arr2.toString();
+    }
+    Element.prototype.equal = function (name, b) {
+        return eve("snap.util.equal", this, name, b).firstDefined();
+    };
+    eve.on("snap.util.equal", function (name, b) {
+        var A, B, a = Str(this.attr(name) || ""),
+            el = this;
+        if (isNumeric(a) && isNumeric(b)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getNumber
+            };
+        }
+        if (names[name] == "colour") {
+            A = Snap.color(a);
+            B = Snap.color(b);
+            return {
+                from: [A.r, A.g, A.b, A.opacity],
+                to: [B.r, B.g, B.b, B.opacity],
+                f: getColour
+            };
+        }
+        if (name == "viewBox") {
+            A = this.attr(name).vb.split(" ").map(Number);
+            B = b.split(" ").map(Number);
+            return {
+                from: A,
+                to: B,
+                f: getViewBox
+            };
+        }
+        if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
+            if (b instanceof Snap.Matrix) {
+                b = b.toTransformString();
+            }
+            if (!Snap._.rgTransform.test(b)) {
+                b = Snap._.svgTransform2string(b);
+            }
+            return equaliseTransform(a, b, function () {
+                return el.getBBox(1);
+            });
+        }
+        if (name == "d" || name == "path") {
+            A = Snap.path.toCubic(a, b);
+            return {
+                from: path2array(A[0]),
+                to: path2array(A[1]),
+                f: getPath(A[0])
+            };
+        }
+        if (name == "points") {
+            A = Str(a).split(Snap._.separator);
+            B = Str(b).split(Snap._.separator);
+            return {
+                from: A,
+                to: B,
+                f: function (val) { return val; }
+            };
+        }
+        var aUnit = a.match(reUnit),
+            bUnit = Str(b).match(reUnit);
+        if (aUnit && arrayEqual(aUnit, bUnit)) {
+            return {
+                from: parseFloat(a),
+                to: parseFloat(b),
+                f: getUnit(aUnit)
+            };
+        } else {
+            return {
+                from: this.asPX(name),
+                to: this.asPX(name, b),
+                f: getNumber
+            };
+        }
+    });
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+    has = "hasOwnProperty",
+    supportsTouch = "createTouch" in glob.doc,
+    events = [
+        "click", "dblclick", "mousedown", "mousemove", "mouseout",
+        "mouseover", "mouseup", "touchstart", "touchmove", "touchend",
+        "touchcancel"
+    ],
+    touchMap = {
+        mousedown: "touchstart",
+        mousemove: "touchmove",
+        mouseup: "touchend"
+    },
+    getScroll = function (xy, el) {
+        var name = xy == "y" ? "scrollTop" : "scrollLeft",
+            doc = el && el.node ? el.node.ownerDocument : glob.doc;
+        return doc[name in doc.documentElement ? "documentElement" : "body"][name];
+    },
+    preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = function (obj, type, fn, element) {
+        var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+            f = function (e) {
+                var scrollY = getScroll("y", element),
+                    scrollX = getScroll("x", element);
+                if (supportsTouch && touchMap[has](type)) {
+                    for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                        if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) {
+                            var olde = e;
+                            e = e.targetTouches[i];
+                            e.originalEvent = olde;
+                            e.preventDefault = preventTouch;
+                            e.stopPropagation = stopTouch;
+                            break;
+                        }
+                    }
+                }
+                var x = e.clientX + scrollX,
+                    y = e.clientY + scrollY;
+                return fn.call(element, e, x, y);
+            };
+
+        if (type !== realName) {
+            obj.addEventListener(type, f, false);
+        }
+
+        obj.addEventListener(realName, f, false);
+
+        return function () {
+            if (type !== realName) {
+                obj.removeEventListener(type, f, false);
+            }
+
+            obj.removeEventListener(realName, f, false);
+            return true;
+        };
+    },
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = getScroll("y"),
+            scrollX = getScroll("x"),
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches && e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            var node = dragi.el.node,
+                o,
+                next = node.nextSibling,
+                parent = node.parentNode,
+                display = node.style.display;
+            // glob.win.opera && parent.removeChild(node);
+            // node.style.display = "none";
+            // o = dragi.el.paper.getElementByPoint(x, y);
+            // node.style.display = display;
+            // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+            // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o);
+            x += scrollX;
+            y += scrollY;
+            eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        Snap.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+            eve.off("snap.drag.*." + dragi.el.id);
+        }
+        drag = [];
+    };
+    /*\
+     * Element.click
+     [ method ]
+     **
+     * Adds a click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unclick
+     [ method ]
+     **
+     * Removes a click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.dblclick
+     [ method ]
+     **
+     * Adds a double click event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.undblclick
+     [ method ]
+     **
+     * Removes a double click event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousedown
+     [ method ]
+     **
+     * Adds a mousedown event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousedown
+     [ method ]
+     **
+     * Removes a mousedown event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mousemove
+     [ method ]
+     **
+     * Adds a mousemove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousemove
+     [ method ]
+     **
+     * Removes a mousemove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseout
+     [ method ]
+     **
+     * Adds a mouseout event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseout
+     [ method ]
+     **
+     * Removes a mouseout event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseover
+     [ method ]
+     **
+     * Adds a mouseover event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseover
+     [ method ]
+     **
+     * Removes a mouseover event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.mouseup
+     [ method ]
+     **
+     * Adds a mouseup event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseup
+     [ method ]
+     **
+     * Removes a mouseup event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchstart
+     [ method ]
+     **
+     * Adds a touchstart event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchstart
+     [ method ]
+     **
+     * Removes a touchstart event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchmove
+     [ method ]
+     **
+     * Adds a touchmove event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchmove
+     [ method ]
+     **
+     * Removes a touchmove event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchend
+     [ method ]
+     **
+     * Adds a touchend event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchend
+     [ method ]
+     **
+     * Removes a touchend event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+
+    /*\
+     * Element.touchcancel
+     [ method ]
+     **
+     * Adds a touchcancel event handler to the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchcancel
+     [ method ]
+     **
+     * Removes a touchcancel event handler from the element
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    for (var i = events.length; i--;) {
+        (function (eventName) {
+            Snap[eventName] = elproto[eventName] = function (fn, scope) {
+                if (Snap.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({
+                        name: eventName,
+                        f: fn,
+                        unbind: addEvent(this.node || document, eventName, fn, scope || this)
+                    });
+                } else {
+                    for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) {
+                        try {
+                            this.events[i].f.call(this);
+                        } catch (e) {}
+                    }
+                }
+                return this;
+            };
+            Snap["un" + eventName] =
+            elproto["un" + eventName] = function (fn) {
+                var events = this.events || [],
+                    l = events.length;
+                while (l--) if (events[l].name == eventName &&
+                               (events[l].f == fn || !fn)) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    /*\
+     * Element.hover
+     [ method ]
+     **
+     * Adds hover event handlers to the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     - icontext (object) #optional context for hover in handler
+     - ocontext (object) #optional context for hover out handler
+     = (object) @Element
+    \*/
+    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+    };
+    /*\
+     * Element.unhover
+     [ method ]
+     **
+     * Removes hover event handlers from the element
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     = (object) @Element
+    \*/
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    var draggable = [];
+    // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture.
+    // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from?
+    // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason.
+    // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start.<id> on start, drag.end.<id> on end and drag.move.<id> on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID?
+    /*\
+     * Element.drag
+     [ method ]
+     **
+     * Adds event handlers for an element's drag gesture
+     **
+     - onmove (function) handler for moving
+     - onstart (function) handler for drag start
+     - onend (function) handler for drag end
+     - mcontext (object) #optional context for moving handler
+     - scontext (object) #optional context for drag start handler
+     - econtext (object) #optional context for drag end handler
+     * Additionaly following `drag` events are triggered: `drag.start.<id>` on start,
+     * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element is dragged over another element
+     * `drag.over.<id>` fires as well.
+     *
+     * Start event and start handler are called in specified context or in context of the element with following parameters:
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * Move event and move handler are called in specified context or in context of the element with following parameters:
+     o dx (number) shift by x from the start point
+     o dy (number) shift by y from the start point
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * End event and end handler are called in specified context or in context of the element with following parameters:
+     o event (object) DOM event object
+     = (object) @Element
+    \*/
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        var el = this;
+        if (!arguments.length) {
+            var origTransform;
+            return el.drag(function (dx, dy) {
+                this.attr({
+                    transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
+                });
+            }, function () {
+                origTransform = this.transform().local;
+            });
+        }
+        function start(e, x, y) {
+            (e.originalEvent || e).preventDefault();
+            el._drag.x = x;
+            el._drag.y = y;
+            el._drag.id = e.identifier;
+            !drag.length && Snap.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+            onstart && eve.on("snap.drag.start." + el.id, onstart);
+            onmove && eve.on("snap.drag.move." + el.id, onmove);
+            onend && eve.on("snap.drag.end." + el.id, onend);
+            eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e);
+        }
+        function init(e, x, y) {
+            eve("snap.draginit." + el.id, el, e, x, y);
+        }
+        eve.on("snap.draginit." + el.id, start);
+        el._drag = {};
+        draggable.push({el: el, start: start, init: init});
+        el.mousedown(init);
+        return el;
+    };
+    /*
+     * Element.onDragOver
+     [ method ]
+     **
+     * Shortcut to assign event handler for `drag.over.<id>` event, where `id` is the element's `id` (see @Element.id)
+     - f (function) handler for event, first argument would be the element you are dragging over
+    \*/
+    // elproto.onDragOver = function (f) {
+    //     f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id);
+    // };
+    /*\
+     * Element.undrag
+     [ method ]
+     **
+     * Removes all drag event handlers from the given element
+    \*/
+    elproto.undrag = function () {
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].init);
+            draggable.splice(i, 1);
+            eve.unbind("snap.drag.*." + this.id);
+            eve.unbind("snap.draginit." + this.id);
+        }
+        !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp);
+        return this;
+    };
+});
+
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob) {
+    var elproto = Element.prototype,
+        pproto = Paper.prototype,
+        rgurl = /^\s*url\((.+)\)/,
+        Str = String,
+        $ = Snap._.$;
+    Snap.filter = {};
+    /*\
+     * Paper.filter
+     [ method ]
+     **
+     * Creates a `<filter>` element
+     **
+     - filstr (string) SVG fragment of filter provided as a string
+     = (object) @Element
+     * Note: It is recommended to use filters embedded into the page inside an empty SVG element.
+     > Usage
+     | var f = paper.filter('<feGaussianBlur stdDeviation="2"/>'),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    pproto.filter = function (filstr) {
+        var paper = this;
+        if (paper.type != "svg") {
+            paper = paper.paper;
+        }
+        var f = Snap.parse(Str(filstr)),
+            id = Snap._.id(),
+            width = paper.node.offsetWidth,
+            height = paper.node.offsetHeight,
+            filter = $("filter");
+        $(filter, {
+            id: id,
+            filterUnits: "userSpaceOnUse"
+        });
+        filter.appendChild(f.node);
+        paper.defs.appendChild(filter);
+        return new Element(filter);
+    };
+
+    eve.on("snap.util.getattr.filter", function () {
+        eve.stop();
+        var p = $(this.node, "filter");
+        if (p) {
+            var match = Str(p).match(rgurl);
+            return match && Snap.select(match[1]);
+        }
+    });
+    eve.on("snap.util.attr.filter", function (value) {
+        if (value instanceof Element && value.type == "filter") {
+            eve.stop();
+            var id = value.node.id;
+            if (!id) {
+                $(value.node, {id: value.id});
+                id = value.id;
+            }
+            $(this.node, {
+                filter: Snap.url(id)
+            });
+        }
+        if (!value || value == "none") {
+            eve.stop();
+            this.node.removeAttribute("filter");
+        }
+    });
+    /*\
+     * Snap.filter.blur
+     [ method ]
+     **
+     * Returns an SVG markup string for the blur filter
+     **
+     - x (number) amount of horizontal blur, in pixels
+     - y (number) #optional amount of vertical blur, in pixels
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.blur(5, 10)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.blur = function (x, y) {
+        if (x == null) {
+            x = 2;
+        }
+        var def = y == null ? x : [x, y];
+        return Snap.format('\<feGaussianBlur stdDeviation="{def}"/>', {
+            def: def
+        });
+    };
+    Snap.filter.blur.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.shadow
+     [ method ]
+     **
+     * Returns an SVG markup string for the shadow filter
+     **
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - blur (number) #optional amount of blur
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - color (string) #optional color of the shadow
+     - opacity (number) #optional `0..1` opacity of the shadow
+     * which makes blur default to `4`. Or
+     - dx (number) #optional horizontal shift of the shadow, in pixels
+     - dy (number) #optional vertical shift of the shadow, in pixels
+     - opacity (number) #optional `0..1` opacity of the shadow
+     = (string) filter representation
+     > Usage
+     | var f = paper.filter(Snap.filter.shadow(0, 2, 3)),
+     |     c = paper.circle(10, 10, 10).attr({
+     |         filter: f
+     |     });
+    \*/
+    Snap.filter.shadow = function (dx, dy, blur, color, opacity) {
+        if (typeof blur == "string") {
+            color = blur;
+            opacity = color;
+            blur = 4;
+        }
+        if (typeof color != "string") {
+            opacity = color;
+            color = "#000";
+        }
+        color = color || "#000";
+        if (blur == null) {
+            blur = 4;
+        }
+        if (opacity == null) {
+            opacity = 1;
+        }
+        if (dx == null) {
+            dx = 0;
+            dy = 2;
+        }
+        if (dy == null) {
+            dy = dx;
+        }
+        color = Snap.color(color);
+        return Snap.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', {
+            color: color,
+            dx: dx,
+            dy: dy,
+            blur: blur,
+            opacity: opacity
+        });
+    };
+    Snap.filter.shadow.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.grayscale
+     [ method ]
+     **
+     * Returns an SVG markup string for the grayscale filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.grayscale = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/>', {
+            a: 0.2126 + 0.7874 * (1 - amount),
+            b: 0.7152 - 0.7152 * (1 - amount),
+            c: 0.0722 - 0.0722 * (1 - amount),
+            d: 0.2126 - 0.2126 * (1 - amount),
+            e: 0.7152 + 0.2848 * (1 - amount),
+            f: 0.0722 - 0.0722 * (1 - amount),
+            g: 0.2126 - 0.2126 * (1 - amount),
+            h: 0.0722 + 0.9278 * (1 - amount)
+        });
+    };
+    Snap.filter.grayscale.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.sepia
+     [ method ]
+     **
+     * Returns an SVG markup string for the sepia filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.sepia = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/>', {
+            a: 0.393 + 0.607 * (1 - amount),
+            b: 0.769 - 0.769 * (1 - amount),
+            c: 0.189 - 0.189 * (1 - amount),
+            d: 0.349 - 0.349 * (1 - amount),
+            e: 0.686 + 0.314 * (1 - amount),
+            f: 0.168 - 0.168 * (1 - amount),
+            g: 0.272 - 0.272 * (1 - amount),
+            h: 0.534 - 0.534 * (1 - amount),
+            i: 0.131 + 0.869 * (1 - amount)
+        });
+    };
+    Snap.filter.sepia.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.saturate
+     [ method ]
+     **
+     * Returns an SVG markup string for the saturate filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.saturate = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feColorMatrix type="saturate" values="{amount}"/>', {
+            amount: 1 - amount
+        });
+    };
+    Snap.filter.saturate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.hueRotate
+     [ method ]
+     **
+     * Returns an SVG markup string for the hue-rotate filter
+     **
+     - angle (number) angle of rotation
+     = (string) filter representation
+    \*/
+    Snap.filter.hueRotate = function (angle) {
+        angle = angle || 0;
+        return Snap.format('<feColorMatrix type="hueRotate" values="{angle}"/>', {
+            angle: angle
+        });
+    };
+    Snap.filter.hueRotate.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.invert
+     [ method ]
+     **
+     * Returns an SVG markup string for the invert filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.invert = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+//        <feColorMatrix type="matrix" values="-1 0 0 0 1  0 -1 0 0 1  0 0 -1 0 1  0 0 0 1 0" color-interpolation-filters="sRGB"/>
+        return Snap.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: 1 - amount
+        });
+    };
+    Snap.filter.invert.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.brightness
+     [ method ]
+     **
+     * Returns an SVG markup string for the brightness filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.brightness = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}"/><feFuncG type="linear" slope="{amount}"/><feFuncB type="linear" slope="{amount}"/></feComponentTransfer>', {
+            amount: amount
+        });
+    };
+    Snap.filter.brightness.toString = function () {
+        return this();
+    };
+    /*\
+     * Snap.filter.contrast
+     [ method ]
+     **
+     * Returns an SVG markup string for the contrast filter
+     **
+     - amount (number) amount of filter (`0..1`)
+     = (string) filter representation
+    \*/
+    Snap.filter.contrast = function (amount) {
+        if (amount == null) {
+            amount = 1;
+        }
+        return Snap.format('<feComponentTransfer><feFuncR type="linear" slope="{amount}" intercept="{amount2}"/><feFuncG type="linear" slope="{amount}" intercept="{amount2}"/><feFuncB type="linear" slope="{amount}" intercept="{amount2}"/></feComponentTransfer>', {
+            amount: amount,
+            amount2: .5 - amount / 2
+        });
+    };
+    Snap.filter.contrast.toString = function () {
+        return this();
+    };
+});
+
+// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
+    var box = Snap._.box,
+        is = Snap.is,
+        firstLetter = /^[^a-z]*([tbmlrc])/i,
+        toString = function () {
+            return "T" + this.dx + "," + this.dy;
+        };
+    /*\
+     * Element.getAlign
+     [ method ]
+     **
+     * Returns shift needed to align the element relatively to given element.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string
+     > Usage
+     | el.transform(el.getAlign(el2, "top"));
+     * or
+     | var dy = el.getAlign(el2, "top").dy;
+    \*/
+    Element.prototype.getAlign = function (el, way) {
+        if (way == null && is(el, "string")) {
+            way = el;
+            el = null;
+        }
+        el = el || this.paper;
+        var bx = el.getBBox ? el.getBBox() : box(el),
+            bb = this.getBBox(),
+            out = {};
+        way = way && way.match(firstLetter);
+        way = way ? way[1].toLowerCase() : "c";
+        switch (way) {
+            case "t":
+                out.dx = 0;
+                out.dy = bx.y - bb.y;
+            break;
+            case "b":
+                out.dx = 0;
+                out.dy = bx.y2 - bb.y2;
+            break;
+            case "m":
+                out.dx = 0;
+                out.dy = bx.cy - bb.cy;
+            break;
+            case "l":
+                out.dx = bx.x - bb.x;
+                out.dy = 0;
+            break;
+            case "r":
+                out.dx = bx.x2 - bb.x2;
+                out.dy = 0;
+            break;
+            default:
+                out.dx = bx.cx - bb.cx;
+                out.dy = 0;
+            break;
+        }
+        out.toString = toString;
+        return out;
+    };
+    /*\
+     * Element.align
+     [ method ]
+     **
+     * Aligns the element relatively to given one via transformation.
+     * If no elements specified, parent `<svg>` container will be used.
+     - el (object) @optional alignment element
+     - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"`
+     = (object) this element
+     > Usage
+     | el.align(el2, "top");
+     * or
+     | el.align("middle");
+    \*/
+    Element.prototype.align = function (el, way) {
+        return this.transform("..." + this.getAlign(el, way));
+    };
+});
+
+return Snap;
+}));
diff --git a/web/pgadmin/misc/templates/explain/js/explain.js b/web/pgadmin/misc/templates/explain/js/explain.js
new file mode 100644
index 0000000..fd9a5e5
--- /dev/null
+++ b/web/pgadmin/misc/templates/explain/js/explain.js
@@ -0,0 +1,691 @@
+define (
+  'pgadmin.misc.explain',
+  ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'backbone', 'snap.svg'],
+  function($, _, S, pgAdmin, Backbone, Snap) {
+
+pgAdmin = pgAdmin || window.pgAdmin || {};
+var pgExplain = pgAdmin.Explain;
+
+// Snap.svg plug-in to write multitext as image name
+Snap.plugin(function (Snap, Element, Paper, glob) {
+  Paper.prototype.multitext = function (x, y, txt, max_width, attributes) {
+    var svg = Snap(),
+        abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+        isWordBroken = false,
+        temp = svg.text(0, 0, abc);
+
+    temp.attr(attributes);
+
+    /*
+     * Find letter width in pixels and
+     * index from where the text should be broken
+     */
+    var letter_width = temp.getBBox().width / abc.length,
+        word_break_index = Math.round((max_width / letter_width)) - 1;
+
+    svg.remove();
+
+    var words = txt.split(" "),
+        width_so_far = 0,
+        lines=[], curr_line = '',
+        /*
+         * Function to divide string into multiple lines
+         * and store them in an array if it size crosses
+         * the max-width boundary.
+         */
+        splitTextInMultiLine = function(leading, so_far, line) {
+          var l = line.length,
+              res = [];
+
+          if (l == 0)
+            return res;
+
+          if (so_far && (so_far + (l * letter_width) > max_width)) {
+            res.push(leading);
+            res = res.concat(splitTextInMultiLine('', 0, line));
+          } else if (so_far) {
+            res.push(leading + ' ' + line);
+          } else {
+            if (leading)
+                res.push(leading);
+            if (line.length > word_break_index + 1)
+                res.push(line.slice(0, word_break_index) + '-');
+            else
+                res.push(line);
+            res = res.concat(splitTextInMultiLine('', 0, line.slice(word_break_index)));
+          }
+
+          return res;
+        };
+
+    for (var i = 0; i < words.length; i++) {
+      var tmpArr = splitTextInMultiLine(
+            curr_line, width_so_far, words[i]
+          );
+
+      if (curr_line) {
+        lines = lines.slice(0, lines.length - 2);
+      }
+      lines = lines.concat(tmpArr);
+      curr_line = lines[lines.length - 1];
+      width_so_far = (curr_line.length * letter_width);
+    }
+
+    // Create multiple tspan for each string in array
+    var t = this.text(x,y,lines).attr(attributes);
+    t.selectAll("tspan:nth-child(n+2)").attr({
+      dy: "1.2em",
+      x: x
+    });
+    return t;
+  };
+});
+
+if (pgAdmin.Explain)
+    return pgAdmin.Explain;
+
+var pgExplain = pgAdmin.Explain = {
+   // Prefix path where images are stored
+   prefix: '{{ url_for('misc.static', filename='explain/img') }}/'
+};
+
+/*
+ * A map which is used to fetch the image to be drawn and
+ * text which will appear below it
+ */
+var imageMapper = {
+    "Aggregate" : {
+        "image":"ex_aggregate.png", "image_text":"Aggregate"
+    },
+    'Append' : {
+        "image":"ex_append.png","image_text":"Append"
+    },
+    "Bitmap Index Scan" : function(data) {
+        return {
+            "image":"ex_bmp_index.png", "image_text":data['Index Name']
+        };
+    },
+    "Bitmap Heap Scan" : function(data) {
+  return {"image":"ex_bmp_heap.png","image_text":data['Relation Name']};
+},
+"BitmapAnd" : {"image":"ex_bmp_and.png","image_text":"Bitmap AND"},
+"BitmapOr" : {"image":"ex_bmp_or.png","image_text":"Bitmap OR"},
+"CTE Scan" : {"image":"ex_cte_scan.png","image_text":"CTE Scan"},
+"Function Scan" : {"image":"ex_result.png","image_text":"Function Scan"},
+"Foreign Scan" : {"image":"ex_foreign_scan.png","image_text":"Foreign Scan"},
+"Gather" : {"image":"ex_gather_motion.png","image_text":"Gather"},
+"Group" : {"image":"ex_group.png","image_text":"Group"},
+"GroupAggregate": {"image":"ex_aggregate.png","image_text":"Group Aggregate"},
+"Hash" : {"image":"ex_hash.png","image_text":"Hash"},
+"Hash Join": function(data) {
+  if (!data['Join Type']) return {"image":"ex_join.png","image_text":"Join"};
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_hash_anti_join.png","image_text":"Hash Anti Join"};
+    case 'Semi': return {"image":"ex_hash_semi_join.png","image_text":"Hash Semi Join"};
+    default: return {"image":"ex_hash.png","image_text":String("Hash " + data['Join Type'] + " Join" )};
+  }
+},
+"HashAggregate" : {"image":"ex_aggregate.png","image_text":"Hash Aggregate"},
+"Index Only Scan" : function(data) {
+  return {"image":"ex_index_only_scan.png","image_text":data['Index Name']};
+},
+"Index Scan" : function(data) {
+  return {"image":"ex_index_scan.png","image_text":data['Index Name']};
+},
+"Index Scan Backword" : {"image":"ex_index_scan.png","image_text":"Index Backward Scan"},
+"Limit" : {"image":"ex_limit.png","image_text":"Limit"},
+"LockRows" : {"image":"ex_lock_rows.png","image_text":"Lock Rows"},
+"Materialize" : {"image":"ex_materialize.png","image_text":"Materialize"},
+"Merge Append": {"image":"ex_merge_append.png","image_text":"Merge Append"},
+"Merge Join": function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_merge_anti_join.png","image_text":"Merge Anti Join"};
+    case 'Semi': return {"image":"ex_merge_semi_join.png","image_text":"Merge Semi Join"};
+    default: return {"image":"ex_merge.png","image_text":String("Merge " + data['Join Type'] + " Join" )};
+  }
+},
+"ModifyTable" : function(data) {
+  switch (data['Operaton']) {
+    case "insert": return { "image":"ex_insert.png",
+                            "image_text":"Insert"
+                           };
+    case "update": return {"image":"ex_update.png","image_text":"Update"};
+    case "Delete": return {"image":"ex_delete.png","image_text":"Delete"};
+  }
+},
+'Nested Loop' : function(data) {
+  switch(data['Join Type']) {
+    case 'Anti': return {"image":"ex_nested_loop_anti_join.png","image_text":"Nested Loop Anti Join"};
+    case 'Semi': return {"image":"ex_nested_loop_semi_join.png","image_text":"Nested Loop Semi Join"};
+    default: return {"image":"ex_nested.png","image_text":"Nested Loop " + data['Join Type'] + " Join"};
+  }
+},
+"Recursive Union" : {"image":"ex_recursive_union.png","image_text":"Recursive Union"},
+"Result" : {"image":"ex_result.png","image_text":"Result"},
+"Sample Scan" : {"image":"ex_scan.png","image_text":"Sample Scan"},
+"Scan" : {"image":"ex_scan.png","image_text":"Scan"},
+"Seek" : {"image":"ex_seek.png","image_text":"Seek"},
+"SetOp" : function(data) {
+  var strategy = data['Strategy'],
+      command = data['Command'];
+
+  if(strategy == "Hashed") {
+    if(command.startsWith("Intersect")) {
+      if(command == "Intersect All")
+        return {"image":"ex_hash_setop_intersect_all.png","image_text":"Hashed Intersect All"};
+      return {"image":"ex_hash_setop_intersect.png","image_text":"Hashed Intersect"};
+    }
+    else if (command.startsWith("Except")) {
+      if(command == "Except All")
+        return {"image":"ex_hash_setop_except_all.png","image_text":"Hashed Except All"};
+      return {"image":"ex_hash_setop_except.png","image_text":"Hash Except"};
+    }
+    return {"image":"ex_hash_setop_unknown.png","image_text":"Hashed SetOp Unknown"};
+  }
+  return {"image":"ex_setop.png","image_text":"SetOp"};
+},
+"Seq Scan": function(data) {
+  return {"image":"ex_scan.png","image_text":data['Relation Name']};
+},
+"Subquery Scan" : {"image":"ex_subplan.png","image_text":"SubQuery Scan"},
+"Sort" : {"image":"ex_sort.png","image_text":"Sort"},
+"Tid Scan" : {"image":"ex_tid_scan.png","image_text":"Tid Scan"},
+"Unique" : {"image":"ex_unique.png","image_text":"Unique"},
+"Values Scan" : {"image":"ex_values_scan.png","image_text":"Values Scan"},
+"WindowAgg" : {"image":"ex_window_aggregate.png","image_text":"Window Aggregate"},
+"WorkTable Scan" : {"image":"ex_worktable_scan.png","image_text":"WorkTable Scan"},
+"Undefined" : {"image":"ex_unknown.png","image_text":"Undefined"},
+}
+
+// Some predefined constants used to calculate image location and its border
+var pWIDTH = pHEIGHT = 100.
+    IMAGE_WIDTH = IMAGE_HEIGHT = 50;
+var offsetX = 200,
+    offsetY = 60;
+var ARROW_WIDTH = 10,
+    ARROW_HEIGHT = 10,
+    DEFAULT_ARROW_SIZE = 2;
+var TXT_ALLIGN = 5,
+    TXT_SIZE = "15px";
+var TOTAL_WIDTH = undefined,
+    TOTAL_HEIGHT = undefined;
+var xMargin = 25,
+    yMargin = 25;
+var MIN_ZOOM_FACTOR = 0.01,
+    MAX_ZOOM_FACTOR = 2,
+    INIT_ZOOM_FACTOR = 1;
+    ZOOM_RATIO = 0.05;
+
+
+// Backbone model for each plan property of input JSON object
+var PlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plans": [],
+        level: [],
+        "image": undefined,
+        "image_text": undefined,
+        xpos: undefined,
+        ypos: undefined,
+        width: pWIDTH,
+        height: pHEIGHT
+    },
+    parse: function(data) {
+        var idx = 1,
+            lvl = data.level = data.level || [idx],
+            plans = [],
+            node_type = data['Node Type'],
+            // Calculating relative xpos of current node from top node
+            xpos = data.xpos = data.xpos - pWIDTH,
+            // Calculating relative ypos of current node from top node
+            ypos = data.ypos,
+            maxChildWidth = 0;
+
+        data['width'] = pWIDTH;
+        data['height'] = pHEIGHT;
+
+        /*
+         * calculating xpos, ypos, width and height if current node is a subplan
+         */
+        if (data['Parent Relationship'] === "SubPlan") {
+            data['width'] += (xMargin * 2) + (xMargin / 2);
+            data['height'] += (yMargin * 2);
+            data['ypos'] += yMargin;
+            xpos -= xMargin;
+            ypos += yMargin;
+        }
+
+        if(node_type.startsWith("(slice"))
+            node_type = node_type.substring(0,7);
+
+        // Get the image information for current node
+        var mapperObj = (_.isFunction(imageMapper[node_type]) &&
+                imageMapper[node_type].apply(undefined, [data])) ||
+                imageMapper[node_type] || 'Undefined';
+
+        data["image"] = mapperObj["image"];
+        data["image_text"] = mapperObj["image_text"];
+
+        // Start calculating xpos, ypos, width and height for child plans if any
+        if ('Plans' in data) {
+
+            data['width'] += offsetX;
+
+            _.each(data['Plans'], function(p) {
+                var level = _.clone(lvl),
+                    plan = new PlanModel();
+
+                level.push(idx);
+                plan.set(plan.parse(_.extend(
+                    p, {
+                        "level": level,
+                        xpos: xpos - offsetX,
+                        ypos: ypos
+                    })));
+
+                if (maxChildWidth < plan.get('width')) {
+                    maxChildWidth = plan.get('width');
+                }
+
+                var childHeight = plan.get('height');
+
+                if (idx !== 1) {
+                    data['height'] = data['height'] + childHeight + offsetY;
+                } else if (childHeight > data['height']) {
+                    data['height'] = childHeight;
+                }
+                ypos += childHeight + offsetY;
+
+                plans.push(plan);
+                idx++;
+            });
+        }
+
+        // Final Width and Height of current node
+        data['width'] += maxChildWidth;
+        data['Plans'] = plans;
+
+        return data;
+    },
+
+    /*
+     * Required to parse and include non-default params of
+     * plan into backbone model
+     */
+    toJSON: function(non_recursive) {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (non_recursive) {
+            delete res['Plans'];
+      } else {
+            var plans = [];
+            _.each(res['Plans'], function(p) {
+              plans.push(p.toJSON());
+            });
+            res['Plans'] = plans;
+      }
+      return res;
+    },
+
+    // Draw an arrow to parent node
+    drawPolyLine: function(g, startX, startY, endX, endY, opts, arrowOpts) {
+      // Calculate end point of first starting straight line (startx1, starty1)
+      // Calculate start point of 2nd straight line (endx1, endy1)
+      var midX1 = startX + ((endX - startX) / 3),
+          midX2 = startX + (2 * ((endX - startX) / 3));
+
+      //create arrow head
+      var arrow = g.polygon(
+                    [0, ARROW_HEIGHT,
+                    (ARROW_WIDTH / 2),ARROW_HEIGHT,
+                    (ARROW_HEIGHT / 4), 0,
+                    0, ARROW_WIDTH]
+                    ).transform("r90");
+      var marker = arrow.marker(
+                         0, 0, ARROW_WIDTH, ARROW_HEIGHT, 0, (ARROW_WIDTH / 2)
+                         ).attr(arrowOpts);
+
+      // First straight line
+      g.line(
+        startX, startY, midX1, startY
+        ).attr(opts);
+
+      // Diagonal line
+      g.line(
+        midX1-1, startY, midX2, endY
+        ).attr(opts);
+
+      // Last straight line
+      var line = g.line(
+                   midX2, endY, endX, endY
+                   ).attr(opts);
+      line.attr({markerEnd: marker})
+    },
+
+    // Draw image, its name and its tooltip
+    draw: function(s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer) {
+        var g = s.g();
+        var currentXpos = xpos + this.get('xpos') ,
+            currentYpos = ypos + this.get('ypos'),
+            isSubPlan = (this.get('Parent Relationship') === "SubPlan");
+
+        // Draw the subplan rectangle
+        if (isSubPlan) {
+          g.rect(
+            currentXpos - this.get('width') + pWIDTH + xMargin,
+            currentYpos - yMargin,
+            this.get('width') - xMargin,
+            this.get('height'), 5
+          ).attr({
+              stroke: '#444444',
+              'strokeWidth': 1.2,
+              fill: 'gray',
+              fillOpacity: 0.2
+          });
+
+          //provide subplan name
+          var text = g.text(
+            currentXpos  + pWIDTH - ( this.get('width') / 2) - xMargin,
+            currentYpos + pHEIGHT  - (this.get('height') / 2) - yMargin,
+            this.get('Subplan Name')
+          ).attr({
+            fontSize: TXT_SIZE, "text-anchor":"start",
+            fill: 'red'
+          });
+        }
+
+        // Draw the actual image for current node
+        var image = g.image(
+            pgExplain.prefix + this.get('image'),
+            currentXpos + (pWIDTH - IMAGE_WIDTH) / 2,
+            currentYpos + (pHEIGHT - IMAGE_HEIGHT) / 2,
+            IMAGE_WIDTH,
+            IMAGE_HEIGHT
+        );
+
+        // Draw tooltip
+        var image_data = this.toJSON();
+        image.mouseover(function(evt){
+
+          // Empty the tooltip content if it has any and add new data
+          toolTipContainer.empty();
+          var tooltip = $('<table></table>',{
+                           class: "pgadmin-tooltip-table"
+                        }).appendTo(toolTipContainer);
+          _.each(image_data, function(value,key) {
+            if(key !== 'image' && key !== 'Plans' &&
+               key !== 'level' && key !== 'image' &&
+               key !== 'image_text' && key !== 'xpos' &&
+               key !== 'ypos' && key !== 'width' &&
+               key !== 'height') {
+              tooltip.append( '<tr><td class="label explain-tooltip">' + key + '</td><td class="label explain-tooltip-val">' + value + '</td></tr>' );
+            };
+          });
+
+          var zoomFactor = graphContainer.data('zoom-factor');
+
+          // Calculate co-ordinates for tooltip
+          var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft());
+          var toolTipY = ((currentYpos + pHEIGHT) * zoomFactor - graphContainer.scrollTop());
+
+          // Recalculate x.y if tooltip is going out of screen
+          if(graphContainer.width() < (toolTipX + toolTipContainer[0].clientWidth))
+            toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH*zoomFactor));
+          //if(document.children[0].clientHeight < (toolTipY + toolTipContainer[0].clientHeight))
+          if(graphContainer.height() < (toolTipY + toolTipContainer[0].clientHeight))
+            toolTipY -= (toolTipContainer[0].clientHeight + ((pHEIGHT/2)*zoomFactor));
+
+          toolTipX = toolTipX < 0 ? 0 : (toolTipX);
+          toolTipY = toolTipY < 0 ? 0 : (toolTipY);
+
+          // Show toolTip at respective x,y coordinates
+          toolTipContainer.css({'opacity': '0.8'});
+          toolTipContainer.css('left', toolTipX);
+          toolTipContainer.css( 'top', toolTipY);
+        });
+
+        // Remove tooltip when mouse is out from node's area
+        image.mouseout(function() {
+          toolTipContainer.empty();
+          toolTipContainer.css({'opacity': '0'});
+          toolTipContainer.css('left', 0);
+          toolTipContainer.css( 'top', 0);
+        });
+
+        // Draw text below the node
+        var node_label = (this.get('Schema') == undefined ?
+                            this.get('image_text') :
+                            this.get('Schema')+"."+this.get('image_text'));
+        var label = g.g();
+        g.multitext(
+          currentXpos + (pWIDTH / 2),
+          currentYpos + pHEIGHT - TXT_ALLIGN,
+          node_label,
+          150,
+          {"font-size": TXT_SIZE ,"text-anchor":"middle"}
+        );
+
+        // Draw Arrow to parent only its not the first node
+        if (!_.isUndefined(pYpos)) {
+            var startx = currentXpos + pWIDTH;
+            var starty = currentYpos + (pHEIGHT / 2);
+            var endx = pXpos - ARROW_WIDTH;
+            var endy = pYpos + (pHEIGHT / 2);
+            var start_cost = this.get("Startup Cost"),
+                total_cost = this.get("Total Cost");
+            var arrow_size = DEFAULT_ARROW_SIZE;
+            // Calculate arrow width according to cost of a particular plan
+            if(start_cost != undefined && total_cost != undefined) {
+              var arrow_size = Math.round(Math.log((start_cost+total_cost)/2 + start_cost));
+              arrow_size = arrow_size < 1 ? 1 : arrow_size > 10 ? 10 : arrow_size;
+            }
+
+
+            var arrow_view_box = [0, 0, 2*ARROW_WIDTH, 2*ARROW_HEIGHT];
+            var opts = {stroke: "#000000", strokeWidth: arrow_size + 1},
+                subplanOpts = {stroke: "#866486", strokeWidth: arrow_size + 1},
+                arrowOpts = {viewBox: arrow_view_box.join(" ")};
+
+            // Draw an arrow from current node to its parent
+            this.drawPolyLine(
+              g, startx, starty, endx, endy,
+              isSubPlan ? subplanOpts : opts, arrowOpts
+            );
+        }
+
+        var plans = this.get('Plans');
+
+        // Draw nodes for current plan's children
+        _.each(plans, function(p) {
+            p.draw(s, xpos, ypos, currentXpos, currentYpos, graphContainer, toolTipContainer);
+        });
+    }
+});
+
+// Main backbone model to store JSON object
+var MainPlanModel = Backbone.Model.extend({
+    defaults: {
+        "Plan": undefined,
+        xpos: 0,
+        ypos: 0,
+    },
+    initialize: function() {
+        this.set("Plan", new PlanModel());
+    },
+
+    // Parse the JSON data and fetch its children plans
+    parse: function(data) {
+        if (data && 'Plan' in data) {
+           var plan = this.get("Plan");
+           plan.set(
+             plan.parse(
+               _.extend(
+                 data['Plan'], {
+                   xpos: 0,
+                   ypos: 0
+                 })));
+
+           data['xpos'] = 0;
+           data['ypos'] = 0;
+           data['width'] = plan.get('width') + (xMargin * 2);
+           data['height'] = plan.get('height') + (yMargin * 2);
+
+           delete data['Plan'];
+        }
+
+      return data;
+    },
+    toJSON: function() {
+      var res = Backbone.Model.prototype.toJSON.apply(this, arguments);
+
+      if (res.Plan) {
+        res.Plan = res.Plan.toJSON();
+      }
+
+      return res;
+    },
+    draw: function(s, xpos, ypos, graphContainer, toolTipContainer) {
+        var g = s.g();
+
+        //draw the border
+        g.rect(
+	        0, 0, this.get('width') - 10, this.get('height') - 10, 5
+	    ).attr({
+            stroke: '#FFEBCD', 'strokeWidth': 1.2,
+            fill: '#FFF8DC', fillOpacity: 0.5
+        });
+
+        //Fetch total width, height
+        TOTAL_WIDTH = this.get('width');
+        TOTAL_HEIGHT = this.get('height');
+        var plan = this.get('Plan');
+
+        //Draw explain graph
+        plan.draw(g, xpos, ypos, undefined, undefined, graphContainer, toolTipContainer);
+    }
+});
+
+// Parse and draw full graphical explain
+_.extend(
+    pgExplain, {
+        // Assumption container is a jQuery object
+        DrawJSONPlan: function(container, plan) {
+          var my_plans = [];
+          container.empty();
+          var curr_zoom_factor = 1.0;
+
+          var zoomArea =$('<div></div>', {
+                class: 'pg-explain-zoom-area btn-group',
+                role: 'group'
+                }).appendTo(container),
+              zoomInBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom in'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-search-plus'
+                  })),
+              zoomToNormal = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom to original'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>',{
+                    class: 'fa fa-arrows-alt'
+                  }))
+              zoomOutBtn = $('<button></button>', {
+                class: 'btn pg-explain-zoom-btn badge',
+                title: 'Zoom out'
+                }).appendTo(zoomArea).append(
+                  $('<i></i>', {
+                    class: 'fa fa-search-minus'
+                  }));
+
+          // Main div to be drawn all images on
+          var planDiv = $('<div></div>',
+                           {class: "pgadmin-explain-container"}
+                         ).appendTo(container),
+              // Div to draw tool-tip on
+              toolTip = $('<div></div>',
+                           {id: "toolTip",
+                           class: "pgadmin-explain-tooltip"
+                           }
+                         ).appendTo(container);
+          toolTip.empty();
+          planDiv.data('zoom-factor', curr_zoom_factor);
+
+          var w = 0, h = 0,
+              x = xMargin, h = yMargin;
+
+          _.each(plan, function(p) {
+            var main_plan = new MainPlanModel();
+
+            // Parse JSON data to backbone model
+            main_plan.set(main_plan.parse(p));
+            w = main_plan.get('width');
+            h = main_plan.get('height');
+
+            var s = Snap(w, h),
+                $svg = $(s.node).detach();
+            planDiv.append($svg);
+            main_plan.draw(s, w - xMargin, yMargin, planDiv, toolTip);
+
+            var initPanelWidth = planDiv.width(),
+                initPanelHeight = planDiv.height();
+
+             /*
+              * Scale graph in case its width is bigger than panel width
+              * in which the graph is displayed
+              */
+            if(initPanelWidth < w) {
+              var width_ratio = initPanelWidth / w;
+
+              curr_zoom_factor = width_ratio;
+              curr_zoom_factor = curr_zoom_factor < MIN_ZOOM_FACTOR ? MIN_ZOOM_FACTOR : curr_zoom_factor;
+              curr_zoom_factor = curr_zoom_factor > INIT_ZOOM_FACTOR ? INIT_ZOOM_FACTOR : curr_zoom_factor;
+
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+            }
+
+            zoomInBtn.on('click', function(e){
+              curr_zoom_factor = ((curr_zoom_factor + ZOOM_RATIO) > MAX_ZOOM_FACTOR) ? MAX_ZOOM_FACTOR : (curr_zoom_factor + ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomInBtn.blur();
+            });
+
+            zoomOutBtn.on('click', function(e) {
+              curr_zoom_factor = ((curr_zoom_factor - ZOOM_RATIO) < MIN_ZOOM_FACTOR) ? MIN_ZOOM_FACTOR : (curr_zoom_factor - ZOOM_RATIO);
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomOutBtn.blur();
+            });
+
+            zoomToNormal.on('click', function(e) {
+              curr_zoom_factor = INIT_ZOOM_FACTOR;
+              var zoomInMatrix = new Snap.matrix();
+              zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor);
+
+              $svg.find('g').first().attr({transform: zoomInMatrix});
+              $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor});
+              planDiv.data('zoom-factor', curr_zoom_factor);
+              zoomToNormal.blur();
+            });
+          });
+
+        }
+    });
+
+    return pgExplain;
+});
diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
index ddb9d8f..1366017 100644
--- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html
+++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
@@ -66,10 +66,53 @@ body {
             </button>
             <ul class="dropdown-menu dropdown-menu">
               <li>
+                <a id="btn-explain" href="#">
+                  <span>{{ _('Explain') }}</span>
+                </a>
+              </li>
+              <li>
+                <a id="btn-explain-analyze" href="#">
+                    <span>{{ _('Explain analyze') }}</span>
+                </a>
+              </li>
+              <li class="divider"></li>
+              <li class="dropdown-submenu dropdown-submenu">
+                <a href="#">{{ _('Explain Options') }}</a>
+                <ul class="dropdown-menu">
+                  <li>
+                    <a id="btn-explain-verbose" href="#" class="noclose">
+                      <i class="explain-verbose fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Verbose') }} </span>
+                    </a>
+                  </li>
+                  <li>
+                    <a id="btn-explain-costs" href="#" class="noclose">
+                      <i class="explain-costs fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Costs') }} </span>
+                    </a>
+                  </li>
+                  <li>
+                    <a id="btn-explain-buffers" href="#" class="noclose">
+                      <i class="explain-buffers fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Buffers') }} </span>
+                    </a>
+                  </li>
+                  <li>
+                    <a id="btn-explain-timing" href="#" class="noclose">
+                      <i class="explain-timing fa fa-check visibility-hidden" aria-hidden="true"></i>
+                      <span> {{ _('Timing') }} </span>
+                    </a>
+                  </li>
+                </ul>
+              </li>
+              <li class="divider"></li>
+              <li>
                 <a id="btn-auto-commit" href="#">
                     <i class="auto-commit fa fa-check" aria-hidden="true"></i>
                     <span> {{ _('Auto-Commit') }} </span>
                 </a>
+              </li>
+              <li>
                 <a id="btn-auto-rollback" href="#">
                     <i class="auto-rollback fa fa-check visibility-hidden" aria-hidden="true"></i>
                     <span> {{ _('Auto-Rollback') }} </span>
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index 3917f3d..8716090 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -17,7 +17,8 @@ from flask import Response, url_for, render_template, session, request
 from flask.ext.babel import gettext
 from flask.ext.security import login_required
 from pgadmin.utils import PgAdminModule
-from pgadmin.utils.ajax import make_json_response, bad_request, success_return, internal_server_error
+from pgadmin.utils.ajax import make_json_response, bad_request, \
+     success_return, internal_server_error
 from pgadmin.utils.driver import get_driver
 from config import PG_DEFAULT_DRIVER
 from pgadmin.tools.sqleditor.command import QueryToolCommand
@@ -66,6 +67,42 @@ class SqlEditorModule(PgAdminModule):
             category_label=gettext('Display')
             )
 
+        self.explain_verbose = self.preference.register(
+            'Explain Options', 'explain_verbose',
+            gettext("Verbose"), 'boolean', False,
+            category_label=gettext('Explain Options')
+            )
+
+        self.explain_costs = self.preference.register(
+            'Explain Options', 'explain_costs',
+            gettext("Costs"), 'boolean', False,
+            category_label=gettext('Explain Options')
+            )
+
+        self.explain_buffers = self.preference.register(
+            'Explain Options', 'explain_buffers',
+            gettext("Buffers"), 'boolean', False,
+            category_label=gettext('Explain Options')
+            )
+
+        self.explain_timing = self.preference.register(
+            'Explain Options', 'explain_timing',
+            gettext("Timing"), 'boolean', False,
+            category_label=gettext('Explain Options')
+            )
+
+        self.auto_commit = self.preference.register(
+            'Options', 'auto_commit',
+            gettext("Auto-Commit"), 'boolean', True,
+            category_label=gettext('Options')
+            )
+
+        self.auto_rollback = self.preference.register(
+            'Options', 'auto_rollback',
+            gettext("Auto-Rollback"), 'boolean', False,
+            category_label=gettext('Options')
+            )
+
 blueprint = SqlEditorModule(MODULE_NAME, __name__, static_url_path='/static')
 
 
@@ -277,6 +314,43 @@ def start_query_tool(trans_id):
         )
 
 
[email protected]('/query_tool/preferences', methods=["GET", "PUT"])
+@login_required
+def get_preferences():
+    """
+        This method is used to get/put explain options from/to preferences
+    """
+    if request.method == 'GET':
+        return make_json_response(
+            data={
+                'explain_verbose': blueprint.explain_verbose.get(),
+                'explain_costs': blueprint.explain_costs.get(),
+                'explain_buffers': blueprint.explain_buffers.get(),
+                'explain_timing': blueprint.explain_timing.get(),
+                'auto_commit': blueprint.auto_commit.get(),
+                'auto_rollback': blueprint.auto_rollback.get()
+            }
+        )
+    else:
+        data = None
+        if request.data:
+            data = json.loads(request.data.decode())
+        else:
+            data = request.args or request.form
+        for k,v in data.items():
+            v = bool(v)
+            if k == 'explain_verbose':
+                blueprint.explain_verbose.set(v)
+            elif k == 'explain_costs':
+                blueprint.explain_costs.set(v)
+            elif k == 'explain_buffers':
+                blueprint.explain_buffers.set(v)
+            elif k == 'explain_timing':
+                blueprint.explain_timing.set(v)
+
+        return success_return()
+
+
 @blueprint.route('/poll/<int:trans_id>', methods=["GET"])
 @login_required
 def poll(trans_id):
@@ -739,6 +813,9 @@ def set_auto_commit(trans_id):
         # Call the set_auto_commit method of transaction object
         trans_obj.set_auto_commit(auto_commit)
 
+        # Set Auto commit in preferences
+        blueprint.auto_commit.set(bool(auto_commit))
+
         # As we changed the transaction object we need to
         # restore it and update the session variable.
         session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
@@ -774,6 +851,9 @@ def set_auto_rollback(trans_id):
         # Call the set_auto_rollback method of transaction object
         trans_obj.set_auto_rollback(auto_rollback)
 
+        # Set Auto Rollback in preferences
+        blueprint.auto_rollback.set(bool(auto_rollback))
+
         # As we changed the transaction object we need to
         # restore it and update the session variable.
         session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index dee3ea1..6db04cb 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -236,3 +236,10 @@
 .CodeMirror-foldgutter-folded:after {
   content: "\25B6";
 }
+
+
+.sql-editor-explain {
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index ee533a1..a780809 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -1,10 +1,10 @@
 define(
-  ['jquery', 'underscore', 'alertify', 'pgadmin', 'backbone', 'backgrid', 'codemirror',
-   'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection', 'codemirror/addon/selection/active-line',
+  ['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', 'pgadmin.misc.explain',
+   'backbone', 'backgrid', 'codemirror', 'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
    'codemirror/addon/fold/foldgutter', 'codemirror/addon/fold/foldcode', 'codemirror/addon/fold/pgadmin-sqlfoldcode',
-   'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator', 'backgrid.filter',
-   'bootstrap', 'pgadmin.browser', 'wcdocker'],
-  function($, _, alertify, pgAdmin, Backbone, Backgrid, CodeMirror) {
+   'codemirror/addon/selection/active-line', 'backgrid.select.all', 'backbone.paginator', 'backgrid.paginator',
+   'backgrid.filter', 'bootstrap', 'pgadmin.browser', 'wcdocker'],
+  function($, _, S, alertify, pgAdmin, pgExplain, Backbone, Backgrid, CodeMirror) {
 
     // Some scripts do export their object in the window only.
     // Generally the one, which do no have AMD support.
@@ -161,6 +161,12 @@ define(
         "click #btn-auto-rollback": "on_auto_rollback",
         "click #btn-clear-history": "on_clear_history",
         "click .noclose": 'do_not_close_menu',
+        "click #btn-explain": "on_explain",
+        "click #btn-explain-analyze": "on_explain_analyze",
+        "click #btn-explain-verbose": "on_explain_verbose",
+        "click #btn-explain-costs": "on_explain_costs",
+        "click #btn-explain-buffers": "on_explain_buffers",
+        "click #btn-explain-timing": "on_explain_timing",
         "change .limit": "on_limit_change"
       },
 
@@ -205,7 +211,6 @@ define(
           isPrivate: true
         });
 
-        //sql_panel.load(main_docker);
         sql_panel.load(main_docker);
         var sql_panel_obj = main_docker.addPanel('sql_panel', wcDocker.DOCK.TOP);
 
@@ -247,7 +252,7 @@ define(
           height:'100%',
           isCloseable: false,
           isPrivate: true,
-          content: '<div class="sql-editor-explian"></div>'
+          content: '<div class="sql-editor-explain"></div>'
         })
 
         var messages = new pgAdmin.Browser.Panel({
@@ -283,6 +288,87 @@ define(
         self.history_panel = main_docker.addPanel('history', wcDocker.DOCK.STACKED, self.data_output_panel);
 
         self.render_history_grid();
+
+        // Get auto-rollback/auto-commit and explain options from preferences
+        self.get_preferences();
+      },
+
+      /*
+       * This function get explain options and auto rollback/auto commit
+       * values from preferences
+       */
+      get_preferences: function() {
+        $.ajax({
+          url: "{{ url_for('sqleditor.index') }}" + "query_tool/preferences" ,
+          method: 'GET',
+          async: false,
+          success: function(res) {
+            if (res.data) {
+              self.explain_verbose = res.data.explain_verbose;
+              self.explain_costs = res.data.explain_costs;
+              self.explain_buffers = res.data.explain_buffers;
+              self.explain_timing = res.data.explain_timing;
+              self.auto_commit = res.data.auto_commit;
+              self.auto_rollback = res.data.auto_rollback;
+            }
+            else {
+              self.explain_verbose = false;
+              self.explain_costs = false;
+              self.explain_buffers = false;
+              self.explain_timing = false;
+              self.auto_commit = true;
+              self.auto_rollback = false;
+            }
+          },
+          error: function(e) {
+            self.explain_verbose = false;
+            self.explain_costs = false;
+            self.explain_buffers = false;
+            self.explain_timing = false;
+            self.auto_commit = true;
+            self.auto_rollback = false;
+          }
+        });
+
+        // Set Auto-commit and auto-rollback on query editor
+        if (self.auto_commit &&
+            $('.auto-commit').hasClass('visibility-hidden') === true)
+          $('.auto-commit').removeClass('visibility-hidden');
+        else {
+          $('.auto-commit').addClass('visibility-hidden');
+        }
+        if (self.auto_rollback &&
+            $('.auto-rollback').hasClass('visibility-hidden') === true)
+          $('.auto-rollback').removeClass('visibility-hidden');
+        else {
+          $('.auto-rollback').addClass('visibility-hidden');
+        }
+
+        // Set explain options on query editor
+        if (self.explain_verbose &&
+            $('.explain-verbose').hasClass('visibility-hidden') === true)
+          $('.explain-verbose').removeClass('visibility-hidden');
+        else {
+          $('.explain-verbose').addClass('visibility-hidden');
+        }
+        if (self.explain_costs &&
+            $('.explain-costs').hasClass('visibility-hidden') === true)
+          $('.explain-costs').removeClass('visibility-hidden');
+        else {
+          $('.explain-costs').addClass('visibility-hidden');
+        }
+        if (self.explain_buffers &&
+            $('.explain-buffers').hasClass('visibility-hidden') === true)
+            $('.explain-buffers').removeClass('visibility-hidden');
+        else {
+            $('.explain-buffers').addClass('visibility-hidden');
+        }
+        if (self.explain_timing &&
+            $('.explain-timing').hasClass('visibility-hidden') === true)
+            $('.explain-timing').removeClass('visibility-hidden');
+        else {
+            $('.explain-timing').addClass('visibility-hidden');
+        }
       },
 
       /* This function is responsible to create and render the
@@ -639,6 +725,79 @@ define(
             self.handler
         );
       },
+
+      // Callback function for explain button click.
+      on_explain: function() {
+        var self = this;
+
+        // Trigger the explain signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain analyze button click.
+      on_explain_analyze: function() {
+        var self = this;
+
+        // Trigger the explain analyze signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-analyze',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "verbose" button click
+      on_explain_verbose: function() {
+        var self = this;
+
+        // Trigger the explain "verbose" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-verbose',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "costs" button click
+      on_explain_costs: function() {
+        var self = this;
+
+        // Trigger the explain "costs" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-costs',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "buffers" button click
+      on_explain_buffers: function() {
+        var self = this;
+
+        // Trigger the explain "buffers" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-buffers',
+            self,
+            self.handler
+        );
+      },
+
+      // Callback function for explain option "timing" button click
+      on_explain_timing: function() {
+        var self = this;
+
+        // Trigger the explain "timing" signal to the SqlEditorController class
+        self.handler.trigger(
+            'pgadmin-sqleditor:button:explain-timing',
+            self,
+            self.handler
+        );
+      },
+
       do_not_close_menu: function(ev) {
         ev.stopPropagation();
       }
@@ -671,6 +830,10 @@ define(
           self.items_per_page = 25;
           self.rows_affected = 0;
           self.marked_line_no = 0;
+          self.explain_verbose = false;
+          self.explain_costs = false;
+          self.explain_buffers = false;
+          self.explain_timing = false;
 
           // We do not allow to call the start multiple times.
           if (self.gridView)
@@ -703,6 +866,12 @@ define(
           self.on('pgadmin-sqleditor:button:download', self._download, self);
           self.on('pgadmin-sqleditor:button:auto_rollback', self._auto_rollback, self);
           self.on('pgadmin-sqleditor:button:auto_commit', self._auto_commit, self);
+          self.on('pgadmin-sqleditor:button:explain', self._explain, self);
+          self.on('pgadmin-sqleditor:button:explain-analyze', self._explain_analyze, self);
+          self.on('pgadmin-sqleditor:button:explain-verbose', self._explain_verbose, self);
+          self.on('pgadmin-sqleditor:button:explain-costs', self._explain_costs, self);
+          self.on('pgadmin-sqleditor:button:explain-buffers', self._explain_buffers, self);
+          self.on('pgadmin-sqleditor:button:explain-timing', self._explain_timing, self);
 
           if (self.is_query_tool) {
             self.gridView.query_tool_obj.refresh();
@@ -743,6 +912,7 @@ define(
                 self.gridView.query_tool_obj.setValue(res.data.sql);
                 self.query = res.data.sql;
 
+
                 /* If filter is applied then remove class 'btn-default'
                  * and add 'btn-warning' to change the colour of the button.
                  */
@@ -880,6 +1050,7 @@ define(
           self.cell_selected = false;
           self.selected_model = null;
           self.changedModels = [];
+          $('.sql-editor-explain').empty();
 
           // Stop listening to all the events
           if (self.collection) {
@@ -948,10 +1119,26 @@ define(
           var message = 'Total query runtime: ' + self.total_time + '\n' + self.rows_affected + ' rows retrieved.';
           $('.sql-editor-message').text(message);
 
-          // Add the data to the collection and render the grid.
-          self.collection.add(data.result, {parse: true});
-          self.gridView.render_grid(self.collection, self.columns);
-          self.gridView.data_output_panel.focus();
+          /* Add the data to the collection and render the grid.
+           * In case of Explain draw the graph on explain panel
+           * and add json formatted data to collection and render.
+           */
+          var explain_data_array = [];
+          if(data.result &&
+              'QUERY PLAN' in data.result[0] &&
+              _.isObject(data.result[0]['QUERY PLAN'])) {
+              var explain_data = {'QUERY PLAN' : JSON.stringify(data.result[0]['QUERY PLAN'], null, 2)};
+              explain_data_array.push(explain_data);
+              self.gridView.explain_panel.focus();
+              pgExplain.DrawJSONPlan($('.sql-editor-explain'), data.result[0]['QUERY PLAN']);
+              self.collection.add(explain_data_array, {parse: true});
+              self.gridView.render_grid(self.collection, self.columns);
+          }
+          else {
+            self.collection.add(data.result, {parse: true});
+            self.gridView.render_grid(self.collection, self.columns);
+            self.gridView.data_output_panel.focus();
+          }
 
           // Hide the loading icon
           self.trigger('pgadmin-sqleditor:loading-icon:hide');
@@ -1700,16 +1887,11 @@ define(
 
         // This function will fetch the sql query from the text box
         // and execute the query.
-        _execute: function () {
+        _execute: function (explain_prefix) {
           var self = this,
               sql = '',
               history_msg = '';
 
-          self.trigger(
-            'pgadmin-sqleditor:loading-icon:show',
-            '{{ _('Initializing query execution.') }}'
-          );
-
           /* If code is selected in the code mirror then execute
            * the selected part else execute the complete code.
            */
@@ -1719,6 +1901,17 @@ define(
           else
             sql = self.gridView.query_tool_obj.getValue();
 
+          // If it is an empty query, do nothing.
+          if (sql.length <= 0) return;
+
+          self.trigger(
+            'pgadmin-sqleditor:loading-icon:show',
+            '{{ _('Initializing the query execution!') }}'
+          );
+
+          if (explain_prefix != undefined)
+            sql = explain_prefix + ' ' + sql;
+
           self.query_start_time = new Date();
           self.query = sql;
           self.rows_affected = 0;
@@ -2037,6 +2230,172 @@ define(
               alertify.alert('Auto Commit Error', msg);
             }
           });
+        },
+
+        // This function will
+        _explain: function() {
+          var self = this;
+          var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          // No need to check for buffers and timing option value in explain
+          var explain_query = 'EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE %s, COSTS %s, BUFFERS OFF, TIMING OFF) ';
+          explain_query = S(explain_query).sprintf(verbose, costs).value();
+          self._execute(explain_query);
+        },
+
+        // This function will
+        _explain_analyze: function() {
+          var self = this;var verbose = $('.explain-verbose').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var costs = $('.explain-costs').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var buffers = $('.explain-buffers').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+          var timing = $('.explain-timing').hasClass('visibility-hidden') ? 'OFF' : 'ON';
+
+          var explain_query = 'Explain (FORMAT JSON, ANALYZE ON, VERBOSE %s, COSTS %s, BUFFERS %s, TIMING %s) ';
+          explain_query = S(explain_query).sprintf(verbose, costs, buffers, timing).value();
+          self._execute(explain_query);
+        },
+
+        // This function will toggle "verbose" option in explain
+        _explain_verbose: function() {
+          if ($('.explain-verbose').hasClass('visibility-hidden') === true) {
+            $('.explain-verbose').removeClass('visibility-hidden');
+            self.explain_verbose = true;
+          }
+          else {
+            $('.explain-verbose').addClass('visibility-hidden');
+            self.explain_verbose = false;
+          }
+
+          // Set this option in preferences
+          var data = {
+            'explain_verbose': self.explain_verbose
+          };
+          $.ajax({
+          url: "{{ url_for('sqleditor.index') }}" + "query_tool/preferences" ,
+          method: 'PUT',
+          contentType: "application/json",
+          data: JSON.stringify(data),
+          success: function(res) {
+            if(res.success == undefined || !res.success) {
+              alertify.alert('Explain options error',
+                '{{ _('Error occurred while setting verbose option in explain') }}'
+              );
+            }
+          },
+          error: function(e) {
+            alertify.alert('Explain options error',
+                '{{ _('Error occurred while setting verbose option in explain') }}'
+            );
+            return;
+          }
+        });
+        },
+
+        // This function will toggle "costs" option in explain
+        _explain_costs: function() {
+          if ($('.explain-costs').hasClass('visibility-hidden') === true) {
+            $('.explain-costs').removeClass('visibility-hidden');
+            self.explain_costs = true;
+          }
+          else {
+            $('.explain-costs').addClass('visibility-hidden');
+            self.explain_costs = false;
+          }
+
+          // Set this option in preferences
+          var data = {
+            'explain_costs': self.explain_costs
+          };
+          $.ajax({
+          url: "{{ url_for('sqleditor.index') }}" + "query_tool/preferences" ,
+          method: 'PUT',
+          contentType: "application/json",
+          data: JSON.stringify(data),
+          success: function(res) {
+            if(res.success == undefined || !res.success) {
+              alertify.alert('Explain options error',
+                '{{ _('Error occurred while setting costs option in explain') }}'
+              );
+            }
+          },
+          error: function(e) {
+            alertify.alert('Explain options error',
+                '{{ _('Error occurred while setting costs option in explain') }}'
+              );
+          }
+        });
+        },
+
+        // This function will toggle "buffers" option in explain
+        _explain_buffers: function() {
+          if ($('.explain-buffers').hasClass('visibility-hidden') === true) {
+            $('.explain-buffers').removeClass('visibility-hidden');
+            self.explain_buffers = true;
+          }
+          else {
+            $('.explain-buffers').addClass('visibility-hidden');
+            self.explain_buffers = false;
+          }
+
+          // Set this option in preferences
+          var data = {
+            'explain_buffers': self.explain_buffers
+          };
+          $.ajax({
+          url: "{{ url_for('sqleditor.index') }}" + "query_tool/preferences" ,
+          method: 'PUT',
+          contentType: "application/json",
+          data: JSON.stringify(data),
+          success: function(res) {
+            if(res.success == undefined || !res.success) {
+              alertify.alert('Explain options error',
+                '{{ _('Error occurred while setting buffers option in explain') }}'
+              );
+            }
+          },
+          error: function(e) {
+            alertify.alert('Explain options error',
+              '{{ _('Error occurred while setting buffers option in explain') }}'
+            );
+          }
+        });
+
+        },
+
+        // This function will toggle "timing" option in explain
+        _explain_timing: function() {
+          if ($('.explain-timing').hasClass('visibility-hidden') === true) {
+            $('.explain-timing').removeClass('visibility-hidden');
+            self.explain_timing = true;
+          }
+          else {
+            $('.explain-timing').addClass('visibility-hidden');
+            self.explain_timing = true;
+          }
+          // Set this option in preferences
+          var data = {
+            'explain_timing': self.explain_timing
+          };
+          $.ajax({
+          url: "{{ url_for('sqleditor.index') }}" + "query_tool/preferences" ,
+          method: 'PUT',
+          contentType: "application/json",
+          data: JSON.stringify(data),
+          success: function(res) {
+            if(res.success == undefined || !res.success) {
+              alertify.alert('Explain options error',
+                '{{ _('Error occurred while setting timing option in explain') }}'
+              );
+            }
+          },
+          error: function(e) {
+            alertify.alert('Explain options error',
+                '{{ _('Error occurred while setting timing option in explain') }}'
+              );
+          }
+        });
+
         }
       }
     );


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  2016-05-09 15:19     ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-09 18:26       ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-10 06:51         ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-10 09:24           ` Re: PATCH: Graphincal explain integrated in sql editor Akshay Joshi <[email protected]>
  2016-05-13 10:25             ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
@ 2016-05-13 10:31               ` Sanket Mehta <[email protected]>
  2016-05-15 19:41                 ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  0 siblings, 1 reply; 10+ messages in thread

From: Sanket Mehta @ 2016-05-13 10:31 UTC (permalink / raw)
  To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers

Hi,

Apart from resolving all the issues mentioned in previous mail, All the
explain options and auto rollback/auto commit are also added to preferences
dialog.
Any change to explains options or auto-rollback/auto-commit in sql editor
will directly reflect its corresponding option in preference dialog.


Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Fri, May 13, 2016 at 3:55 PM, Sanket Mehta <[email protected]
> wrote:

> Hi,
>
> Revised patch is attached
> Response in inline.
>
>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Tue, May 10, 2016 at 2:54 PM, Akshay Joshi <
> [email protected]> wrote:
>
>> Hi Sanket
>>
>> On Tue, May 10, 2016 at 12:21 PM, Sanket Mehta <sanket.mehta@enterprisedb
>> .com> wrote:
>>
>>> Hi,
>>>
>>> As previous patch was not applicable to latest pgadmin4 source code,
>>> here is the new patch accommodating latest code.
>>> Please do review it and send comments.
>>>
>>
>>     Following is my review comments
>>
>>    - Remove Demo and sample code which you have used for testing before
>>    integration.
>>
>> Fixes
>
>>
>>    - Facing issue to open QueryTool as there is some problem in require
>>    module.
>>
>> Fixed
>
>>
>>    - Please add 'codemirror/addon/fold/foldgutter', 'codemirror/addon/fold/foldcode',
>>    'codemirror/addon/fold/pgadmin-sqlfoldcode' files which has been
>>    removed from your patch.
>>
>> Fixed
>
>>
>>    - Remove below code from sqleditor.js which is duplicated in _execute
>>    function
>>    -
>>       - self.trigger(
>>       -            'pgadmin-sqleditor:loading-icon:show',
>>       -             '{{ _('Initializing the query execution!') }}'
>>       -           )
>>
>> Fixed
>
>>
>>    - Clear the existing contents of the Explain tab when execute the
>>    query without explain.
>>
>> Fixed
>
>>
>>    - If schema name is exists then please prefix the schema name to the
>>    node.
>>
>> Fixed
>
>>
>>    - Please check the data is available before working on it in sqleditor
>>    .js at line no 1043 inside _render().  In case of "View Data" it
>>    throws an error.
>>
>> Fixed
>
>>
>>
>>>
>>> Regards,
>>> Sanket Mehta
>>> Sr Software engineer
>>> Enterprisedb
>>>
>>> On Mon, May 9, 2016 at 11:56 PM, Sanket Mehta <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> Please ignore previous patch as there was an error in it.
>>>>
>>>> Error:
>>>> Tooltip was not getting disappear when user moves cursor out of image.
>>>>
>>>> I have attached a proper patch with this mail.
>>>> Please consider it for testing.
>>>>
>>>> Regards,
>>>> Sanket Mehta
>>>> Sr Software engineer
>>>> Enterprisedb
>>>>
>>>> On Mon, May 9, 2016 at 8:49 PM, Sanket Mehta <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> PFA revised patch according to Ashesh's comments.
>>>>> Please find my response inline.
>>>>>
>>>>> I am currently adding minimap feature in graphical explain.
>>>>> I will send a new patch for the same.
>>>>>
>>>>> Regards,
>>>>> Sanket Mehta
>>>>> Sr Software engineer
>>>>> Enterprisedb
>>>>>
>>>>> On Mon, Apr 25, 2016 at 4:36 PM, Ashesh Vashi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Sanket,
>>>>>>
>>>>>> Please find the review comments.
>>>>>> - Please add the missing 'explain.css'.
>>>>>>
>>>>> Done
>>>>>
>>>>>> - The application should be smart enough to handle conflict in
>>>>>> options.
>>>>>>    i.e.
>>>>>>    Buffer is not a valid options without EXPLAIN ANALYZE.
>>>>>>
>>>>> Done
>>>>>
>>>>>> - A statement having EXPLAIN keywords with different format should at
>>>>>> least render the output in the data-grid.
>>>>>>   i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
>>>>>>
>>>>> Done
>>>>>
>>>>>> - Please use the keywords used in the EXPLAIN statement in capital.
>>>>>>
>>>>> Done
>>>>>
>>>>>> - Explain should not work with empty string.
>>>>>>
>>>>> Done
>>>>>
>>>>>> - Font size in the tooltip is very small.
>>>>>>
>>>>> Done
>>>>>
>>>>>>
>>>>>>
>>>>> - Smoothing the zoom functionality.
>>>>>>
>>>>> Minimap will be added and zoom functionality will be removed. So it is
>>>>> ignored.
>>>>>
>>>>> - Arrow marker is hardly visible.
>>>>>>
>>>>> Done.
>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>>
>>>>>> Thanks & Regards,
>>>>>>
>>>>>> Ashesh Vashi
>>>>>> EnterpriseDB INDIA: Enterprise PostgreSQL Company
>>>>>> <http://www.enterprisedb.com;
>>>>>>
>>>>>>
>>>>>> *http://www.linkedin.com/in/asheshvashi*
>>>>>> <http://www.linkedin.com/in/asheshvashi;
>>>>>>
>>>>>> On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>>
>>>>>>> This patch includes the patch sent earlier for stand alone graphical
>>>>>>> explain.
>>>>>>>
>>>>>>> And also "horizontal lines are not proper" bug is fixed in the same
>>>>>>> which was reported by Dave in previous patch.
>>>>>>>
>>>>>>> Regards,
>>>>>>> Sanket Mehta
>>>>>>> Sr Software engineer
>>>>>>> Enterprisedb
>>>>>>>
>>>>>>> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Team,
>>>>>>>>
>>>>>>>> PFA the first patch for graphical explain integrated in sql editor.
>>>>>>>>
>>>>>>>> Below are the few things which are different from previous patch
>>>>>>>> which was sent for stand alone graphical explain.
>>>>>>>>
>>>>>>>>  -  Now user can select Explain/Explain Analyze with four optional
>>>>>>>> properties (Verbose, costs, timing and buffers)
>>>>>>>>
>>>>>>>>  - Initially graph will be scale (according to only its width not
>>>>>>>> height) to fit to screen so no blank space will be there in case of very
>>>>>>>> large graph.
>>>>>>>>
>>>>>>>> - Along with zoom in/out button, "zoom to original" button is also
>>>>>>>> provided, by clicking on which graph will be scale to its original size
>>>>>>>> (not same as initial one which is according to screen size).
>>>>>>>>
>>>>>>>> Please do review this patch and let me know in case you have any
>>>>>>>> comments.
>>>>>>>>
>>>>>>>>
>>>>>>>> Regards,
>>>>>>>> Sanket Mehta
>>>>>>>> Sr Software engineer
>>>>>>>> Enterprisedb
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>>
>>> --
>>> Sent via pgadmin-hackers mailing list ([email protected])
>>> To make changes to your subscription:
>>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>>
>>>
>>
>>
>> --
>> *Akshay Joshi*
>> *Principal Software Engineer *
>>
>>
>>
>> *Phone: +91 20-3058-9517Mobile: +91 976-788-8246*
>>
>
>


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: PATCH: Graphincal explain integrated in sql editor
  2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 09:36 ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-04-25 11:06   ` Re: PATCH: Graphincal explain integrated in sql editor Ashesh Vashi <[email protected]>
  2016-05-09 15:19     ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-09 18:26       ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-10 06:51         ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-10 09:24           ` Re: PATCH: Graphincal explain integrated in sql editor Akshay Joshi <[email protected]>
  2016-05-13 10:25             ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
  2016-05-13 10:31               ` Re: PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
@ 2016-05-15 19:41                 ` Ashesh Vashi <[email protected]>
  0 siblings, 0 replies; 10+ messages in thread

From: Ashesh Vashi @ 2016-05-15 19:41 UTC (permalink / raw)
  To: Sanket Mehta <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers

On Fri, May 13, 2016 at 4:01 PM, Sanket Mehta <[email protected]
> wrote:

> Hi,
>
> Apart from resolving all the issues mentioned in previous mail, All the
> explain options and auto rollback/auto commit are also added to preferences
> dialog.
> Any change to explains options or auto-rollback/auto-commit in sql editor
> will directly reflect its corresponding option in preference dialog.
>
Thanks - committed.

Looking forward to a minimap kind of functionality for better zooming, and
sliding functionalities integrated with the explain module.
Also - added nice to have TODO list for the 'Graphical Explain' module.

--

Thanks & Regards,

Ashesh Vashi
EnterpriseDB INDIA: Enterprise PostgreSQL Company
<http://www.enterprisedb.com/;


*http://www.linkedin.com/in/asheshvashi*
<http://www.linkedin.com/in/asheshvashi;

>
>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Fri, May 13, 2016 at 3:55 PM, Sanket Mehta <
> [email protected]> wrote:
>
>> Hi,
>>
>> Revised patch is attached
>> Response in inline.
>>
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>> On Tue, May 10, 2016 at 2:54 PM, Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Sanket
>>>
>>> On Tue, May 10, 2016 at 12:21 PM, Sanket Mehta <sanket.mehta@
>>> enterprisedb.com> wrote:
>>>
>>>> Hi,
>>>>
>>>> As previous patch was not applicable to latest pgadmin4 source code,
>>>> here is the new patch accommodating latest code.
>>>> Please do review it and send comments.
>>>>
>>>
>>>     Following is my review comments
>>>
>>>    - Remove Demo and sample code which you have used for testing before
>>>    integration.
>>>
>>> Fixes
>>
>>>
>>>    - Facing issue to open QueryTool as there is some problem in require
>>>    module.
>>>
>>> Fixed
>>
>>>
>>>    - Please add 'codemirror/addon/fold/foldgutter', 'codemirror/addon/fold/foldcode',
>>>    'codemirror/addon/fold/pgadmin-sqlfoldcode' files which has been
>>>    removed from your patch.
>>>
>>> Fixed
>>
>>>
>>>    - Remove below code from sqleditor.js which is duplicated in
>>>    _execute function
>>>    -
>>>       - self.trigger(
>>>       -            'pgadmin-sqleditor:loading-icon:show',
>>>       -             '{{ _('Initializing the query execution!') }}'
>>>       -           )
>>>
>>> Fixed
>>
>>>
>>>    - Clear the existing contents of the Explain tab when execute the
>>>    query without explain.
>>>
>>> Fixed
>>
>>>
>>>    - If schema name is exists then please prefix the schema name to the
>>>    node.
>>>
>>> Fixed
>>
>>>
>>>    - Please check the data is available before working on it in
>>>    sqleditor.js at line no 1043 inside _render().  In case of "View
>>>    Data" it throws an error.
>>>
>>> Fixed
>>
>>>
>>>
>>>>
>>>> Regards,
>>>> Sanket Mehta
>>>> Sr Software engineer
>>>> Enterprisedb
>>>>
>>>> On Mon, May 9, 2016 at 11:56 PM, Sanket Mehta <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> Please ignore previous patch as there was an error in it.
>>>>>
>>>>> Error:
>>>>> Tooltip was not getting disappear when user moves cursor out of image.
>>>>>
>>>>> I have attached a proper patch with this mail.
>>>>> Please consider it for testing.
>>>>>
>>>>> Regards,
>>>>> Sanket Mehta
>>>>> Sr Software engineer
>>>>> Enterprisedb
>>>>>
>>>>> On Mon, May 9, 2016 at 8:49 PM, Sanket Mehta <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> PFA revised patch according to Ashesh's comments.
>>>>>> Please find my response inline.
>>>>>>
>>>>>> I am currently adding minimap feature in graphical explain.
>>>>>> I will send a new patch for the same.
>>>>>>
>>>>>> Regards,
>>>>>> Sanket Mehta
>>>>>> Sr Software engineer
>>>>>> Enterprisedb
>>>>>>
>>>>>> On Mon, Apr 25, 2016 at 4:36 PM, Ashesh Vashi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Sanket,
>>>>>>>
>>>>>>> Please find the review comments.
>>>>>>> - Please add the missing 'explain.css'.
>>>>>>>
>>>>>> Done
>>>>>>
>>>>>>> - The application should be smart enough to handle conflict in
>>>>>>> options.
>>>>>>>    i.e.
>>>>>>>    Buffer is not a valid options without EXPLAIN ANALYZE.
>>>>>>>
>>>>>> Done
>>>>>>
>>>>>>> - A statement having EXPLAIN keywords with different format should
>>>>>>> at least render the output in the data-grid.
>>>>>>>   i.e. EXPLAIN (FORMAT xml) SELECT * FROM xyz;
>>>>>>>
>>>>>> Done
>>>>>>
>>>>>>> - Please use the keywords used in the EXPLAIN statement in capital.
>>>>>>>
>>>>>> Done
>>>>>>
>>>>>>> - Explain should not work with empty string.
>>>>>>>
>>>>>> Done
>>>>>>
>>>>>>> - Font size in the tooltip is very small.
>>>>>>>
>>>>>> Done
>>>>>>
>>>>>>>
>>>>>>>
>>>>>> - Smoothing the zoom functionality.
>>>>>>>
>>>>>> Minimap will be added and zoom functionality will be removed. So it
>>>>>> is ignored.
>>>>>>
>>>>>> - Arrow marker is hardly visible.
>>>>>>>
>>>>>> Done.
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>>
>>>>>>> Thanks & Regards,
>>>>>>>
>>>>>>> Ashesh Vashi
>>>>>>> EnterpriseDB INDIA: Enterprise PostgreSQL Company
>>>>>>> <http://www.enterprisedb.com;
>>>>>>>
>>>>>>>
>>>>>>> *http://www.linkedin.com/in/asheshvashi*
>>>>>>> <http://www.linkedin.com/in/asheshvashi;
>>>>>>>
>>>>>>> On Mon, Apr 25, 2016 at 3:06 PM, Sanket Mehta <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> This patch includes the patch sent earlier for stand alone
>>>>>>>> graphical explain.
>>>>>>>>
>>>>>>>> And also "horizontal lines are not proper" bug is fixed in the same
>>>>>>>> which was reported by Dave in previous patch.
>>>>>>>>
>>>>>>>> Regards,
>>>>>>>> Sanket Mehta
>>>>>>>> Sr Software engineer
>>>>>>>> Enterprisedb
>>>>>>>>
>>>>>>>> On Thu, Apr 21, 2016 at 8:38 PM, Sanket Mehta <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Team,
>>>>>>>>>
>>>>>>>>> PFA the first patch for graphical explain integrated in sql editor.
>>>>>>>>>
>>>>>>>>> Below are the few things which are different from previous patch
>>>>>>>>> which was sent for stand alone graphical explain.
>>>>>>>>>
>>>>>>>>>  -  Now user can select Explain/Explain Analyze with four optional
>>>>>>>>> properties (Verbose, costs, timing and buffers)
>>>>>>>>>
>>>>>>>>>  - Initially graph will be scale (according to only its width not
>>>>>>>>> height) to fit to screen so no blank space will be there in case of very
>>>>>>>>> large graph.
>>>>>>>>>
>>>>>>>>> - Along with zoom in/out button, "zoom to original" button is also
>>>>>>>>> provided, by clicking on which graph will be scale to its original size
>>>>>>>>> (not same as initial one which is according to screen size).
>>>>>>>>>
>>>>>>>>> Please do review this patch and let me know in case you have any
>>>>>>>>> comments.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Regards,
>>>>>>>>> Sanket Mehta
>>>>>>>>> Sr Software engineer
>>>>>>>>> Enterprisedb
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> Sent via pgadmin-hackers mailing list ([email protected])
>>>> To make changes to your subscription:
>>>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>>>
>>>>
>>>
>>>
>>> --
>>> *Akshay Joshi*
>>> *Principal Software Engineer *
>>>
>>>
>>>
>>> *Phone: +91 20-3058-9517Mobile: +91 976-788-8246*
>>>
>>
>>
>


^ permalink  raw  reply  [nested|flat] 10+ messages in thread


end of thread, other threads:[~2016-05-15 19:41 UTC | newest]

Thread overview: 10+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2016-04-21 15:08 PATCH: Graphincal explain integrated in sql editor Sanket Mehta <[email protected]>
2016-04-25 09:36 ` Sanket Mehta <[email protected]>
2016-04-25 11:06   ` Ashesh Vashi <[email protected]>
2016-05-09 15:19     ` Sanket Mehta <[email protected]>
2016-05-09 18:26       ` Sanket Mehta <[email protected]>
2016-05-10 06:51         ` Sanket Mehta <[email protected]>
2016-05-10 09:24           ` Akshay Joshi <[email protected]>
2016-05-13 10:25             ` Sanket Mehta <[email protected]>
2016-05-13 10:31               ` Sanket Mehta <[email protected]>
2016-05-15 19:41                 ` Ashesh Vashi <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox