Subject: PATCH: graphical explain

From: Sanket Mehta <[email-masked]>
Date: Mon, 11 Apr 2016 22:04:27 +0530

Hi,

PFA the patch for graphical explain feature.

It consists of explain.js, __init__.py and demo_explain.html, snap_svg.js
and snap_svg-min.js file.

Currently I have placed hard coded JSON data in demo_explain.html file to
display graphical output; which will be replace by actual output user get
from server in JSON format.

Use below URL to access graphical output:

*http://localhost:5050/misc/explain <http://localhost:5050/misc/explain>
where 5050 is the port your web server is running on*

To test the patch, user can replace plan data (stored in 'data' variable)
in demo_explain.html with other valid plan data in JSON format and run the
above URL.

Please review the patch and provide comments for the same.

TODO: Integration of graphical explain into sql editor

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index f461cbe..d715d0c 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -9,25 +9,49 @@
 
 """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.static', filename='explain/js/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
+
 # 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
 ##########################################################################
 
-
 @blueprint.route("/ping", methods=('get', 'post'))
 def ping():
     """Generate a "PING" response to indicate that the server is alive."""
     driver.ping()
 
     return "PING"
+
+# TODO:: This is a demo url, remove it later.
[email-masked]("/explain")
+def demo():
+    return render_template('demo_explain.html')
+
[email-masked]("/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_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
zcmV;U2VD4xP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700093P)t-s0001q
zx!X2~!Eep(LA~ikzUg(&?|IPgQNrp}!s=JU>VwnpiPi9C$n0v#>~6~Jl-Ti^+VOnO
z?SRnjgVF7z-SMd2@{ZK*k=5>#*6*+3^8}~i2&m#nzv)fE=@+fz9k1hC#p`0n>oc|G
zIJf0JxaD!n?0C-XP`>9?z~@=P=Z4YkYscts$mx~W?^vp<rNQK8meDrBJ=gTf%J8?z
z@V3bBw$$^;#_zVp@3zwO$kOu1!tS-f?zPbI$IkJ`zU;KU?6kb>v&`|v;qUjj>z}pi
zp2zRDvgw_%>7B#xwXNu!tLL1m=bOLnw5H~orRJKX<(j(ev)AhNwCbL->YcCYov-Me
zspp%c<(i`9nYrw<*6H-b@3yY!oT%oTr{<fx?6aWcnYZh+(elT@?X}GC#>();xa+gX
z@5PDRjnwJ%!|t`d?X=DD$I0-;w(7FA>aw)zvc>Pj)9CZV@3yDrnw;a9n&X(6;+L}N
zvC`-Bp5&OC;+L1=m$2!vuj!nPsD)8oPC7a|R9;dzayvalJ#K?+tlz4Z;g+xIu+Zl6
ze0+S^*x3L7|CW}P^78VdqoaeGgs|zb!R^D&=JGd|Ja2Dr%F4=NVPRT)TsFEr&gJrA
zYhZqvd#l!@evf-NgFLLvsa9TAIC(q6?!>L=u&n5?!0p4#<?(^6dY;IeQe9G(#h8)c
zm5|?+kKdK5=da7;@u=sUU1?fzm2H&IjdhK3oyVGu-;}B5ugc@_k+F-U-l~k=l#AY!
zr{=H7<M7My#)*lE<>lqP?83+6@HtF6IaNDqoNPFaJb%Z6#^Ud@>9NG_#IESDs^_r1
z?8Ch5!=~o0rRJ`r=B~T!!o}h5vFWjr;FgHplZM@sh1`;(<*mcu?up)$h1`>b+>)Z@
zt-|2%w(GK#;g+Q3t%BT=q2#T?;O?&Ju)6EQxa+~d-|npEu&d{=zU;%N=B}XRt)Jwr
zw(7yT+w6ebk$&2dp5v|5>GQtc?XA}7jo+1w-;{mYk;vok=7Bya00001bW%=J06^y0
zW&i*H0b)x>M2><5vu^+Z010qNS#tmY07w7;07w8v$!k6U00YfQL_t(Y$L-W-R8wad
z2XMU3+PbUOs&(&~2LeQcLX^Y^21QXIq7WBos|+>9t=FhQNW|d4jU(WSL0d;0i32wU
z%aDMeq5>+43_}!)zV`(@$vxqN=XiSh!RN!9FZrE+{?C1LZEU{Je?9dGYU(_#5u$#B
zh7B7Ljhp<?^he^SX3bl)BwDp@^K)CGUHe};{7P6Q4LWx0)S39LOV_U5i0(al_UcXa
z>D#aW0HXcCfrADU-za4W{>^os7T+DpU<^ecU~6Y*XA8DWro96=II>v7U^tuYGy+CC
zyKqK<1!0UH&7g>#tE(#$m|QNG2dpuC{#anUxsMZoi^up0o-{IXq8YL0PV#aD7Ju>-
zHaNMD^L7T#1Rq}^jZB?dOStyHo8&cy1%6YexjBKrd%8ad0(^W$GprL^ru~-|GuckF
zPzx6hXEw|U44e^UmCOZd>O3vUym>gqfyZ-1DSp4RXTk`9;E(w*D!@}X2Nnc}goe>l
z7B0ku=S8(xG_9t^Vh(uvicpJS@e;{W8d<gs6GyaIKeSjkcL6y2dyEeNp-8km7(!P_
zR<5#6SbY9!Hmq4olIx@-HS0G}r*xx4CJU#LO`9+oMzxSeM9Srn3OO~Kqo`A{WtB2K
znnt#6{j#xb+vPh*GD<<l$Ro-1QE~-I?%ZW%W4m|b6zW;**&$c#-M2sX03rtuVIn=O
zQpLs7QjQ$Kgt{K<BIOZBeS~lfk>iK(4~sdWKB-BdkyEEI5qO{W{}ZMBb>?jBxf(*H
z#A!6=wKQ_!LN%Gb*yCbEb(g(n<kI=eIz5eCxq``j4kzHM^xAc_v6$K}cw<_vK9NRl
z+%UUiu{TNb)@_iIx9;4<kH_l9?%l&Fvpt1@3m_;YG${=JR)!}>Lwrht_CEZRnwFkH
zOL_1B6LdX9_<Be(A>3e4$3Y5ik(!nLFo#BRb1?}NEf<GCl4PY!3CU`;CIz%Q-J?`U
zfBf)C9*sPGipevvSeyizGTHNFP@UAEXQ6+Tm6ZnhPYMbOY2?KVOoBp_aEq6!+7^kZ
z#VaVvD=aLck=L)k`d?X1AZ%%-@-RfJ-W12fTdlq%5i;^~@`|9Sw6v_;JO#b@-o2w<
zez=7Ss;>vtA{}yYi&CSp!emYu?>|&let6HY0p)XpK?TJh|HX^V%Fc(pf&ybHlvPwz
znJiOsD=TxUlw^ZiT?`4Ab-EHr%YKX&TWB;I%fM7sWl50v^oe>YLv&nmN<7@xm!xLE
ztC|*Ns71NSWGc7ZSj}tvYc}?M{$KMM@J07H8ln{50000bbVXQnWMOn=I%9HWVRU5x
zGB7bPEip1JFfmjzFgi3dIy5vZFf}?bFgTed9smFUC3HntbYx+4WjbwdWNBu305UK!
pFfA}SEif@uGBY|fG&(RgD=;-WFfhuORjdF2002ovPDHLkV1ib?C5Zq4

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
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
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
zcmV;;1~U1HP)<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$M8d?TNYDHi*Hsx9YOA>5`V;
zba>TudDfVl;lRP|xVr1a#qMv-?nS=oOu*?=!s><8@RZo_dd}>F(d~=W?ycYRy1eVG
zuIIwU?d<LOK)mS(sNxW-;u@~wUB&Bh%j{0R=32q$WX0%`)$hQ;?!CY5b9mKtde+Ix
z@Y&h**4Fgb*Y&BOdclu2p^kTSi`DP$-@v+osGm@qkTs2g8T9eV@a(tD&GE*^@6FEg
z-rn}Y!gkf$l-}o>=JclL^r_X^k;1`s>*k-lz3i*3=(DuyyuIzz)brEXi_X=6)7*yR
z@}1)Fn#|LB(bkE<z;w;d@y^fj&DDI=+KsfZT%(*n%F%h#)s3a7<mB_5(aodf;>)wI
zTi@!H;_9L1^rM-NE%ET<;OUv$y@|D%e&zF`<nf^2=$Pd4q21+{>-4wX<CXC2w!elk
zw}gPOUOux+6SZPL#E^t=X1Khyc-!HV-s;qsyLGzJqwD3K>g1j0)t|Ru8_7)?*HI$j
zR412IQuXNJ!o%*Zuj$3b@7mvzfv!Sxr8k+pc<<}B@94ABoNVG$DF6Te+NO%UzU<lE
zkf+aQcB?zJR~Ex?L)hGpu-$Z0jT!Up#_r~_?d7rT;;+}*jk4f&da*&~*_GwilH}8n
zzi2Sz>Z#Y*jEuli<I<1f&Wz5IUDe)v)ZBXB<eb*mitFC2;?RxZ&5Pj5iQvPM>F>JI
z+IZ&awbj*$@8iDc*r(^!rR?6izl?*pWkAW5hS=YV*W8HI)Qi;9hvm?r<j$Y#*|yl*
zjnmSG+`fR@ynfode&frY+1-<vjV#)_eaFUhu&GwBr&YzmaL~<&&C7?wz;4OLexR2-
z$i;fXzHH0LgzDI~yti!7&wtO(e80PB$HREm)sD)>fYsIXCi79B00001bW%=J06^y0
zW&i*H0b)x>M61aGXD9#w010qNS#tmY07w7;07w8v$!k6U00Q?(L_t(Y$75g^@qm$u
znS~VvurM<*;@82(&cVsW&B?>d$1fl#BrGB-CN6<j4-=cDl%zDW79JT{ISD4X0(k`x
zfRbPlW<@C_Wff$1sj8`KXfngKC}?Tx=;|rx8yFfHD}Y5<OiY!{%#rOiH&c@nWI<@L
zu(YzaQLweMcW{JgVRdqLF-LW)xvQHyD;`@|ot;@3&;YB4Cqm528w7m7ZuW(0kpWBl
z`3D3B1qJx~p=<FD4habf_6~Il3y)w$*5c<M85JE98y64KgQ_JYAt52eJ25FaB^5=B
ze`H#EMrKxaPOd*;EqVDMMnGIaVNr2WPDyD1K`mvhtYttdD7L()qOzi@x+aLAU!WkU
zHnXmtp}wxV0bNTYIK-O3Ay$T>rMV>tM72f}Z%Z3QOLTh>(SGUZ0BdRQ#HyvMyQjCW
zf5JqF4qi8qo=HJLlR#QJC&RT&nK})sW%`Vnvu4kkJ7@O1`5+w&7A{<b@K(^|#c(ZC
zmn>Zd_RI3+D^|{4wQBY1RkPOsB}LXQTqq|BcSg|Sb#N`ymagBh3>sn^=d1$hnX?J(
z6Il(UzzbTp8R3^@8@6l(+Olo?j-7pTSA+D-?L$g1Fu!cxg|KDA?ma+Tym#!~xBtML
z)gV0}TVPry1syyH(i61n5W+8q_kjJfbKjAp$7X}P0`?13%cP*1<0no6^#mO{g%M(>
z&zwDX9uZ<tEkPIBPh4DlDTpEH)Mbo#xpMW|wd>tC`ulF)n#h1;OVI5*Am0RCzDqcb
zLA?(NU522$_b_a^e*gZ1hgfoD(7H!pEsq~#Xu1F7>9glpa%IqqLob1e|K(#0hj~AE
z_UaWjErF%4-vG1N+js8+(X~8(g<VU)hmW7$et!Gq>o<&y{Qdd!=ijmT#s9~bukU{T
z`u*pJKd~X^=l|o|`@4Vt{lF}gaM^;V=)>X{ECuX;{93RT<Nw|9X~9}q$RJe~n0_Hx
zC1Hjb!HN$PkWsyoQHhUa008<2i-%GaY|Q`w03~!qSaf7zbY(hYa%Ew3WdJfTF)%GL
zGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6WZEs|0W_bWIFflMKFgYzS
jF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjf_`$`P

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
zcmY+EX;jl^7RCc83W}o8IHN*Uq-Zs4LRCbI4z#9NL@EWMAP(RVtW_JR7PVGNRRkQh
znn<Z&+4l&9og#vPgai^o2nm4%Ldc%~mVd$)>ZI+bIp?|eIrp6BcfY*ny;<R5pE}rY
zw#Q&F4*2825f=CTvsT+!`tIrLKVmR8nD7&)Lo8!yi~K_7@W2ejo`r{IVXgw<s|_3_
z!kB^j<Y2EH=#lArWI(q}|3<EVGYj-6^t~#5uNt6hfPO95rvv--AOirIAT(eR1hF7+
z5QgYdkUkBv<PcAd3@Koy9O_j7{d#~2fCCmm;Oj|!w*=^s0=-i}p9~aeP_`wefEcr2
zpAw`ifwvlf34*O6ZTq;kU99Vz&~YuRR4_{k4X7Zd3S_9Del^%QrlAQne64|}K{y(i
zt$~<Y=oMdGFHkp&G_NK4F)hl~B12kuPzMd@K!!yi$b`UZo~m|4Mdho6I#i%DjOq<M
z9WtzkIeNHiSV<jKH;ifKmX_zu%M0dZrVgs)D(m=ay=jrjVm*HR_}3RN3JMEv+`O4g
zBoYXO$jHd3=;%|YB90zC%F;s>TxI33vX-xUB~aHl((Ytrq@|@%U)9qY40=Dq-`~HS
zqpad7&!0aZckbN9i(hHy78aJ5xxA5pfPjgqDKTOk1mJRxqJpbv9oIs0OT5uhJRUy|
zqFLG5J#Xns)BF$smvI#3Tt!t)O>=A8;_@Hk5IPPSMG!h`oS!rp*#J~MqN*EJ>p%#c
zUz|csBOt=DP(zB!VZ{tG7akriMT|lS6+nhT02%<Gx>1#B$t;G9V#_KR<$_4z%a@*>
zo*o_^LKtNMQ0b7OYf>);joY_xhZmOkATp*mJbC)`?KCJwOd=Qs4QN7Qq7DEnh81;u
zRe5ElVbMHiHs8K|djc|6@l>5%T|xkrD^+9)WpR0#I;t83A>*Ri#l?jS!YmM~<SBU&
z!UiD;Y#f78K4chz;jSsY#9-n=Fb9FT2;3pj4XR-QfQn(`FpLZ%$OwWo2-Q5jfsYsj
z1_Rad`s3QxNv+U;3JnGkiqgh4%@bOQ(Kvw`d*lF94M|NV)}cJq@&~L^Bf`GG1Sy0U
zEQ8&Z<1r+QAOEX9VO~6f!K~Pd4?cSOcz;6tKUOAMee%H$jN1e32N$-*pHE8ok1~E*
zE>ld;&dew`IXXIT#9}wR=R|q*^a=TK(y`GU9$sD?W=(frT}8=DHl6NZosse3Hr$SX
z%c@c|5u1IJ34Z?mO|-Ps_YR=W&rpZ84i0TOIXStxA(@~5KsX)g&OhNpd!CnnCORcW
z+ptTsn-H@{BoqqA$3@~p_wQp|?#5;p7Z?AW_fzRd<r_N+8ncU@J$qJNou8lI(NVF>
z6X)vcTJSJVAwldPq#qdynv%)1TCMVZ4Bu(jI{rrInmc9gJN^9FBTRP0y`O(P92jW5
zdWV}^aZyo%wYBxt<SYj}yWH$K>-N3deeA3@Iyu=qxtfg6%5}gSQyaKpp&SN#fHA}z
zWN`)ual20B?2d4vbeEO3mh3E{v4@X^?*A>Yqp0D--`mUD+XJd!VJk{kcaYm<vPJHJ
zN7%x*y1RX4B^Ml%iI*;ds=X1tRi)J(2P~ofGnCR6>g!iHH=^}w+KFHNzOxU{-i*b4
zQJzK#YPzyBj9Od!!iUsTBO)o9n>qQu=VBc?$VB%wSnDQ6<CWH(U-AW5PeR~DQ_VpV
zWpAu4e@9jb3#lXSi6=Nmc@jni<b5RnaMmwPp_JM)|2@O9eS}@PE<~AId#R}@Z{Pk)
z5^H`#lT!)Z`5yLU1^Ir-r6%z;T9|~E<Z+zkSwr~tUro9@$)fz|wuEyF-#;ST1g9K)
z?njjVmY2Ue#m<U&aIK)>_6F|Kr#}!LZmdq4t)E%&_+|eF9xu&T{<M5kbm(=S+T67-
zznh>ah!wYmx|EY6USeI6-05#ol0W_ir*KtD-b7p)wrHhWR0i!VCdEqOvz%$l$;MN*
z5eUttCkw3kMGr$*ryr3Ic*MINA}tIwmt+Qew~kAs4bCMY_AS^oAT8ma&qnUNG|Nix
zo7NktDI)t5l!%Ck=q5fTj-)#^aov;Z`21{&crnV@GWzx9uVT_~{o%Pgo|qLGs;azB
z-QsiypCld3Uoz>1Rz>Sy#HMQ(_2L$vHNx2-P#S|-oAhwY7N;Or{AVe|Ji8S*mur_}
zUta?Ya`t|UD@_VJOzu2u``MSCL#w<#7pC4ecH-8%e*OFRv8MJKom&mz7?pk_CeT(>
zU;j<kgB6;X<&p&Dc*n`YBk8D0L}H({<2Ae)sZ_3v=7dTs7*S4fALot1Q`3|CFH1Ua
zEv>spZkUS;IvhB0*VZfc*2kR$8$qMV&GF`9=Bb`kYQ;InbPcI<ghKUlHC!c$#O(_q
zF3}_PiE;fcptNgw9=2)Rx~C08n<bf-3SS4M&$s33Cd@_ax87=y&%N(GpBvXre#-aS
zg_{hw9k%8hqUfr^Gi+XyODNfFoAK`5yW*7X#py09gKaRar}nL){ZzKiaxmOeL!wg?
z&!;8@Bz%)(F^sRbk2lW87w7GB+Sfb4Z(o3)-yU!O0B`RDdG4<N2VA<GNWAdf-+=eQ
czkol}0p1_|4Gd5sYb*c;9}*T^cPu{Rf7sxo_y7O^

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
zcmV;e2U7TnP)<h;3K|Lk000e1NJLTq001%o001%w0{{R3DdBj700093P)t-s0001q
zx!X2~!Eep(LA~ikzUg(&?|IPgQNrp}!s=JU>VwnpiPi9C$n0v#>~6~Jl-Ti^+VOnO
z?SRnjgVF7z-SMd2@{ZK*k=5>#*6*+3^8}~i2&m#nzv)fE=@+fz9k1hC#p`0n>oc|G
zIJf0JxaD!n?0C-XP`>9?z~@=P=Z4YkYscts$mx~W?|qB4rNQK8meJ5eW%S9}@VCtG
zw#)O!*Y2~(?y|@6#MJDt#q6)d?5@M`!O`!)(CVwe>8ii(zRuzA_v@d#>YcjkoVn?n
zxbL*d=$W<Wn6&4Yv*nbp<dUxIt-|7tso{*L=%~Ec>h<ZIxapd==9aSKk*wp8tKf>I
z*6H-`w94q2w(YUT=9RJKl(654q~D06=B2rdKT%UnPewyTlyrWDZFV|3I(I%y#G<Zt
zWNL(KbcAtuL_0lQQB|jbj)G`#z?`UTJxGCOZfRU#pg~tqMoH!6<?{0KiHV8-|NpSC
zu)V##R8&;h*w|rVVV0JbZ*OnP%F1U^SJdhBqobooM@Q4>^OZnUe0+SQev3swK~YFc
z(&zKMLtn3jlUz(tb6sRlLr2b%q)|yse`0FT=JL+w^5c=DVm(EGKTgi&@@ZOMwU(gB
zm8FAjb)$QS&y=Rj<?+;<uYFx+W=~hCL0V{6TytDwd{|@He3sdms>|f@jBa<hZiAC<
zc(OuV%H#0J<M7I)vA>3umU@DWa(Tz&@QGq<#^UczJ3z(Z@5Pa!#dVCr;O=lzTe*jp
ziD`1#eV1`sU}jTT?7GS1p|$F^#p0f^;+?SJoUiD#!|1ZX-<Yf4m#XHlzviyI-Ib@^
zl&9U3rsS==<g2>dk)+#@qS}w5<Eyy9-|p+U$K#)~<DRnMo37xRt>Brg=d!@wmZ{#A
zsN9mI+K-{yj-a{Q?BSfS)_9WDcaYU~kJECD(r}5;ZHCWmgwJb(&1iwmXMfp@p492{
zzTWMv*6G-LmDhTd)O3!~Z-~xng3M-q&S`?qX@S~~q2j5weBkSj00001bW%=J06^y0
zW&i*H0b)x>M0ugy)X)F`010qNS#tmY07w7;07w8v$!k6U00Y-aL_t(Y$L*APR8>_J
z#_6U_Hcx6c*gRSBHar=`ATcUJ(Ht;saO%7iG$Fe8?l;``uHGjvHK9N)P1H=2tk6nR
zgOUm|qrd?tL?l2>Amk|uYT-F2DDLgiTCU}4^@sb9ec!w5{MNU>{oQr^{La^ZT^9(f
zI_$m>;lfUxJ6|MRe95JkbrCMV;>xS87OuJWy6bNcZtU8v`%QvRq*IR`H{T-MdRxz)
zw+naNdDq?d2>0H1{{s&SU3>L<=waa;sXW4G?y&35`kPGt^Z@~Zfq?-KU^bh3L+_xV
z;1CE2?Gx4)9u1F(cnoxisb4=6LjofsBh6s$|9I3B5cK3z(V@`i>6n-S5I!(Ac8~!X
zJh(mbOw^Dd2#$^(8VX@C!-j`LL~LA~5g9R}4e4)&XQPIU42DrdpL;$G1`HcMFaid}
zz3}2_pU9Z8<Ho;4EH6(W6XO#InKU_`(0XM`B1uXjuO|CKUK6DR8Lf#nv|A-ahoT@Y
zC;hLuP@SqrrcGmr#fo+(s$)e_Mvd4}Rp>)RH$@zx+n_RiIzv)q^r$GgC94zV*Jnr)
zN5q*nR8byp@G@%_L*B5XQ&iD|vWA}7sSb$^CMsz*+TVQ3Cj#jda7ii~uT4cmnIbmS
zG7QL^IUI?%O42x064O0@d6_AS+zYx=^vK+~44FsGsBb&bYO%C!Onb+JDn%4@r0S9R
z^SMgMM6%#r3rSkIFq7r7D3fF^&L9gCNk#^lWKdbMgfE#TM=b9xCGXE(mPUUrPyT?c
z$oi1KH+osQk|L6uC5PHa8mh}_en_XJGkyGtlf725^~kDKf2t^GBbIcLX2T-4qN^2g
z{!?Z3YVPGzicDDMV#zUtemJy$_BlgltT7;K*Yb^JQzY>V4egRTl@G@Dn>~gNCMC?%
zBkR`vt+HNZC)|9q){a#+Y~aY36iIW@12W3=u!kcgm3vwGFNmVqbKIKD2eY&8@VT+v
zT#k%hLB9Grdt**s&c>8)Xcpflv!2RxF{-p8-{t4$7eK-Hn|{~~KNc4L^fMF{Z`ryH
zwwLVKxyyj;-pw}#tUBzz5ZF^%y0-u}m+dRx5Bn>ADK3Jd%F2@Mu&b)7TBkxUz5@r?
z<p-sCd07Xc?9i`=3!$Q>wstF2*8Nt$6RM6JY4Em3Oh=D3HXb`_@`Jru$4`_&`QfIf
z3Mj5^Zmxuq`udYqP~FgQ%Bxq&ZEVbCDw~d<I8+XWO*J*eu%)@Kt^{_RJpFq$MNa7v
z%Fu7jpL%KUVq2?=KdM`x#q0I9=r`7Sji1fN&e#8|&HxgQ2avxbUitt403~!qSaf7z
zbY(hYa%Ew3WdJfTF)%GLGA%GMR5CC+G&4FhG%GMQIxsLenI#?o001R)MObuXVRU6W
zZEs|0W_bWIFflMKFgYzSF;p@$Iy5voFg7bNH99ab%9mBF00000NkvXXu0mjfomvcc

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/explain.js b/web/pgadmin/misc/static/explain/js/explain.js
new file mode 100644
index 0000000..ea31d91
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/explain.js
@@ -0,0 +1,638 @@
+define (
+  'pgadmin.misc.explain',
+  ['jquery', 'underscore', 'pgadmin', 'backbone', 'snap.svg'],
+  function($, _, 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: 'static/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;
+
+// Backbone model for each plan property of input JSON object
+var PlanModel = Backbone.Model.extend({
+    defaults: {
+        "Node Type": undefined,
+        "Startup Cost": undefined,
+        "Total Cost": undefined,
+        "Plan Rows": undefined,
+        "Plan Width": undefined,
+        "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+1, midX2+1, 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">' + key + '</td><td class="label">' + 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(document.children[0].clientWidth < (toolTipX + toolTipContainer[0].clientWidth))
+            toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH*zoomFactor));
+          if(document.children[0].clientHeight < (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(
+	        10, 10, 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 zoomInBtn = $('<span></span>', {
+                class: 'explain-zoom-btn explain-zoom-in badge',
+                style: 'pull-left'
+                }).text('+').appendTo(container),
+              zoomOutBtn = $('<span></span>', {
+                class: 'explain-zoom-btn explain-zoom-out badge',
+                style: 'pull-left'
+                }).text('-').appendTo(container);
+
+          // Main div to be drawn all images on
+          var planDiv = $('<div></div>',
+                           {class: "pgadmin-explain-container",
+                           style: "overflow: auto; max-height: 650px; width: 99%;"}
+                         ).appendTo(container);
+
+            planDiv.data('zoom-factor', curr_zoom_factor);
+
+          // Div to draw tool-tip on
+          var toolTip = $('<div></div>',
+                           {id: "toolTip",
+                           class: "pgadmin-explain-toolTip",
+                           style: "position: absolute;padding:5px; border: 1px solid white;opacity:0;color: cornsilk;background-color: #010125;"
+                           }
+                         ).appendTo(container);
+          toolTip.empty();
+          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, $('.pgadmin-explain-container'), $('.pgadmin-explain-toolTip'));
+
+            zoomInBtn.on('click', function(e){
+              curr_zoom_factor = (curr_zoom_factor > 2) ? 2 : (curr_zoom_factor + 0.10);
+              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.on('click', function(e) {
+              curr_zoom_factor = (curr_zoom_factor < 0.10) ? 0.10 : (curr_zoom_factor - 0.10);
+              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);
+            })
+          });
+        }
+    });
+
+    return pgExplain;
+});
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..5475170
--- /dev/null
+++ b/web/pgadmin/misc/static/explain/js/snap.svg.js
@@ -0,0 +1,8170 @@
+// 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/demo_explain.html b/web/pgadmin/misc/templates/demo_explain.html
new file mode 100644
index 0000000..99b49d9
--- /dev/null
+++ b/web/pgadmin/misc/templates/demo_explain.html
@@ -0,0 +1,1836 @@
+{% extends "base.html" %}
+{% block title %}{{ config.APP_NAME }}{% endblock %}
+{% block init_script %}
+try {
+  require(
+    ['pgadmin', 'pgadmin.misc.explain'],
+    function(pgAdmin, pgExplain) {
+var data = [
+  {
+    "Plan": {
+      "Node Type": "Append",
+      "Startup Cost": 314.58,
+      "Total Cost": 5431.36,
+      "Plan Rows": 15710,
+      "Plan Width": 287,
+      "Plans": [
+        {
+          "Node Type": "Hash Join",
+          "Parent Relationship": "Member",
+          "Join Type": "Right",
+          "Startup Cost": 314.58,
+          "Total Cost": 408.52,
+          "Plan Rows": 3655,
+          "Plan Width": 344,
+          "Hash Cond": "((c.probe_id = p.id) AND (c.agent_id = a.id))",
+          "Plans": [
+            {
+              "Node Type": "Seq Scan",
+              "Parent Relationship": "Outer",
+              "Relation Name": "probe_config_agent",
+              "Alias": "c",
+              "Startup Cost": 0.00,
+              "Total Cost": 27.30,
+              "Plan Rows": 1730,
+              "Plan Width": 17
+            },
+            {
+              "Node Type": "Hash",
+              "Parent Relationship": "Inner",
+              "Startup Cost": 259.76,
+              "Total Cost": 259.76,
+              "Plan Rows": 3655,
+              "Plan Width": 335,
+              "Plans": [
+                {
+                  "Node Type": "Nested Loop",
+                  "Parent Relationship": "Outer",
+                  "Join Type": "Inner",
+                  "Startup Cost": 0.00,
+                  "Total Cost": 259.76,
+                  "Plan Rows": 3655,
+                  "Plan Width": 335,
+                  "Join Filter": "(((p.agent_capability IS NULL) OR (p.agent_capability = ANY (a.agent_capability_list))) AND ((p.collection_method <> ALL ('{b,w}'::bpchar[])) OR ((p.collection_method = 'b'::bpchar) AND (a.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a.platform, p.platform) <> 0) OR ((a.platform !~ 'windows'::text) AND (p.platform = 'unix'::text)))) OR ((p.collection_method = 'w'::bpchar) AND (strpos(a.platform, 'windows'::text) <> 0))))",
+                  "Plans": [
+                    {
+                      "Node Type": "Seq Scan",
+                      "Parent Relationship": "Outer",
+                      "Relation Name": "agent",
+                      "Alias": "a",
+                      "Startup Cost": 0.00,
+                      "Total Cost": 14.00,
+                      "Plan Rows": 400,
+                      "Plan Width": 69
+                    },
+                    {
+                      "Node Type": "Materialize",
+                      "Parent Relationship": "Inner",
+                      "Startup Cost": 0.00,
+                      "Total Cost": 5.78,
+                      "Plan Rows": 12,
+                      "Plan Width": 377,
+                      "Plans": [
+                        {
+                          "Node Type": "Seq Scan",
+                          "Parent Relationship": "Outer",
+                          "Relation Name": "probe",
+                          "Alias": "p",
+                          "Startup Cost": 0.00,
+                          "Total Cost": 5.72,
+                          "Plan Rows": 12,
+                          "Plan Width": 377,
+                          "Filter": "((NOT deleted) AND (target_type_id = 100))"
+                        }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "Node Type": "Hash Join",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 2833.75,
+          "Total Cost": 3132.62,
+          "Plan Rows": 3960,
+          "Plan Width": 913,
+          "Hash Cond": "((p_1.id = psv.probe_id) AND (sd.server_version_id = psv.server_version_id))",
+          "Filter": "(p_1.any_server_version OR (psv.probe_id IS NOT NULL))",
+          "Plans": [
+            {
+              "Node Type": "Merge Join",
+              "Parent Relationship": "Outer",
+              "Join Type": "Right",
+              "Startup Cost": 2822.18,
+              "Total Cost": 2932.19,
+              "Plan Rows": 3960,
+              "Plan Width": 385,
+              "Merge Cond": "((c_1.probe_id = p_1.id) AND (c_1.server_id = b.server_id))",
+              "Plans": [
+                {
+                  "Node Type": "Index Scan",
+                  "Parent Relationship": "Outer",
+                  "Scan Direction": "Forward",
+                  "Index Name": "probe_config_server_pkey",
+                  "Relation Name": "probe_config_server",
+                  "Alias": "c_1",
+                  "Startup Cost": 0.15,
+                  "Total Cost": 70.10,
+                  "Plan Rows": 1730,
+                  "Plan Width": 17
+                },
+                {
+                  "Node Type": "Sort",
+                  "Parent Relationship": "Inner",
+                  "Startup Cost": 2822.03,
+                  "Total Cost": 2831.93,
+                  "Plan Rows": 3960,
+                  "Plan Width": 376,
+                  "Sort Key": ["p_1.id", "b.server_id"],
+                  "Plans": [
+                    {
+                      "Node Type": "Hash Join",
+                      "Parent Relationship": "Outer",
+                      "Join Type": "Inner",
+                      "Startup Cost": 2344.89,
+                      "Total Cost": 2585.39,
+                      "Plan Rows": 3960,
+                      "Plan Width": 376,
+                      "Hash Cond": "(b.server_id = s.id)",
+                      "Join Filter": "(((p_1.agent_capability IS NULL) OR (p_1.agent_capability = ANY (a_1.agent_capability_list))) AND ((p_1.collection_method <> 'b'::bpchar) OR ((p_1.collection_method = 'b'::bpchar) AND (a_1.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_1.platform, p_1.platform) <> 0) OR ((a_1.platform !~ 'windows'::text) AND (p_1.platform = 'unix'::text))))))",
+                      "Plans": [
+                        {
+                          "Node Type": "Hash Join",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Left",
+                          "Startup Cost": 38.90,
+                          "Total Cost": 63.90,
+                          "Plan Rows": 400,
+                          "Plan Width": 109,
+                          "Hash Cond": "(b.server_id = sd.server_id)",
+                          "Plans": [
+                            {
+                              "Node Type": "Hash Join",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Inner",
+                              "Startup Cost": 19.00,
+                              "Total Cost": 38.50,
+                              "Plan Rows": 400,
+                              "Plan Width": 105,
+                              "Hash Cond": "(b.agent_id = a_1.id)",
+                              "Plans": [
+                                {
+                                  "Node Type": "Seq Scan",
+                                  "Parent Relationship": "Outer",
+                                  "Relation Name": "agent_server_binding",
+                                  "Alias": "b",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 14.00,
+                                  "Plan Rows": 400,
+                                  "Plan Width": 40
+                                },
+                                {
+                                  "Node Type": "Hash",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 14.00,
+                                  "Total Cost": 14.00,
+                                  "Plan Rows": 400,
+                                  "Plan Width": 69,
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "agent",
+                                      "Alias": "a_1",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 14.00,
+                                      "Plan Rows": 400,
+                                      "Plan Width": 69
+                                    }
+                                  ]
+                                }
+                              ]
+                            },
+                            {
+                              "Node Type": "Hash",
+                              "Parent Relationship": "Inner",
+                              "Startup Cost": 14.40,
+                              "Total Cost": 14.40,
+                              "Plan Rows": 440,
+                              "Plan Width": 8,
+                              "Plans": [
+                                {
+                                  "Node Type": "Seq Scan",
+                                  "Parent Relationship": "Outer",
+                                  "Relation Name": "server_info",
+                                  "Alias": "sd",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 14.40,
+                                  "Plan Rows": 440,
+                                  "Plan Width": 8
+                                }
+                              ]
+                            }
+                          ]
+                        },
+                        {
+                          "Node Type": "Hash",
+                          "Parent Relationship": "Inner",
+                          "Startup Cost": 2270.24,
+                          "Total Cost": 2270.24,
+                          "Plan Rows": 2860,
+                          "Plan Width": 382,
+                          "Plans": [
+                            {
+                              "Node Type": "Nested Loop",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Inner",
+                              "Startup Cost": 0.00,
+                              "Total Cost": 2270.24,
+                              "Plan Rows": 2860,
+                              "Plan Width": 382,
+                              "Join Filter": "(NOT (SubPlan 1))",
+                              "Plans": [
+                                {
+                                  "Node Type": "Seq Scan",
+                                  "Parent Relationship": "Outer",
+                                  "Relation Name": "server",
+                                  "Alias": "s",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 12.20,
+                                  "Plan Rows": 220,
+                                  "Plan Width": 5
+                                },
+                                {
+                                  "Node Type": "Materialize",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 5.85,
+                                  "Plan Rows": 26,
+                                  "Plan Width": 378,
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "probe",
+                                      "Alias": "p_1",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 5.72,
+                                      "Plan Rows": 26,
+                                      "Plan Width": 378,
+                                      "Filter": "((NOT deleted) AND (target_type_id = 200))"
+                                    }
+                                  ]
+                                },
+                                {
+                                  "Node Type": "Result",
+                                  "Parent Relationship": "SubPlan",
+                                  "Subplan Name": "SubPlan 1",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 0.51,
+                                  "Plan Rows": 100,
+                                  "Plan Width": 0
+                                }
+                              ]
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "Node Type": "Hash",
+              "Parent Relationship": "Inner",
+              "Startup Cost": 8.23,
+              "Total Cost": 8.23,
+              "Plan Rows": 223,
+              "Plan Width": 541,
+              "Plans": [
+                {
+                  "Node Type": "Seq Scan",
+                  "Parent Relationship": "Outer",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv",
+                  "Startup Cost": 0.00,
+                  "Total Cost": 8.23,
+                  "Plan Rows": 223,
+                  "Plan Width": 541
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "Node Type": "Hash Join",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 173.08,
+          "Total Cost": 1032.86,
+          "Plan Rows": 7997,
+          "Plan Width": 913,
+          "Hash Cond": "((p_2.id = psv_1.probe_id) AND (sd_1.server_version_id = psv_1.server_version_id))",
+          "Filter": "(p_2.any_server_version OR (psv_1.probe_id IS NOT NULL))",
+          "Plans": [
+            {
+              "Node Type": "Merge Join",
+              "Parent Relationship": "Outer",
+              "Join Type": "Left",
+              "Startup Cost": 161.50,
+              "Total Cost": 639.89,
+              "Plan Rows": 7997,
+              "Plan Width": 385,
+              "Merge Cond": "((b_1.server_id = c_2.server_id) AND (ocd.database_name = (c_2.database_name)::text))",
+              "Join Filter": "(p_2.id = c_2.probe_id)",
+              "Plans": [
+                {
+                  "Node Type": "Merge Join",
+                  "Parent Relationship": "Outer",
+                  "Join Type": "Inner",
+                  "Startup Cost": 89.66,
+                  "Total Cost": 517.76,
+                  "Plan Rows": 7997,
+                  "Plan Width": 376,
+                  "Merge Cond": "(ocd.server_id = b_1.server_id)",
+                  "Plans": [
+                    {
+                      "Node Type": "Index Scan",
+                      "Parent Relationship": "Outer",
+                      "Scan Direction": "Forward",
+                      "Index Name": "oc_database_pkey",
+                      "Relation Name": "oc_database",
+                      "Alias": "ocd",
+                      "Startup Cost": 0.15,
+                      "Total Cost": 55.40,
+                      "Plan Rows": 375,
+                      "Plan Width": 36,
+                      "Filter": "connections_allowed"
+                    },
+                    {
+                      "Node Type": "Materialize",
+                      "Parent Relationship": "Inner",
+                      "Startup Cost": 89.51,
+                      "Total Cost": 352.13,
+                      "Plan Rows": 4265,
+                      "Plan Width": 344,
+                      "Plans": [
+                        {
+                          "Node Type": "Nested Loop",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Inner",
+                          "Startup Cost": 89.51,
+                          "Total Cost": 341.47,
+                          "Plan Rows": 4265,
+                          "Plan Width": 344,
+                          "Join Filter": "(((p_2.agent_capability IS NULL) OR (p_2.agent_capability = ANY (a_2.agent_capability_list))) AND ((p_2.collection_method <> 'b'::bpchar) OR ((p_2.collection_method = 'b'::bpchar) AND (a_2.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_2.platform, p_2.platform) <> 0) OR ((a_2.platform !~ 'windows'::text) AND (p_2.platform = 'unix'::text))))))",
+                          "Plans": [
+                            {
+                              "Node Type": "Merge Join",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Left",
+                              "Startup Cost": 89.51,
+                              "Total Cost": 97.71,
+                              "Plan Rows": 400,
+                              "Plan Width": 77,
+                              "Merge Cond": "(b_1.server_id = sd_1.server_id)",
+                              "Plans": [
+                                {
+                                  "Node Type": "Sort",
+                                  "Parent Relationship": "Outer",
+                                  "Startup Cost": 55.79,
+                                  "Total Cost": 56.79,
+                                  "Plan Rows": 400,
+                                  "Plan Width": 73,
+                                  "Sort Key": ["b_1.server_id"],
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Hash Join",
+                                      "Parent Relationship": "Outer",
+                                      "Join Type": "Inner",
+                                      "Startup Cost": 19.00,
+                                      "Total Cost": 38.50,
+                                      "Plan Rows": 400,
+                                      "Plan Width": 73,
+                                      "Hash Cond": "(b_1.agent_id = a_2.id)",
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "agent_server_binding",
+                                          "Alias": "b_1",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 14.00,
+                                          "Plan Rows": 400,
+                                          "Plan Width": 8
+                                        },
+                                        {
+                                          "Node Type": "Hash",
+                                          "Parent Relationship": "Inner",
+                                          "Startup Cost": 14.00,
+                                          "Total Cost": 14.00,
+                                          "Plan Rows": 400,
+                                          "Plan Width": 69,
+                                          "Plans": [
+                                            {
+                                              "Node Type": "Seq Scan",
+                                              "Parent Relationship": "Outer",
+                                              "Relation Name": "agent",
+                                              "Alias": "a_2",
+                                              "Startup Cost": 0.00,
+                                              "Total Cost": 14.00,
+                                              "Plan Rows": 400,
+                                              "Plan Width": 69
+                                            }
+                                          ]
+                                        }
+                                      ]
+                                    }
+                                  ]
+                                },
+                                {
+                                  "Node Type": "Sort",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 33.72,
+                                  "Total Cost": 34.82,
+                                  "Plan Rows": 440,
+                                  "Plan Width": 8,
+                                  "Sort Key": ["sd_1.server_id"],
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "server_info",
+                                      "Alias": "sd_1",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 14.40,
+                                      "Plan Rows": 440,
+                                      "Plan Width": 8
+                                    }
+                                  ]
+                                }
+                              ]
+                            },
+                            {
+                              "Node Type": "Materialize",
+                              "Parent Relationship": "Inner",
+                              "Startup Cost": 0.00,
+                              "Total Cost": 5.79,
+                              "Plan Rows": 14,
+                              "Plan Width": 378,
+                              "Plans": [
+                                {
+                                  "Node Type": "Seq Scan",
+                                  "Parent Relationship": "Outer",
+                                  "Relation Name": "probe",
+                                  "Alias": "p_2",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 5.72,
+                                  "Plan Rows": 14,
+                                  "Plan Width": 378,
+                                  "Filter": "((NOT deleted) AND (target_type_id = 300))"
+                                }
+                              ]
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  ]
+                },
+                {
+                  "Node Type": "Sort",
+                  "Parent Relationship": "Inner",
+                  "Startup Cost": 71.84,
+                  "Total Cost": 74.42,
+                  "Plan Rows": 1030,
+                  "Plan Width": 49,
+                  "Sort Key": ["c_2.server_id", "c_2.database_name"],
+                  "Plans": [
+                    {
+                      "Node Type": "Seq Scan",
+                      "Parent Relationship": "Outer",
+                      "Relation Name": "probe_config_database",
+                      "Alias": "c_2",
+                      "Startup Cost": 0.00,
+                      "Total Cost": 20.30,
+                      "Plan Rows": 1030,
+                      "Plan Width": 49
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "Node Type": "Hash",
+              "Parent Relationship": "Inner",
+              "Startup Cost": 8.23,
+              "Total Cost": 8.23,
+              "Plan Rows": 223,
+              "Plan Width": 541,
+              "Plans": [
+                {
+                  "Node Type": "Seq Scan",
+                  "Parent Relationship": "Outer",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv_1",
+                  "Startup Cost": 0.00,
+                  "Total Cost": 8.23,
+                  "Plan Rows": 223,
+                  "Plan Width": 541
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "Node Type": "Hash Join",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 141.91,
+          "Total Cost": 195.22,
+          "Plan Rows": 66,
+          "Plan Width": 945,
+          "Hash Cond": "((p_3.id = psv_2.probe_id) AND (sd_2.server_version_id = psv_2.server_version_id))",
+          "Filter": "(p_3.any_server_version OR (psv_2.probe_id IS NOT NULL))",
+          "Plans": [
+            {
+              "Node Type": "Hash Join",
+              "Parent Relationship": "Outer",
+              "Join Type": "Right",
+              "Startup Cost": 130.34,
+              "Total Cost": 180.50,
+              "Plan Rows": 66,
+              "Plan Width": 417,
+              "Hash Cond": "((c_3.probe_id = p_3.id) AND (c_3.server_id = b_2.server_id) AND ((c_3.database_name)::text = oc.database_name) AND ((c_3.schema_name)::text = oc.schema_name))",
+              "Plans": [
+                {
+                  "Node Type": "Seq Scan",
+                  "Parent Relationship": "Outer",
+                  "Relation Name": "probe_config_schema",
+                  "Alias": "c_3",
+                  "Startup Cost": 0.00,
+                  "Total Cost": 17.30,
+                  "Plan Rows": 730,
+                  "Plan Width": 81
+                },
+                {
+                  "Node Type": "Hash",
+                  "Parent Relationship": "Inner",
+                  "Startup Cost": 129.02,
+                  "Total Cost": 129.02,
+                  "Plan Rows": 66,
+                  "Plan Width": 408,
+                  "Plans": [
+                    {
+                      "Node Type": "Nested Loop",
+                      "Parent Relationship": "Outer",
+                      "Join Type": "Inner",
+                      "Startup Cost": 96.35,
+                      "Total Cost": 129.02,
+                      "Plan Rows": 66,
+                      "Plan Width": 408,
+                      "Join Filter": "(((p_3.agent_capability IS NULL) OR (p_3.agent_capability = ANY (a_3.agent_capability_list))) AND ((p_3.collection_method <> 'b'::bpchar) OR ((p_3.collection_method = 'b'::bpchar) AND (a_3.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_3.platform, p_3.platform) <> 0) OR ((a_3.platform !~ 'windows'::text) AND (p_3.platform = 'unix'::text))))))",
+                      "Plans": [
+                        {
+                          "Node Type": "Nested Loop",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Left",
+                          "Startup Cost": 96.35,
+                          "Total Cost": 119.71,
+                          "Plan Rows": 14,
+                          "Plan Width": 141,
+                          "Plans": [
+                            {
+                              "Node Type": "Nested Loop",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Inner",
+                              "Startup Cost": 96.20,
+                              "Total Cost": 115.72,
+                              "Plan Rows": 14,
+                              "Plan Width": 137,
+                              "Plans": [
+                                {
+                                  "Node Type": "Hash Join",
+                                  "Parent Relationship": "Outer",
+                                  "Join Type": "Inner",
+                                  "Startup Cost": 96.05,
+                                  "Total Cost": 111.69,
+                                  "Plan Rows": 14,
+                                  "Plan Width": 72,
+                                  "Hash Cond": "(b_2.server_id = ocd_1.server_id)",
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "agent_server_binding",
+                                      "Alias": "b_2",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 14.00,
+                                      "Plan Rows": 400,
+                                      "Plan Width": 8
+                                    },
+                                    {
+                                      "Node Type": "Hash",
+                                      "Parent Relationship": "Inner",
+                                      "Startup Cost": 95.97,
+                                      "Total Cost": 95.97,
+                                      "Plan Rows": 7,
+                                      "Plan Width": 72,
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Merge Join",
+                                          "Parent Relationship": "Outer",
+                                          "Join Type": "Inner",
+                                          "Startup Cost": 33.68,
+                                          "Total Cost": 95.97,
+                                          "Plan Rows": 7,
+                                          "Plan Width": 72,
+                                          "Merge Cond": "((oc.server_id = ocd_1.server_id) AND (oc.database_name = ocd_1.database_name))",
+                                          "Plans": [
+                                            {
+                                              "Node Type": "Index Only Scan",
+                                              "Parent Relationship": "Outer",
+                                              "Scan Direction": "Forward",
+                                              "Index Name": "oc_schema_pkey",
+                                              "Relation Name": "oc_schema",
+                                              "Alias": "oc",
+                                              "Startup Cost": 0.15,
+                                              "Total Cost": 55.70,
+                                              "Plan Rows": 770,
+                                              "Plan Width": 68
+                                            },
+                                            {
+                                              "Node Type": "Sort",
+                                              "Parent Relationship": "Inner",
+                                              "Startup Cost": 33.53,
+                                              "Total Cost": 34.47,
+                                              "Plan Rows": 375,
+                                              "Plan Width": 36,
+                                              "Sort Key": ["ocd_1.server_id", "ocd_1.database_name"],
+                                              "Plans": [
+                                                {
+                                                  "Node Type": "Seq Scan",
+                                                  "Parent Relationship": "Outer",
+                                                  "Relation Name": "oc_database",
+                                                  "Alias": "ocd_1",
+                                                  "Startup Cost": 0.00,
+                                                  "Total Cost": 17.50,
+                                                  "Plan Rows": 375,
+                                                  "Plan Width": 36,
+                                                  "Filter": "connections_allowed"
+                                                }
+                                              ]
+                                            }
+                                          ]
+                                        }
+                                      ]
+                                    }
+                                  ]
+                                },
+                                {
+                                  "Node Type": "Index Scan",
+                                  "Parent Relationship": "Inner",
+                                  "Scan Direction": "Forward",
+                                  "Index Name": "agent_pkey",
+                                  "Relation Name": "agent",
+                                  "Alias": "a_3",
+                                  "Startup Cost": 0.15,
+                                  "Total Cost": 0.28,
+                                  "Plan Rows": 1,
+                                  "Plan Width": 69,
+                                  "Index Cond": "(id = b_2.agent_id)"
+                                }
+                              ]
+                            },
+                            {
+                              "Node Type": "Index Scan",
+                              "Parent Relationship": "Inner",
+                              "Scan Direction": "Forward",
+                              "Index Name": "server_info_pkey",
+                              "Relation Name": "server_info",
+                              "Alias": "sd_2",
+                              "Startup Cost": 0.15,
+                              "Total Cost": 0.28,
+                              "Plan Rows": 1,
+                              "Plan Width": 8,
+                              "Index Cond": "(b_2.server_id = server_id)"
+                            }
+                          ]
+                        },
+                        {
+                          "Node Type": "Materialize",
+                          "Parent Relationship": "Inner",
+                          "Startup Cost": 0.00,
+                          "Total Cost": 5.75,
+                          "Plan Rows": 6,
+                          "Plan Width": 378,
+                          "Plans": [
+                            {
+                              "Node Type": "Seq Scan",
+                              "Parent Relationship": "Outer",
+                              "Relation Name": "probe",
+                              "Alias": "p_3",
+                              "Startup Cost": 0.00,
+                              "Total Cost": 5.72,
+                              "Plan Rows": 6,
+                              "Plan Width": 378,
+                              "Filter": "((NOT deleted) AND (target_type_id = 400))"
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "Node Type": "Hash",
+              "Parent Relationship": "Inner",
+              "Startup Cost": 8.23,
+              "Total Cost": 8.23,
+              "Plan Rows": 223,
+              "Plan Width": 541,
+              "Plans": [
+                {
+                  "Node Type": "Seq Scan",
+                  "Parent Relationship": "Outer",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv_2",
+                  "Startup Cost": 0.00,
+                  "Total Cost": 8.23,
+                  "Plan Rows": 223,
+                  "Plan Width": 541
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "Node Type": "Nested Loop",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 76.69,
+          "Total Cost": 115.46,
+          "Plan Rows": 8,
+          "Plan Width": 977,
+          "Join Filter": "(b_3.server_id = c_4.server_id)",
+          "Plans": [
+            {
+              "Node Type": "Nested Loop",
+              "Parent Relationship": "Outer",
+              "Join Type": "Left",
+              "Startup Cost": 76.54,
+              "Total Cost": 113.08,
+              "Plan Rows": 8,
+              "Plan Width": 968,
+              "Filter": "(p_4.any_server_version OR (psv_3.probe_id IS NOT NULL))",
+              "Plans": [
+                {
+                  "Node Type": "Nested Loop",
+                  "Parent Relationship": "Outer",
+                  "Join Type": "Left",
+                  "Startup Cost": 76.40,
+                  "Total Cost": 111.10,
+                  "Plan Rows": 8,
+                  "Plan Width": 440,
+                  "Plans": [
+                    {
+                      "Node Type": "Nested Loop",
+                      "Parent Relationship": "Outer",
+                      "Join Type": "Inner",
+                      "Startup Cost": 76.25,
+                      "Total Cost": 108.82,
+                      "Plan Rows": 8,
+                      "Plan Width": 436,
+                      "Join Filter": "(((p_4.agent_capability IS NULL) OR (p_4.agent_capability = ANY (a_4.agent_capability_list))) AND ((p_4.collection_method <> 'b'::bpchar) OR ((p_4.collection_method = 'b'::bpchar) AND (a_4.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_4.platform, p_4.platform) <> 0) OR ((a_4.platform !~ 'windows'::text) AND (p_4.platform = 'unix'::text))))))",
+                      "Plans": [
+                        {
+                          "Node Type": "Nested Loop",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Inner",
+                          "Startup Cost": 76.10,
+                          "Total Cost": 105.32,
+                          "Plan Rows": 11,
+                          "Plan Width": 482,
+                          "Join Filter": "(ocd_2.server_id = b_3.server_id)",
+                          "Plans": [
+                            {
+                              "Node Type": "Nested Loop",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Inner",
+                              "Startup Cost": 75.95,
+                              "Total Cost": 88.96,
+                              "Plan Rows": 5,
+                              "Plan Width": 482,
+                              "Plans": [
+                                {
+                                  "Node Type": "Merge Join",
+                                  "Parent Relationship": "Outer",
+                                  "Join Type": "Inner",
+                                  "Startup Cost": 75.95,
+                                  "Total Cost": 83.17,
+                                  "Plan Rows": 5,
+                                  "Plan Width": 104,
+                                  "Merge Cond": "((ocd_2.server_id = oc_1.server_id) AND (ocd_2.database_name = oc_1.database_name))",
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Outer",
+                                      "Startup Cost": 33.53,
+                                      "Total Cost": 34.47,
+                                      "Plan Rows": 375,
+                                      "Plan Width": 36,
+                                      "Sort Key": ["ocd_2.server_id", "ocd_2.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_database",
+                                          "Alias": "ocd_2",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 17.50,
+                                          "Plan Rows": 375,
+                                          "Plan Width": 36,
+                                          "Filter": "connections_allowed"
+                                        }
+                                      ]
+                                    },
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Inner",
+                                      "Startup Cost": 42.42,
+                                      "Total Cost": 43.87,
+                                      "Plan Rows": 580,
+                                      "Plan Width": 100,
+                                      "Sort Key": ["oc_1.server_id", "oc_1.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_table",
+                                          "Alias": "oc_1",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 15.80,
+                                          "Plan Rows": 580,
+                                          "Plan Width": 100
+                                        }
+                                      ]
+                                    }
+                                  ]
+                                },
+                                {
+                                  "Node Type": "Materialize",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 5.73,
+                                  "Plan Rows": 1,
+                                  "Plan Width": 378,
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "probe",
+                                      "Alias": "p_4",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 5.72,
+                                      "Plan Rows": 1,
+                                      "Plan Width": 378,
+                                      "Filter": "((NOT deleted) AND (target_type_id = 500))"
+                                    }
+                                  ]
+                                }
+                              ]
+                            },
+                            {
+                              "Node Type": "Index Only Scan",
+                              "Parent Relationship": "Inner",
+                              "Scan Direction": "Forward",
+                              "Index Name": "agent_server_binding_pkey",
+                              "Relation Name": "agent_server_binding",
+                              "Alias": "b_3",
+                              "Startup Cost": 0.15,
+                              "Total Cost": 3.25,
+                              "Plan Rows": 2,
+                              "Plan Width": 8,
+                              "Index Cond": "(server_id = oc_1.server_id)"
+                            }
+                          ]
+                        },
+                        {
+                          "Node Type": "Index Scan",
+                          "Parent Relationship": "Inner",
+                          "Scan Direction": "Forward",
+                          "Index Name": "agent_pkey",
+                          "Relation Name": "agent",
+                          "Alias": "a_4",
+                          "Startup Cost": 0.15,
+                          "Total Cost": 0.28,
+                          "Plan Rows": 1,
+                          "Plan Width": 69,
+                          "Index Cond": "(id = b_3.agent_id)"
+                        }
+                      ]
+                    },
+                    {
+                      "Node Type": "Index Scan",
+                      "Parent Relationship": "Inner",
+                      "Scan Direction": "Forward",
+                      "Index Name": "server_info_pkey",
+                      "Relation Name": "server_info",
+                      "Alias": "sd_3",
+                      "Startup Cost": 0.15,
+                      "Total Cost": 0.28,
+                      "Plan Rows": 1,
+                      "Plan Width": 8,
+                      "Index Cond": "(b_3.server_id = server_id)"
+                    }
+                  ]
+                },
+                {
+                  "Node Type": "Index Scan",
+                  "Parent Relationship": "Inner",
+                  "Scan Direction": "Forward",
+                  "Index Name": "probe_server_version_unique",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv_3",
+                  "Startup Cost": 0.14,
+                  "Total Cost": 0.24,
+                  "Plan Rows": 1,
+                  "Plan Width": 541,
+                  "Index Cond": "((p_4.id = probe_id) AND (sd_3.server_version_id = server_version_id))"
+                }
+              ]
+            },
+            {
+              "Node Type": "Index Scan",
+              "Parent Relationship": "Inner",
+              "Scan Direction": "Forward",
+              "Index Name": "probe_config_table_pkey",
+              "Relation Name": "probe_config_table",
+              "Alias": "c_4",
+              "Startup Cost": 0.15,
+              "Total Cost": 0.28,
+              "Plan Rows": 1,
+              "Plan Width": 113,
+              "Index Cond": "((p_4.id = probe_id) AND (oc_1.database_name = (database_name)::text) AND (oc_1.schema_name = (schema_name)::text) AND (oc_1.table_name = (table_name)::text))"
+            }
+          ]
+        },
+        {
+          "Node Type": "Nested Loop",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 65.56,
+          "Total Cost": 97.23,
+          "Plan Rows": 6,
+          "Plan Width": 977,
+          "Join Filter": "((oc_2.database_name = (c_5.database_name)::text) AND (oc_2.schema_name = (c_5.schema_name)::text) AND (oc_2.index_name = (c_5.index_name)::text))",
+          "Plans": [
+            {
+              "Node Type": "Nested Loop",
+              "Parent Relationship": "Outer",
+              "Join Type": "Left",
+              "Startup Cost": 65.41,
+              "Total Cost": 95.42,
+              "Plan Rows": 6,
+              "Plan Width": 968,
+              "Filter": "(p_5.any_server_version OR (psv_4.probe_id IS NOT NULL))",
+              "Plans": [
+                {
+                  "Node Type": "Nested Loop",
+                  "Parent Relationship": "Outer",
+                  "Join Type": "Left",
+                  "Startup Cost": 65.26,
+                  "Total Cost": 93.93,
+                  "Plan Rows": 6,
+                  "Plan Width": 440,
+                  "Plans": [
+                    {
+                      "Node Type": "Nested Loop",
+                      "Parent Relationship": "Outer",
+                      "Join Type": "Inner",
+                      "Startup Cost": 65.12,
+                      "Total Cost": 92.22,
+                      "Plan Rows": 6,
+                      "Plan Width": 436,
+                      "Join Filter": "(((p_5.agent_capability IS NULL) OR (p_5.agent_capability = ANY (a_5.agent_capability_list))) AND ((p_5.collection_method <> 'b'::bpchar) OR ((p_5.collection_method = 'b'::bpchar) AND (a_5.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_5.platform, p_5.platform) <> 0) OR ((a_5.platform !~ 'windows'::text) AND (p_5.platform = 'unix'::text))))))",
+                      "Plans": [
+                        {
+                          "Node Type": "Nested Loop",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Inner",
+                          "Startup Cost": 64.97,
+                          "Total Cost": 89.68,
+                          "Plan Rows": 8,
+                          "Plan Width": 482,
+                          "Join Filter": "(ocd_3.server_id = b_4.server_id)",
+                          "Plans": [
+                            {
+                              "Node Type": "Nested Loop",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Inner",
+                              "Startup Cost": 64.82,
+                              "Total Cost": 76.45,
+                              "Plan Rows": 4,
+                              "Plan Width": 482,
+                              "Plans": [
+                                {
+                                  "Node Type": "Merge Join",
+                                  "Parent Relationship": "Outer",
+                                  "Join Type": "Inner",
+                                  "Startup Cost": 64.82,
+                                  "Total Cost": 70.67,
+                                  "Plan Rows": 4,
+                                  "Plan Width": 104,
+                                  "Merge Cond": "((ocd_3.server_id = oc_2.server_id) AND (ocd_3.database_name = oc_2.database_name))",
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Outer",
+                                      "Startup Cost": 33.53,
+                                      "Total Cost": 34.47,
+                                      "Plan Rows": 375,
+                                      "Plan Width": 36,
+                                      "Sort Key": ["ocd_3.server_id", "ocd_3.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_database",
+                                          "Alias": "ocd_3",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 17.50,
+                                          "Plan Rows": 375,
+                                          "Plan Width": 36,
+                                          "Filter": "connections_allowed"
+                                        }
+                                      ]
+                                    },
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Inner",
+                                      "Startup Cost": 31.29,
+                                      "Total Cost": 32.29,
+                                      "Plan Rows": 400,
+                                      "Plan Width": 100,
+                                      "Sort Key": ["oc_2.server_id", "oc_2.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_index",
+                                          "Alias": "oc_2",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 14.00,
+                                          "Plan Rows": 400,
+                                          "Plan Width": 100
+                                        }
+                                      ]
+                                    }
+                                  ]
+                                },
+                                {
+                                  "Node Type": "Materialize",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 5.73,
+                                  "Plan Rows": 1,
+                                  "Plan Width": 378,
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "probe",
+                                      "Alias": "p_5",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 5.72,
+                                      "Plan Rows": 1,
+                                      "Plan Width": 378,
+                                      "Filter": "((NOT deleted) AND (target_type_id = 600))"
+                                    }
+                                  ]
+                                }
+                              ]
+                            },
+                            {
+                              "Node Type": "Index Only Scan",
+                              "Parent Relationship": "Inner",
+                              "Scan Direction": "Forward",
+                              "Index Name": "agent_server_binding_pkey",
+                              "Relation Name": "agent_server_binding",
+                              "Alias": "b_4",
+                              "Startup Cost": 0.15,
+                              "Total Cost": 3.28,
+                              "Plan Rows": 2,
+                              "Plan Width": 8,
+                              "Index Cond": "(server_id = oc_2.server_id)"
+                            }
+                          ]
+                        },
+                        {
+                          "Node Type": "Index Scan",
+                          "Parent Relationship": "Inner",
+                          "Scan Direction": "Forward",
+                          "Index Name": "agent_pkey",
+                          "Relation Name": "agent",
+                          "Alias": "a_5",
+                          "Startup Cost": 0.15,
+                          "Total Cost": 0.28,
+                          "Plan Rows": 1,
+                          "Plan Width": 69,
+                          "Index Cond": "(id = b_4.agent_id)"
+                        }
+                      ]
+                    },
+                    {
+                      "Node Type": "Index Scan",
+                      "Parent Relationship": "Inner",
+                      "Scan Direction": "Forward",
+                      "Index Name": "server_info_pkey",
+                      "Relation Name": "server_info",
+                      "Alias": "sd_4",
+                      "Startup Cost": 0.15,
+                      "Total Cost": 0.28,
+                      "Plan Rows": 1,
+                      "Plan Width": 8,
+                      "Index Cond": "(b_4.server_id = server_id)"
+                    }
+                  ]
+                },
+                {
+                  "Node Type": "Index Scan",
+                  "Parent Relationship": "Inner",
+                  "Scan Direction": "Forward",
+                  "Index Name": "probe_server_version_unique",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv_4",
+                  "Startup Cost": 0.14,
+                  "Total Cost": 0.24,
+                  "Plan Rows": 1,
+                  "Plan Width": 541,
+                  "Index Cond": "((p_5.id = probe_id) AND (sd_4.server_version_id = server_version_id))"
+                }
+              ]
+            },
+            {
+              "Node Type": "Index Scan",
+              "Parent Relationship": "Inner",
+              "Scan Direction": "Forward",
+              "Index Name": "probe_config_index_pkey",
+              "Relation Name": "probe_config_index",
+              "Alias": "c_5",
+              "Startup Cost": 0.15,
+              "Total Cost": 0.28,
+              "Plan Rows": 1,
+              "Plan Width": 113,
+              "Index Cond": "((p_5.id = probe_id) AND (b_4.server_id = server_id))"
+            }
+          ]
+        },
+        {
+          "Node Type": "Nested Loop",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 84.55,
+          "Total Cost": 115.54,
+          "Plan Rows": 8,
+          "Plan Width": 977,
+          "Join Filter": "(b_5.server_id = c_6.server_id)",
+          "Plans": [
+            {
+              "Node Type": "Nested Loop",
+              "Parent Relationship": "Outer",
+              "Join Type": "Left",
+              "Startup Cost": 84.40,
+              "Total Cost": 113.17,
+              "Plan Rows": 8,
+              "Plan Width": 968,
+              "Filter": "(p_6.any_server_version OR (psv_5.probe_id IS NOT NULL))",
+              "Plans": [
+                {
+                  "Node Type": "Nested Loop",
+                  "Parent Relationship": "Outer",
+                  "Join Type": "Left",
+                  "Startup Cost": 84.25,
+                  "Total Cost": 111.19,
+                  "Plan Rows": 8,
+                  "Plan Width": 440,
+                  "Plans": [
+                    {
+                      "Node Type": "Nested Loop",
+                      "Parent Relationship": "Outer",
+                      "Join Type": "Inner",
+                      "Startup Cost": 84.11,
+                      "Total Cost": 108.91,
+                      "Plan Rows": 8,
+                      "Plan Width": 436,
+                      "Join Filter": "(((p_6.agent_capability IS NULL) OR (p_6.agent_capability = ANY (a_6.agent_capability_list))) AND ((p_6.collection_method <> 'b'::bpchar) OR ((p_6.collection_method = 'b'::bpchar) AND (a_6.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_6.platform, p_6.platform) <> 0) OR ((a_6.platform !~ 'windows'::text) AND (p_6.platform = 'unix'::text))))))",
+                      "Plans": [
+                        {
+                          "Node Type": "Nested Loop",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Inner",
+                          "Startup Cost": 83.96,
+                          "Total Cost": 105.41,
+                          "Plan Rows": 11,
+                          "Plan Width": 482,
+                          "Plans": [
+                            {
+                              "Node Type": "Seq Scan",
+                              "Parent Relationship": "Outer",
+                              "Relation Name": "probe",
+                              "Alias": "p_6",
+                              "Startup Cost": 0.00,
+                              "Total Cost": 5.72,
+                              "Plan Rows": 1,
+                              "Plan Width": 378,
+                              "Filter": "((NOT deleted) AND (target_type_id = 700))"
+                            },
+                            {
+                              "Node Type": "Hash Join",
+                              "Parent Relationship": "Inner",
+                              "Join Type": "Inner",
+                              "Startup Cost": 83.96,
+                              "Total Cost": 99.58,
+                              "Plan Rows": 11,
+                              "Plan Width": 104,
+                              "Hash Cond": "(b_5.server_id = ocd_4.server_id)",
+                              "Plans": [
+                                {
+                                  "Node Type": "Seq Scan",
+                                  "Parent Relationship": "Outer",
+                                  "Relation Name": "agent_server_binding",
+                                  "Alias": "b_5",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 14.00,
+                                  "Plan Rows": 400,
+                                  "Plan Width": 8
+                                },
+                                {
+                                  "Node Type": "Hash",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 83.88,
+                                  "Total Cost": 83.88,
+                                  "Plan Rows": 6,
+                                  "Plan Width": 104,
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Merge Join",
+                                      "Parent Relationship": "Outer",
+                                      "Join Type": "Inner",
+                                      "Startup Cost": 76.59,
+                                      "Total Cost": 83.88,
+                                      "Plan Rows": 6,
+                                      "Plan Width": 104,
+                                      "Merge Cond": "((ocd_4.server_id = oc_3.server_id) AND (ocd_4.database_name = oc_3.database_name))",
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Sort",
+                                          "Parent Relationship": "Outer",
+                                          "Startup Cost": 33.53,
+                                          "Total Cost": 34.47,
+                                          "Plan Rows": 375,
+                                          "Plan Width": 36,
+                                          "Sort Key": ["ocd_4.server_id", "ocd_4.database_name"],
+                                          "Plans": [
+                                            {
+                                              "Node Type": "Seq Scan",
+                                              "Parent Relationship": "Outer",
+                                              "Relation Name": "oc_database",
+                                              "Alias": "ocd_4",
+                                              "Startup Cost": 0.00,
+                                              "Total Cost": 17.50,
+                                              "Plan Rows": 375,
+                                              "Plan Width": 36,
+                                              "Filter": "connections_allowed"
+                                            }
+                                          ]
+                                        },
+                                        {
+                                          "Node Type": "Sort",
+                                          "Parent Relationship": "Inner",
+                                          "Startup Cost": 43.05,
+                                          "Total Cost": 44.53,
+                                          "Plan Rows": 590,
+                                          "Plan Width": 100,
+                                          "Sort Key": ["oc_3.server_id", "oc_3.database_name"],
+                                          "Plans": [
+                                            {
+                                              "Node Type": "Seq Scan",
+                                              "Parent Relationship": "Outer",
+                                              "Relation Name": "oc_sequence",
+                                              "Alias": "oc_3",
+                                              "Startup Cost": 0.00,
+                                              "Total Cost": 15.90,
+                                              "Plan Rows": 590,
+                                              "Plan Width": 100
+                                            }
+                                          ]
+                                        }
+                                      ]
+                                    }
+                                  ]
+                                }
+                              ]
+                            }
+                          ]
+                        },
+                        {
+                          "Node Type": "Index Scan",
+                          "Parent Relationship": "Inner",
+                          "Scan Direction": "Forward",
+                          "Index Name": "agent_pkey",
+                          "Relation Name": "agent",
+                          "Alias": "a_6",
+                          "Startup Cost": 0.15,
+                          "Total Cost": 0.28,
+                          "Plan Rows": 1,
+                          "Plan Width": 69,
+                          "Index Cond": "(id = b_5.agent_id)"
+                        }
+                      ]
+                    },
+                    {
+                      "Node Type": "Index Scan",
+                      "Parent Relationship": "Inner",
+                      "Scan Direction": "Forward",
+                      "Index Name": "server_info_pkey",
+                      "Relation Name": "server_info",
+                      "Alias": "sd_5",
+                      "Startup Cost": 0.15,
+                      "Total Cost": 0.28,
+                      "Plan Rows": 1,
+                      "Plan Width": 8,
+                      "Index Cond": "(b_5.server_id = server_id)"
+                    }
+                  ]
+                },
+                {
+                  "Node Type": "Index Scan",
+                  "Parent Relationship": "Inner",
+                  "Scan Direction": "Forward",
+                  "Index Name": "probe_server_version_unique",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv_5",
+                  "Startup Cost": 0.14,
+                  "Total Cost": 0.24,
+                  "Plan Rows": 1,
+                  "Plan Width": 541,
+                  "Index Cond": "((p_6.id = probe_id) AND (sd_5.server_version_id = server_version_id))"
+                }
+              ]
+            },
+            {
+              "Node Type": "Index Scan",
+              "Parent Relationship": "Inner",
+              "Scan Direction": "Forward",
+              "Index Name": "probe_config_sequence_pkey",
+              "Relation Name": "probe_config_sequence",
+              "Alias": "c_6",
+              "Startup Cost": 0.15,
+              "Total Cost": 0.28,
+              "Plan Rows": 1,
+              "Plan Width": 113,
+              "Index Cond": "((p_6.id = probe_id) AND (oc_3.database_name = (database_name)::text) AND (oc_3.schema_name = (schema_name)::text) AND (oc_3.sequence_name = (sequence_name)::text))"
+            }
+          ]
+        },
+        {
+          "Node Type": "Nested Loop",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 61.97,
+          "Total Cost": 88.40,
+          "Plan Rows": 5,
+          "Plan Width": 977,
+          "Join Filter": "((oc_4.database_name = (c_7.database_name)::text) AND (oc_4.schema_name = (c_7.schema_name)::text) AND (oc_4.function_name = (c_7.function_name)::text))",
+          "Plans": [
+            {
+              "Node Type": "Nested Loop",
+              "Parent Relationship": "Outer",
+              "Join Type": "Left",
+              "Startup Cost": 61.82,
+              "Total Cost": 86.88,
+              "Plan Rows": 5,
+              "Plan Width": 968,
+              "Filter": "(p_7.any_server_version OR (psv_6.probe_id IS NOT NULL))",
+              "Plans": [
+                {
+                  "Node Type": "Nested Loop",
+                  "Parent Relationship": "Outer",
+                  "Join Type": "Left",
+                  "Startup Cost": 61.67,
+                  "Total Cost": 85.65,
+                  "Plan Rows": 5,
+                  "Plan Width": 440,
+                  "Plans": [
+                    {
+                      "Node Type": "Nested Loop",
+                      "Parent Relationship": "Outer",
+                      "Join Type": "Inner",
+                      "Startup Cost": 61.52,
+                      "Total Cost": 84.22,
+                      "Plan Rows": 5,
+                      "Plan Width": 436,
+                      "Join Filter": "(((p_7.agent_capability IS NULL) OR (p_7.agent_capability = ANY (a_7.agent_capability_list))) AND ((p_7.collection_method <> 'b'::bpchar) OR ((p_7.collection_method = 'b'::bpchar) AND (a_7.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_7.platform, p_7.platform) <> 0) OR ((a_7.platform !~ 'windows'::text) AND (p_7.platform = 'unix'::text))))))",
+                      "Plans": [
+                        {
+                          "Node Type": "Nested Loop",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Inner",
+                          "Startup Cost": 61.38,
+                          "Total Cost": 82.32,
+                          "Plan Rows": 6,
+                          "Plan Width": 482,
+                          "Plans": [
+                            {
+                              "Node Type": "Nested Loop",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Inner",
+                              "Startup Cost": 61.23,
+                              "Total Cost": 72.39,
+                              "Plan Rows": 3,
+                              "Plan Width": 482,
+                              "Plans": [
+                                {
+                                  "Node Type": "Merge Join",
+                                  "Parent Relationship": "Outer",
+                                  "Join Type": "Inner",
+                                  "Startup Cost": 61.23,
+                                  "Total Cost": 66.62,
+                                  "Plan Rows": 3,
+                                  "Plan Width": 104,
+                                  "Merge Cond": "((ocd_5.server_id = oc_4.server_id) AND (ocd_5.database_name = oc_4.database_name))",
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Outer",
+                                      "Startup Cost": 33.53,
+                                      "Total Cost": 34.47,
+                                      "Plan Rows": 375,
+                                      "Plan Width": 36,
+                                      "Sort Key": ["ocd_5.server_id", "ocd_5.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_database",
+                                          "Alias": "ocd_5",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 17.50,
+                                          "Plan Rows": 375,
+                                          "Plan Width": 36,
+                                          "Filter": "connections_allowed"
+                                        }
+                                      ]
+                                    },
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Inner",
+                                      "Startup Cost": 27.70,
+                                      "Total Cost": 28.55,
+                                      "Plan Rows": 340,
+                                      "Plan Width": 100,
+                                      "Sort Key": ["oc_4.server_id", "oc_4.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_function",
+                                          "Alias": "oc_4",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 13.40,
+                                          "Plan Rows": 340,
+                                          "Plan Width": 100
+                                        }
+                                      ]
+                                    }
+                                  ]
+                                },
+                                {
+                                  "Node Type": "Materialize",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 5.73,
+                                  "Plan Rows": 1,
+                                  "Plan Width": 378,
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "probe",
+                                      "Alias": "p_7",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 5.72,
+                                      "Plan Rows": 1,
+                                      "Plan Width": 378,
+                                      "Filter": "((NOT deleted) AND (target_type_id = 800))"
+                                    }
+                                  ]
+                                }
+                              ]
+                            },
+                            {
+                              "Node Type": "Index Only Scan",
+                              "Parent Relationship": "Inner",
+                              "Scan Direction": "Forward",
+                              "Index Name": "agent_server_binding_pkey",
+                              "Relation Name": "agent_server_binding",
+                              "Alias": "b_6",
+                              "Startup Cost": 0.15,
+                              "Total Cost": 3.29,
+                              "Plan Rows": 2,
+                              "Plan Width": 8,
+                              "Index Cond": "(server_id = ocd_5.server_id)"
+                            }
+                          ]
+                        },
+                        {
+                          "Node Type": "Index Scan",
+                          "Parent Relationship": "Inner",
+                          "Scan Direction": "Forward",
+                          "Index Name": "agent_pkey",
+                          "Relation Name": "agent",
+                          "Alias": "a_7",
+                          "Startup Cost": 0.15,
+                          "Total Cost": 0.28,
+                          "Plan Rows": 1,
+                          "Plan Width": 69,
+                          "Index Cond": "(id = b_6.agent_id)"
+                        }
+                      ]
+                    },
+                    {
+                      "Node Type": "Index Scan",
+                      "Parent Relationship": "Inner",
+                      "Scan Direction": "Forward",
+                      "Index Name": "server_info_pkey",
+                      "Relation Name": "server_info",
+                      "Alias": "sd_6",
+                      "Startup Cost": 0.15,
+                      "Total Cost": 0.28,
+                      "Plan Rows": 1,
+                      "Plan Width": 8,
+                      "Index Cond": "(b_6.server_id = server_id)"
+                    }
+                  ]
+                },
+                {
+                  "Node Type": "Index Scan",
+                  "Parent Relationship": "Inner",
+                  "Scan Direction": "Forward",
+                  "Index Name": "probe_server_version_unique",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv_6",
+                  "Startup Cost": 0.14,
+                  "Total Cost": 0.24,
+                  "Plan Rows": 1,
+                  "Plan Width": 541,
+                  "Index Cond": "((p_7.id = probe_id) AND (sd_6.server_version_id = server_version_id))"
+                }
+              ]
+            },
+            {
+              "Node Type": "Index Scan",
+              "Parent Relationship": "Inner",
+              "Scan Direction": "Forward",
+              "Index Name": "probe_config_function_pkey",
+              "Relation Name": "probe_config_function",
+              "Alias": "c_7",
+              "Startup Cost": 0.15,
+              "Total Cost": 0.28,
+              "Plan Rows": 1,
+              "Plan Width": 113,
+              "Index Cond": "((p_7.id = probe_id) AND (b_6.server_id = server_id))"
+            }
+          ]
+        },
+        {
+          "Node Type": "Nested Loop",
+          "Parent Relationship": "Member",
+          "Join Type": "Left",
+          "Startup Cost": 61.97,
+          "Total Cost": 88.40,
+          "Plan Rows": 5,
+          "Plan Width": 977,
+          "Join Filter": "((oc_5.database_name = (c_8.database_name)::text) AND (oc_5.schema_name = (c_8.schema_name)::text) AND (oc_5.view_name = (c_8.view_name)::text))",
+          "Plans": [
+            {
+              "Node Type": "Nested Loop",
+              "Parent Relationship": "Outer",
+              "Join Type": "Left",
+              "Startup Cost": 61.82,
+              "Total Cost": 86.88,
+              "Plan Rows": 5,
+              "Plan Width": 968,
+              "Filter": "(p_8.any_server_version OR (psv_7.probe_id IS NOT NULL))",
+              "Plans": [
+                {
+                  "Node Type": "Nested Loop",
+                  "Parent Relationship": "Outer",
+                  "Join Type": "Left",
+                  "Startup Cost": 61.67,
+                  "Total Cost": 85.65,
+                  "Plan Rows": 5,
+                  "Plan Width": 440,
+                  "Plans": [
+                    {
+                      "Node Type": "Nested Loop",
+                      "Parent Relationship": "Outer",
+                      "Join Type": "Inner",
+                      "Startup Cost": 61.52,
+                      "Total Cost": 84.22,
+                      "Plan Rows": 5,
+                      "Plan Width": 436,
+                      "Join Filter": "(((p_8.agent_capability IS NULL) OR (p_8.agent_capability = ANY (a_8.agent_capability_list))) AND ((p_8.collection_method <> 'b'::bpchar) OR ((p_8.collection_method = 'b'::bpchar) AND (a_8.agent_capability_list @> '{allow_batch_probes}'::text[]) AND ((strpos(a_8.platform, p_8.platform) <> 0) OR ((a_8.platform !~ 'windows'::text) AND (p_8.platform = 'unix'::text))))))",
+                      "Plans": [
+                        {
+                          "Node Type": "Nested Loop",
+                          "Parent Relationship": "Outer",
+                          "Join Type": "Inner",
+                          "Startup Cost": 61.38,
+                          "Total Cost": 82.32,
+                          "Plan Rows": 6,
+                          "Plan Width": 482,
+                          "Plans": [
+                            {
+                              "Node Type": "Nested Loop",
+                              "Parent Relationship": "Outer",
+                              "Join Type": "Inner",
+                              "Startup Cost": 61.23,
+                              "Total Cost": 72.39,
+                              "Plan Rows": 3,
+                              "Plan Width": 482,
+                              "Plans": [
+                                {
+                                  "Node Type": "Merge Join",
+                                  "Parent Relationship": "Outer",
+                                  "Join Type": "Inner",
+                                  "Startup Cost": 61.23,
+                                  "Total Cost": 66.62,
+                                  "Plan Rows": 3,
+                                  "Plan Width": 104,
+                                  "Merge Cond": "((ocd_6.server_id = oc_5.server_id) AND (ocd_6.database_name = oc_5.database_name))",
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Outer",
+                                      "Startup Cost": 33.53,
+                                      "Total Cost": 34.47,
+                                      "Plan Rows": 375,
+                                      "Plan Width": 36,
+                                      "Sort Key": ["ocd_6.server_id", "ocd_6.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_database",
+                                          "Alias": "ocd_6",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 17.50,
+                                          "Plan Rows": 375,
+                                          "Plan Width": 36,
+                                          "Filter": "connections_allowed"
+                                        }
+                                      ]
+                                    },
+                                    {
+                                      "Node Type": "Sort",
+                                      "Parent Relationship": "Inner",
+                                      "Startup Cost": 27.70,
+                                      "Total Cost": 28.55,
+                                      "Plan Rows": 340,
+                                      "Plan Width": 100,
+                                      "Sort Key": ["oc_5.server_id", "oc_5.database_name"],
+                                      "Plans": [
+                                        {
+                                          "Node Type": "Seq Scan",
+                                          "Parent Relationship": "Outer",
+                                          "Relation Name": "oc_views",
+                                          "Alias": "oc_5",
+                                          "Startup Cost": 0.00,
+                                          "Total Cost": 13.40,
+                                          "Plan Rows": 340,
+                                          "Plan Width": 100
+                                        }
+                                      ]
+                                    }
+                                  ]
+                                },
+                                {
+                                  "Node Type": "Materialize",
+                                  "Parent Relationship": "Inner",
+                                  "Startup Cost": 0.00,
+                                  "Total Cost": 5.73,
+                                  "Plan Rows": 1,
+                                  "Plan Width": 378,
+                                  "Plans": [
+                                    {
+                                      "Node Type": "Seq Scan",
+                                      "Parent Relationship": "Outer",
+                                      "Relation Name": "probe",
+                                      "Alias": "p_8",
+                                      "Startup Cost": 0.00,
+                                      "Total Cost": 5.72,
+                                      "Plan Rows": 1,
+                                      "Plan Width": 378,
+                                      "Filter": "((NOT deleted) AND (target_type_id = 900))"
+                                    }
+                                  ]
+                                }
+                              ]
+                            },
+                            {
+                              "Node Type": "Index Only Scan",
+                              "Parent Relationship": "Inner",
+                              "Scan Direction": "Forward",
+                              "Index Name": "agent_server_binding_pkey",
+                              "Relation Name": "agent_server_binding",
+                              "Alias": "b_7",
+                              "Startup Cost": 0.15,
+                              "Total Cost": 3.29,
+                              "Plan Rows": 2,
+                              "Plan Width": 8,
+                              "Index Cond": "(server_id = ocd_6.server_id)"
+                            }
+                          ]
+                        },
+                        {
+                          "Node Type": "Index Scan",
+                          "Parent Relationship": "Inner",
+                          "Scan Direction": "Forward",
+                          "Index Name": "agent_pkey",
+                          "Relation Name": "agent",
+                          "Alias": "a_8",
+                          "Startup Cost": 0.15,
+                          "Total Cost": 0.28,
+                          "Plan Rows": 1,
+                          "Plan Width": 69,
+                          "Index Cond": "(id = b_7.agent_id)"
+                        }
+                      ]
+                    },
+                    {
+                      "Node Type": "Index Scan",
+                      "Parent Relationship": "Inner",
+                      "Scan Direction": "Forward",
+                      "Index Name": "server_info_pkey",
+                      "Relation Name": "server_info",
+                      "Alias": "sd_7",
+                      "Startup Cost": 0.15,
+                      "Total Cost": 0.28,
+                      "Plan Rows": 1,
+                      "Plan Width": 8,
+                      "Index Cond": "(b_7.server_id = server_id)"
+                    }
+                  ]
+                },
+                {
+                  "Node Type": "Index Scan",
+                  "Parent Relationship": "Inner",
+                  "Scan Direction": "Forward",
+                  "Index Name": "probe_server_version_unique",
+                  "Relation Name": "probe_server_version",
+                  "Alias": "psv_7",
+                  "Startup Cost": 0.14,
+                  "Total Cost": 0.24,
+                  "Plan Rows": 1,
+                  "Plan Width": 541,
+                  "Index Cond": "((p_8.id = probe_id) AND (sd_7.server_version_id = server_version_id))"
+                }
+              ]
+            },
+            {
+              "Node Type": "Index Scan",
+              "Parent Relationship": "Inner",
+              "Scan Direction": "Forward",
+              "Index Name": "probe_config_view_pkey",
+              "Relation Name": "probe_config_view",
+              "Alias": "c_8",
+              "Startup Cost": 0.15,
+              "Total Cost": 0.28,
+              "Plan Rows": 1,
+              "Plan Width": 113,
+              "Index Cond": "((p_8.id = probe_id) AND (b_7.server_id = server_id))"
+            }
+          ]
+        }
+      ]
+    }
+  }
+];
+      pgExplain.DrawJSONPlan($('#explain-container'), data);
+    },
+    function() {
+      /* TODO:: Show proper error dialog */
+      console.log(arguments);
+    });
+} catch (err) {
+  /* Show proper error dialog */
+  console.log(err);
+}
+{% endblock %}
+{% block body %}
+<div id="explain-container"></div>
+{% endblock %}
+
+{% block css_link %}
+<style>
+body {
+  padding-top: 20px;
+  overflow: auto;
+}
+
+.label {
+  display: table-cell;
+  text-align: left;
+  white-space: inherit;
+}
+</style>
+{% endblock %}

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

---

From: Sanket Mehta <[email-masked]>
Date: Tue, 12 Apr 2016 16:06:59 +0530

Hi,

Please do ignore previous patch as it contains trailing whitespaces.
PFA the revised patch after resolving this issue.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

> TODO: Integration of graphical explain into sql editor
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..d715d0c 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -9,25 +9,49 @@

-import datetime
 
 MODULE_NAME = 'misc'

 
-
 
     return "PING"
+

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..ef0fb6d
@@ -0,0 +1,8149 @@

---

From: Dave Page <[email-masked]>
Date: Wed, 13 Apr 2016 09:53:03 +0100

Hi,

A couple of comments following a quick test:

1) Some of the horizontal lines are not actually horizontal - they
noticeably drop down. Please see the attached screenshot.

2) I thought we were going to use a "viewport" to allow the user to
navigate through the diagram? I was thinking of displaying a thumbnail
with viewport in the top-left, with the main view being synchronised
to the viewport position/zoom level - for example, look at the 2D Zoom
example on http://www.ajax-zoom.com/. Something like Croppie might
help: http://foliotek.github.io/Croppie/ (as ajax-zoom is not free).

On Tue, Apr 12, 2016 at 11:36 AM, Sanket Mehta
<[email-masked]> wrote:
> On Mon, Apr 11, 2016 at 10:04 PM, Sanket Mehta
> <[email-masked]> wrote:
>>
>> Hi,
>>
>> http://localhost:5050/misc/explain where 5050 is the port your web server
>> is running on
>>

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Sanket Mehta <[email-masked]>
Date: Mon, 18 Apr 2016 12:48:42 +0530

Hi Dave,

Regarding your second comment,
Almost all the libraries that I have checked uses Jquery UI which is quite
heavy.
Please share your comments about using these libraries.

Meanwhile currently I have implemented zoom in/out functionality in
graphical explain.
I have added two buttons on left top corner of panel (see the attached
screen shot),
which you can use to zoom in/out the graph.

Please do review it and let me know your comments about them.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

---

From: Dave Page <[email-masked]>
Date: Mon, 18 Apr 2016 13:08:20 +0100

> Please share your comments about using these libraries.

What have you found?

> Please do review it and let me know your comments about them.

I have tested the code, so no screenshots necessary :-).

The problem with the zoom is that it doesn't give you any visual indicators
that there is something on the canvas. With a big plan, it'll just look
blank, unless the user starts scrolling/zooming. The viewport will give
instant hints to the user that there's a big plan there, albeit out of
view, and allow them to very easily navigate it.

>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Wed, Apr 13, 2016 at 2:23 PM, Dave Page <[email-masked]
> <javascript:_e(%7B%7D,'cvml',[email-masked]');>> wrote:
>
>> Hi,
>>
>> <[email-masked]
>> <javascript:_e(%7B%7D,'cvml',[email-masked]');>> wrote:
>> > Hi,
>> >
>> > <[email-masked]
>> <javascript:_e(%7B%7D,'cvml',[email-masked]');>> wrote:
>> >>
>> >> Hi,
>> >>
>> > Sent via pgadmin-hackers mailing list ([email-masked]
>> <javascript:_e(%7B%7D,'cvml',[email-masked]');>)

---

From: Sanket Mehta <[email-masked]>
Date: Tue, 19 Apr 2016 15:29:59 +0530

Hi,

On Mon, Apr 18, 2016 at 5:38 PM, Dave Page <[email-masked]> wrote:

>> Almost all the libraries that I have checked uses Jquery UI which is
>> quite heavy.
Below are the libraries I have checked out which uses JQUERY UI.

 - https://github.com/princejwesley/minimap/blob/master/src/minimap.js

 - https://github.com/samcroft/mini-map (alpha release only)

 -
http://www.jqueryscript.net/text/Sublime-Text-Like-Web-Page-Minimap-Plugin-For-jQuery.html

 -
http://www.jqueryscript.net/other/jQuery-Plugin-To-Create-A-Flexible-Web-Page-Minimap-minimap-js.html

 - https://github.com/goldenapples/jquery.minimap

 - https://www.mapbox.com/mapbox.js/example/v1.0.0/leaflet-minimap/ (not
free)

 - http://codepen.io/billdwhite/pen/lCAdi (depends upon d3 library)

 - http://blog.urvatechlabs.com/minimap-jquery-plugin/

 - http://jsfiddle.net/ARTsinn/gbSSw/

 - http://jsfiddle.net/danimt/ARVWA/ (depends upon L library)

 - http://atom-minimap.github.io/minimap/

 - http://js-tutorial.com/minimap-a-preview-of-the-full-web-page-1334

Some of them uses or depends upon different libraries (mentioned besides
the link)

> The problem with the zoom is that it doesn't give you any visual
> indicators that there is something on the canvas. With a big plan, it'll
> just look blank, unless the user starts scrolling/zooming. The viewport
> will give instant hints to the user that there's a big plan there, albeit
> out of view, and allow them to very easily navigate it.
>
>
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>

---

From: Dave Page <[email-masked]>
Date: Wed, 20 Apr 2016 08:42:44 -0400

On Tuesday, April 19, 2016, Sanket Mehta <[email-masked]>
wrote:

> Hi,
>
> On Mon, Apr 18, 2016 at 5:38 PM, Dave Page <[email-masked]
>> On Monday, April 18, 2016, Sanket Mehta <[email-masked]

That one would work wound't it? Assuming it can do x and y movement.

---

From: Sanket Mehta <[email-masked]>
Date: Wed, 20 Apr 2016 18:20:15 +0530

> That one would work wound't it? Assuming it can do x and y movement.
I have tried to implement the same but it is not working properly as
scrolling in minimap was not there and sometimes it was not showing the map
itself.

---

From: Dave Page <[email-masked]>
Date: Thu, 21 Apr 2016 10:07:28 -0400

This looks awesome, and is MIT licenced:

http://www.jqueryrain.com/2016/01/imageviewer-jquery-zooming-and-panning-plugin/

On Wednesday, April 20, 2016, Sanket Mehta <[email-masked]>
wrote:

>
> On Wed, Apr 20, 2016 at 6:12 PM, Dave Page <[email-masked]
>> On Tuesday, April 19, 2016, Sanket Mehta <[email-masked]

---

From: Dave Page <[email-masked]>
Date: Fri, 27 May 2016 12:14:23 +0100

What is the status of this Sanket? I'd really like to see it completed
before alpha.

---

From: Sanket Mehta <[email-masked]>
Date: Fri, 27 May 2016 16:52:16 +0530

Hi,

I will start working on it once I will complete PEM 6.0.1 issues.
Now there is only 1 issue remaining in that bucket which I am currently
working on

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Fri, May 27, 2016 at 4:44 PM, Dave Page <[email-masked]> wrote:

> https://github.com/princejwesley/minimap/blob/master/src/minimap.js
> >>>
> >>>