From fcc3d112e28c49196191b76d844d9f53b87ea3e7 Mon Sep 17 00:00:00 2001 From: Sanket Mehta Date: Mon, 16 May 2016 01:07:52 +0530 Subject: [PATCH] Integrate the graphical explain module in the Query Editor. Added few TODO list for the graphical explain module by Ashesh. --- TODO.txt | 8 + web/pgadmin/misc/__init__.py | 50 +- .../misc/static/explain/css/explain.css | 56 + .../misc/static/explain/img/ex_aggregate.png | Bin 0 -> 574 bytes .../misc/static/explain/img/ex_append.png | Bin 0 -> 1162 bytes .../misc/static/explain/img/ex_bmp_and.png | Bin 0 -> 1006 bytes .../misc/static/explain/img/ex_bmp_heap.png | Bin 0 -> 1106 bytes .../misc/static/explain/img/ex_bmp_index.png | Bin 0 -> 1172 bytes .../misc/static/explain/img/ex_bmp_or.png | Bin 0 -> 685 bytes .../explain/img/ex_broadcast_motion.png | Bin 0 -> 334 bytes .../misc/static/explain/img/ex_cte_scan.png | Bin 0 -> 1955 bytes .../misc/static/explain/img/ex_delete.png | Bin 0 -> 1129 bytes .../static/explain/img/ex_foreign_scan.png | Bin 0 -> 1607 bytes .../static/explain/img/ex_gather_motion.png | Bin 0 -> 218 bytes .../misc/static/explain/img/ex_group.png | Bin 0 -> 1228 bytes .../misc/static/explain/img/ex_hash.png | Bin 0 -> 1169 bytes .../static/explain/img/ex_hash_anti_join.png | Bin 0 -> 1571 bytes .../static/explain/img/ex_hash_semi_join.png | Bin 0 -> 1452 bytes .../explain/img/ex_hash_setop_except.png | Bin 0 -> 1377 bytes .../explain/img/ex_hash_setop_except_all.png | Bin 0 -> 1402 bytes .../explain/img/ex_hash_setop_intersect.png | Bin 0 -> 1389 bytes .../img/ex_hash_setop_intersect_all.png | Bin 0 -> 1417 bytes .../explain/img/ex_hash_setop_unknown.png | Bin 0 -> 1490 bytes .../static/explain/img/ex_index_only_scan.png | Bin 0 -> 498 bytes .../misc/static/explain/img/ex_index_scan.png | Bin 0 -> 1298 bytes .../misc/static/explain/img/ex_insert.png | Bin 0 -> 1065 bytes .../misc/static/explain/img/ex_join.png | Bin 0 -> 1090 bytes .../misc/static/explain/img/ex_limit.png | Bin 0 -> 1237 bytes .../misc/static/explain/img/ex_lock_rows.png | Bin 0 -> 1520 bytes .../static/explain/img/ex_materialize.png | Bin 0 -> 1221 bytes .../misc/static/explain/img/ex_merge.png | Bin 0 -> 1127 bytes .../static/explain/img/ex_merge_anti_join.png | Bin 0 -> 1599 bytes .../static/explain/img/ex_merge_append.png | Bin 0 -> 980 bytes .../static/explain/img/ex_merge_semi_join.png | Bin 0 -> 1344 bytes .../misc/static/explain/img/ex_nested.png | Bin 0 -> 1108 bytes .../explain/img/ex_nested_loop_anti_join.png | Bin 0 -> 1741 bytes .../explain/img/ex_nested_loop_semi_join.png | Bin 0 -> 1679 bytes .../static/explain/img/ex_recursive_union.png | Bin 0 -> 1224 bytes .../explain/img/ex_redistribute_motion.png | Bin 0 -> 218 bytes .../misc/static/explain/img/ex_result.png | Bin 0 -> 1320 bytes .../misc/static/explain/img/ex_scan.png | Bin 0 -> 1320 bytes .../misc/static/explain/img/ex_seek.png | Bin 0 -> 1326 bytes .../misc/static/explain/img/ex_setop.png | Bin 0 -> 1143 bytes .../misc/static/explain/img/ex_sort.png | Bin 0 -> 1157 bytes .../misc/static/explain/img/ex_subplan.png | Bin 0 -> 1283 bytes .../misc/static/explain/img/ex_tid_scan.png | Bin 0 -> 1074 bytes .../misc/static/explain/img/ex_unique.png | Bin 0 -> 1238 bytes .../misc/static/explain/img/ex_unknown.png | Bin 0 -> 1088 bytes .../misc/static/explain/img/ex_update.png | Bin 0 -> 1090 bytes .../static/explain/img/ex_values_scan.png | Bin 0 -> 913 bytes .../explain/img/ex_window_aggregate.png | Bin 0 -> 2021 bytes .../static/explain/img/ex_worktable_scan.png | Bin 0 -> 1965 bytes .../misc/static/explain/img/exclude.png | Bin 0 -> 725 bytes .../misc/static/explain/img/extension-sm.png | Bin 0 -> 423 bytes .../misc/static/explain/img/extension.png | Bin 0 -> 996 bytes .../misc/static/explain/img/extensions.png | Bin 0 -> 1017 bytes .../misc/static/explain/img/exttable-sm.png | Bin 0 -> 612 bytes .../misc/static/explain/img/exttable.png | Bin 0 -> 793 bytes .../misc/static/explain/img/exttables.png | Bin 0 -> 662 bytes .../misc/static/explain/js/snap.svg-min.js | 21 + .../misc/static/explain/js/snap.svg.js | 8149 +++++++++++++++++ .../misc/templates/explain/js/explain.js | 691 ++ .../datagrid/templates/datagrid/index.html | 43 + web/pgadmin/tools/sqleditor/__init__.py | 82 +- .../tools/sqleditor/static/css/sqleditor.css | 7 + .../templates/sqleditor/js/sqleditor.js | 403 +- 66 files changed, 9487 insertions(+), 23 deletions(-) create mode 100644 web/pgadmin/misc/static/explain/css/explain.css create mode 100644 web/pgadmin/misc/static/explain/img/ex_aggregate.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_append.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_bmp_and.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_bmp_heap.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_bmp_index.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_bmp_or.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_broadcast_motion.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_cte_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_delete.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_foreign_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_gather_motion.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_group.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash_anti_join.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash_semi_join.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash_setop_except.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash_setop_except_all.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash_setop_intersect_all.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_hash_setop_unknown.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_index_only_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_index_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_insert.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_join.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_limit.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_lock_rows.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_materialize.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_merge.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_merge_anti_join.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_merge_append.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_merge_semi_join.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_nested.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_nested_loop_anti_join.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_nested_loop_semi_join.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_recursive_union.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_redistribute_motion.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_result.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_seek.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_setop.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_sort.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_subplan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_tid_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_unique.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_unknown.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_update.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_values_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_window_aggregate.png create mode 100644 web/pgadmin/misc/static/explain/img/ex_worktable_scan.png create mode 100644 web/pgadmin/misc/static/explain/img/exclude.png create mode 100644 web/pgadmin/misc/static/explain/img/extension-sm.png create mode 100644 web/pgadmin/misc/static/explain/img/extension.png create mode 100644 web/pgadmin/misc/static/explain/img/extensions.png create mode 100644 web/pgadmin/misc/static/explain/img/exttable-sm.png create mode 100644 web/pgadmin/misc/static/explain/img/exttable.png create mode 100644 web/pgadmin/misc/static/explain/img/exttables.png create mode 100644 web/pgadmin/misc/static/explain/js/snap.svg-min.js create mode 100644 web/pgadmin/misc/static/explain/js/snap.svg.js create mode 100644 web/pgadmin/misc/templates/explain/js/explain.js diff --git a/TODO.txt b/TODO.txt index ed5109e98..93abba069 100644 --- a/TODO.txt +++ b/TODO.txt @@ -37,3 +37,11 @@ Restore Object List down the objects within the backup file, and allow the user to select/deselect the only objects, which user may want to restore. + +Graphical Explain +----------------- +* A better zooming/scaling functionality. A minimap for the explain will be + very nice. +* Explanation on the statistic for the graphical explain plan. +* Arrow colouring based on the percentage of the cost, and time taken on each + explain node. diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py index 0c4de3a40..91d867575 100644 --- a/web/pgadmin/misc/__init__.py +++ b/web/pgadmin/misc/__init__.py @@ -9,24 +9,68 @@ """A blueprint module providing utility functions for the application.""" +from flask import url_for, render_template + +import config from pgadmin.utils import PgAdminModule import pgadmin.utils.driver as driver MODULE_NAME = 'misc' + +class MiscModule(PgAdminModule): + + def get_own_javascripts(self): + return [{ + 'name': 'pgadmin.misc.explain', + 'path': url_for('misc.index') + 'explain/explain', + 'preloaded': False + }, { + 'name': 'snap.svg', + 'path': url_for( + 'misc.static', filename='explain/js/' + ( + 'snap.svg' if config.DEBUG else 'snap.svg-min' + )), + 'preloaded': False + }] + + def get_own_stylesheets(self): + stylesheets = [] + stylesheets.append( + url_for('misc.static', filename='explain/css/explain.css') + ) + return stylesheets + + # Initialise the module -blueprint = PgAdminModule( - MODULE_NAME, __name__, url_prefix='' -) +blueprint = MiscModule(MODULE_NAME, __name__) + ########################################################################## # A special URL used to "ping" the server ########################################################################## +@blueprint.route("/") +def index(): + return '' +########################################################################## +# 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" + + +@blueprint.route("/explain/explain.js") +def explain_js(): + """ + explain_js() + + Returns: + javascript for the explain module + """ + return render_template("explain/js/explain.js") diff --git a/web/pgadmin/misc/static/explain/css/explain.css b/web/pgadmin/misc/static/explain/css/explain.css new file mode 100644 index 000000000..49a4bc4ec --- /dev/null +++ b/web/pgadmin/misc/static/explain/css/explain.css @@ -0,0 +1,56 @@ +.pg-explain-zoom-area { + position: absolute; + top: 5px; + left: 5px; + opacity: 0.5; + cursor: pointer; +} + +.pg-explain-zoom-btn { + top: 5px; + min-width: 25px; + cursor: pointer; + border: 1px solid transparent; +} + +.pg-explain-zoom-area:hover { + opacity: 1; +} + +.explain-tooltip { + display: table-cell; + text-align: left; + white-space: nowrap; + line-height: 10px !important; + padding: 2px !important; + font-size: small; +} + +td.explain-tooltip-val { + display: table-cell; + text-align: left; + white-space: pre-wrap; + font-size: smaller; +} + +.pgadmin-explain-tooltip { + position: absolute; + padding:5px; + border: 1px solid white; + opacity:0; + color: cornsilk; + background-color: #010125; +} + +.pgadmin-tooltip-table { + border-collapse: collapse; + border-spacing: 1px; + top: auto; + left: auto; +} + +.pgadmin-explain-container { + height: 100%; + width: 100%; + overflow: auto; +} \ No newline at end of file diff --git a/web/pgadmin/misc/static/explain/img/ex_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_aggregate.png new file mode 100644 index 0000000000000000000000000000000000000000..6bfe75d909f5092c4d3ba8978c75fbc57d7f606d GIT binary patch literal 574 zcmV-E0>S->P)$mMIX+&H|b@* 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}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?B>));G-mL1~tMBB$>*k;D>$d6On&{t|?&h=S+m`0ol<@94Ab;k?JHb@1lE=hUR;)1&O& zx!}r)-o=I9!-U^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=eFxKwPp 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=D86a;T`NPQK>5s&lKWtGldpyRCMut*u(Y=dP}(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^Nk4PwuJ zQ#!=9lF84TBBa*NRjVOi9LP1IA^nW2-@@fKw*1L<;8opaGaip`h_l>+MlB1G6SG9S z=+u$;BX}4UBk&RoE`h+(O1ZE*><Ht zNXoo1qP+&hYdgl>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^r9W&i*H07*qoM6N<$f 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)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^YbyzqqbGRa-^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^&(-vmu3&*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(Y@0AyyV$hfa#N zVHoX*8Jn9s_Kewxb3cRzb}`MpuD9%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)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;mCDaX<<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#GyzqqcGRa-^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+bsbiox#sZ z$i~K>oS5uhcm(RXT&@m##cZ|)wNxxtQMr8q$pr#|oL~`iI2^P}$JW;+qy$+HkJ#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?N0000DsC3+^Xu_s_Wah>)x#F-MQ@G zt?b~g?cclJ-uCR`uk7Qn@8rMa^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#gZrjMO1> 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 rRpn4LVwnpiPi9C$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?^vpv&`|v;qUjj>z}pi zp2zRDvgw_%>7B#xwXNu!tLL1m=bOLnw5H~orRJKX<(j(ev)AhNwCbL->YcCYov-Me zspp%c<(i`9nYrw<*6H-b@3yY!oT%oTr{();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#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 zh7B7LjhpD#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;{W8diK(4~sdWKB-BdkyEEI5qO{W{}ZMBb>?jBxf(*H z#A!6=wKQ_!LN%Gb*yCbEb(g(nLwrht_CEZRnwFkH zOL_1B6LdX9_3e4$3Y5ik(!nLFo#BRb1?}NEfvtA{}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 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 FV1m**ONRge literal 0 HcmV?d00001 diff --git a/web/pgadmin/misc/static/explain/img/ex_gather_motion.png b/web/pgadmin/misc/static/explain/img/ex_gather_motion.png new file mode 100644 index 0000000000000000000000000000000000000000..06b72826862098966412a9ed9320ed4514fe3e5a GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*@xBpA$Gw#oo0rjj7PU9z9h#qH&> z?c}iR?B?BK2I-mL1~tMBB$bEG!w=AY@{n&{q`?&h=R*_GtekmAse>DsC4 zDi~^&Wz~SrlQAS@94Ab;k>5IWyh*@@aDkh)THLqqwL+ecBwj^wTIx! ziQdJ9-ou35!Gh+|q2I`d<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+K3EONldUfRE$& 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~_TR5CLel?&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%{EiufNlm?Sban zmF(iLDs94;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#3ciXMG^ue5lb*` zBLBL(BnC@M_{lzaKFl*S`~03W=gh1~bf??Z7>o&i8z-E`2MDn+LnITE(kU`Ph=my< zli!=3;UOHsGB!NW(76$6NJ2r~EN>D;qxHI?ZTyx!j(ABM$Jw28;N7n`m*n{ee-CEij_l=W|)m#q9t^g?o`5n$u2q89~HPuuHQD{TVq$evdr}9gW5IqV% zY=p)*2{p#QmtU5UXB!g~#e{J`Q!Sk0S*=#954-)4+s{4^DqlQEV)eLGs$i|rXr2#4 zu8J*k#g!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)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*k;9<(}{Bw(se+xaofG=CkePvFF;B=Gm3z*p%$yuJG)(>g1j3f@a0;+*d1v%K(xBI2n!RpnIz8rM-G;Z!G=R8sZm;ipqDj2iYBa-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({<^D z9AXwQt|i#zmMlzGOe_$#HAax4*I#(9iE+I;|$c2Ym*n9lwVL-R9sSAR9c46QeFW9FwZL(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_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&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@6$+o03~@7;%^vuMm@JJGmnsy zuG1*Xb4;BYgOU392vO(P&!Kp6@p=_SEyk&Y-2;lk%6vCay@A6~WuE;;C#7g56s?$SP%?QaR1+n-c`~kT8uNVa*2|@o?lo)=KN~N}8`2cd*w{>1 zTTgjgh@L@R+)G zO(PhQvf3nzE#>#mKw1u{SyOTb#j{N!#xvnegJKyEY1bgl8X%hmqzq8Kx;j$0LMUJl zz`VsQjOo~s>UR}uO21G3b~5>_sl;f1pL3yp6Y(*2 zTg|QPYVFdf4n2i1ryt-~R&&EyEH~GIaAZa|e{p<(Ym$Sy#1kbWFdS!sl4#8 zg+-&46R)?p=vdx>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>T0alj*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#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)P)t-s0001q zx!X2~!Eep(O~C14#_M*??55uGK)mS^tKuxP9z`^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$dan$D+qz>g1j3n(pSaq{?IO>bB|Oo9N$}=iQg=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+qmVe#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`)E#4zJS}je%iZz zOV0d!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-_lqPS2uKbKd9_ zOxilSdivP3!2P0PU|jav({i+z0kltEhjT>}Dx zaQX!$jRpIKgaY-1nSww#aj8!^Gy>=})5xgkm{?PyQeSXrL>$l-)A)qMq-30aK}mc* zDRE%Gq^6~3Wa896poFAkiJr!&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)9z`^du$M4O~ z^4{L|yuIwJtmw0}>b$+})YS6;005<^<8@RZo_dd}>F(d~=W?ycYR znY?&XjTw-(Y}nDs91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35 z!Gh(_ptk3H=G3I!!h_tvf!n=*woLpxaH2EfEY#uzcLWj_l&D*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{lOka_Rtznx*Q{N)9pF 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)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$dan$D+qz>g1j3n(pSaq{?IO>bB|Oo9N$}=iQg=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+qmVe#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^Xk>j;?9ie*r$Ha zR+!l<;?RxZ&5Pj5iRaa&h3hnp?kDf#zUbJe=hmg{-MQ@Ey5-QJnikS+;RXAJmlQ30 z`~reP!h9rY5fK#=ml%v%NcW2*9|%YeK!`!p9j?@eO$!sVl(dX2lbpPQq7oB6Ey^k? zs%j7|9O@dHT1?tHx_bK9w7~tMVqjnh(L=x%M2IP?7yJ1{WI;kKJ0~|UAGa3dqEDp&qNT7XzqkaaUr=o+ z1#2M?VvrtTZN}KoH}FXtZ5xU#_SoGdQ9icoi~30+?JY7kdD?VGZ!wL)ma15GX=wKri&IY zS-K3arM3%1wk%%=1S@Jm?8z+{T2`)Fy=E<3OIvc3+)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(IX9z`^du$M4O~ z^4{L|yuIwJtmw0}>b$+})YS6;005<^<8@RZo_dd}>F(d~=W?ycYR znY?&XjTw-(Y}nDs91*Qf2@yLz!f>DsB_%ZlH~hu+17=Fy^!!Bgnhrr*Yf-ou35 z!Gh(_ptk3H=G3I!!h_tvf!n=*woLpxaH2Eokq-C-39F=-8*{)}`#-x$NG$<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<1vZeeLPDm%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>!*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 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 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 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 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)$$q@!^H2!#_)!zWKxY8p~PNwrpGpj!Eep(LA>Zn!0CI@?}XFvW5()e$n0**?3dZ` zpxg0;(e8}Y?vd5*uHf?tsp4J4>N2(Ebg1j2;F{;$m*?A-?BlSe%w_81oao+}=Gm3(;;-o4nC97(<=2z!;H{?6 zXYAmv@aV$Fs&%W?Z0p~x>fEaD<-p_8kK@pe>DsAbl`meHCBewLqviyiJ|ZV|uVayzqqY?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`<&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(dLSw290sM`GP^JRT&hu(P*AR2H%Cx8^sD$O;e!W_|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)9yuR$dzwN)l?a@XO2b+1mBo-1XAa^VHPz)zV?$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=nIz8rM-G;Z!G=R8sZm;b)sQqtRwwm?i4*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<M4bk^^05E_010qNS#tmY07w7;07w8v$!k6U00L=AL_t(Y$75g^1&mA} zfIYbTh^QEoxP+vXG?R?1oV>zd(L%ak$OthC zfTEHjsalkjl_=7pq6z{KQVqf;q(xO-LsLszRYzA(-#}H}(8$aY>#=;r%2liT=YaI|uff!^cHR07QO%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 z0=Kcc|5-M;K@_mX$%Dr zm&Q~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 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 ZD=;-WFfhuORjdF2002ovPDHLkV1g+E(G&mx literal 0 HcmV?d00001 diff --git a/web/pgadmin/misc/static/explain/img/ex_recursive_union.png b/web/pgadmin/misc/static/explain/img/ex_recursive_union.png new file mode 100644 index 0000000000000000000000000000000000000000..66952ea454e3703dcb08251bd2273193aacaeb28 GIT binary patch literal 1224 zcmV;(1ULJMP)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%EWC2=(Fb9mF3ryf)T}-9pzKn&{q{=iHa<n{?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@+^xnCJ=y<#A#t-W?^MxXX4=G;^tu@ zQVTC1zkncaJZKN(>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^~CjTHy^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+%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 zy&|Rq4YRc_yz2s%c>~px_ zSfSIA(d>o8VwejipJ$*z2zgay*ywR-w~O!0LI->S@2`Yq#KKzve-^=xegvb++DV$m?jj z8=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*&oHufd5Vx<$E{y1rNF{wq{1pVhD#p6C2Rn5oh%7L0oleB1N|4Fp`jMQ=#pw)z990@QuA5do z%zYPv$i2ZQ&zL%%$z6BtR(}-AOZ|1ZsiIV3#aX_aKKpI?Rtwj~pUC1Wk{1EpWn@(i<$E=y+6>NkK z&qpAySa4-uHvH^XzT03OH>05UK!FfB1MEif@u zGB7$cGdeUhD=;-WFfcfoB_03(03~!qSaf7zbY(hiZ)9m^c>ppnF)%GKIV~_TR5CL< eG&DLeHY+eSIxsNGmsP9)0000n 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~ zX%f3E0nHYQkNqG4U%j7Mp2U7oF*>GZTDYnYlh_prI4U=^O{(k1 z1&_l=Y&sWTE`Nqyi*-+eA9Md zK3zI6YurED-VlgxD2VgQq4T)o<{Uc52dBM4_ns4qT@|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#_(duq1mbS2==EN8$unCzu)=|NpXJS+8k(y zsf-o0Hs=vz(jedS7h1^)Nk6Egj#I}j#|fGRi^DszZaa6=0txir$OP}|H5oNdm)-MvQ!!rm(9c)!#r=_M zBHea|)}|e>!%!27`lYDuS>)H4O$vGq?bkY|V%O_yDBIEKFR-P%ozXV=^u|^833oyO zaZUL45Q3B6k&=}Bj~!U7+UU*(%Cey-DHB+7FHqH zPj|}S-*ndrRToh|RU)~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 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 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)NdYtf>m5`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(B3-?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}L0qhf6q?R^LC z+aGBz3y6${r7U>JZlAM>UNjEhXh=TF8Nd2bRuFzH;GTIO2?j%Mzk8N%fEdW$AV22w zL@?s0-wW-D+06_Mp*`e&BlRIa<9G3lpVBjaeSfJrI7i?75F?V; zhba6A>5ka^!s61W*yk_H%U@$pB6W_~LOdQ{ip?)B&3~&x5><%OLCKO($?{#QQC=UB zgwAm@Mw(G_XV%g#!oHwzf7zl+6?hyND182=m3g{rzGd!crMCwGM*d8pU;Vu)TYD zgz$MJ63OJ|-to!teyLO{m&;J$!1{@Z8k8**uFuf<<<-sY1Q997kA*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 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 literal 0 HcmV?d00001 diff --git a/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png b/web/pgadmin/misc/static/explain/img/ex_window_aggregate.png new file mode 100644 index 0000000000000000000000000000000000000000..1f858be1cee3f5d8f2b67f55d730e6d3f43e9b42 GIT binary patch literal 2021 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 literal 0 HcmV?d00001 diff --git a/web/pgadmin/misc/static/explain/js/snap.svg-min.js b/web/pgadmin/misc/static/explain/js/snap.svg-min.js new file mode 100644 index 000000000..6567d1992 --- /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;od;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;bc;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))).5;){var n,o,p,q,r,s;(p=f-k)>=0&&(r=d(n=i.getPointAtLength(p)))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;ei;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=""+a+"",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+="")}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;eb;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('{contents}',{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;c1&&(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.xb.x||b.xa.x)&&(a.yb.y||b.ya.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)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)Q(e,g)||Q(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;cc;c++)h.push(this[a+c]);for(;cc?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('',{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('',{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('',{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('',{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('',{amount:1-b})},a.filter.saturate.toString=function(){return this()},a.filter.hueRotate=function(b){return b=b||0,a.format('',{angle:b})},a.filter.hueRotate.toString=function(){return this()},a.filter.invert=function(b){return null==b&&(b=1),a.format('',{amount:b,amount2:1-b})},a.filter.invert.toString=function(){return this()},a.filter.brightness=function(b){return null==b&&(b=1),a.format('',{amount:b})},a.filter.brightness.toString=function(){return this()},a.filter.contrast=function(b){return null==b&&(b=1),a.format('',{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 000000000..ef0fb6d40 --- /dev/null +++ b/web/pgadmin/misc/static/explain/js/snap.svg.js @@ -0,0 +1,8149 @@ +// Snap.svg 0.4.1 +// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// build: 2015-04-13 +// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ┌────────────────────────────────────────────────────────────┐ \\ +// │ Eve 0.4.2 - JavaScript Events Library │ \\ +// ├────────────────────────────────────────────────────────────┤ \\ +// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\ +// └────────────────────────────────────────────────────────────┘ \\ +(function (glob) { + var version = "0.4.2", + has = "hasOwnProperty", + separator = /[\.\/]/, + comaseparator = /\s*,\s*/, + wildcard = "*", + fun = function () {}, + numsort = function (a, b) { + return a - b; + }, + current_event, + stop, + events = {n: {}}, + firstDefined = function () { + for (var i = 0, ii = this.length; i < ii; i++) { + if (typeof this[i] != "undefined") { + return this[i]; + } + } + }, + lastDefined = function () { + var i = this.length; + while (--i) { + if (typeof this[i] != "undefined") { + return this[i]; + } + } + }, + /*\ + * eve + [ method ] + * Fires event with given `name`, given scope and other parameters. + > Arguments + - name (string) name of the *event*, dot (`.`) or slash (`/`) separated + - scope (object) context for the event handlers + - varargs (...) the rest of arguments will be sent to event handlers + = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value. + \*/ + eve = function (name, scope) { + name = String(name); + var e = events, + oldstop = stop, + args = Array.prototype.slice.call(arguments, 2), + listeners = eve.listeners(name), + z = 0, + f = false, + l, + indexed = [], + queue = {}, + out = [], + ce = current_event, + errors = []; + out.firstDefined = firstDefined; + out.lastDefined = lastDefined; + current_event = name; + stop = 0; + for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) { + indexed.push(listeners[i].zIndex); + if (listeners[i].zIndex < 0) { + queue[listeners[i].zIndex] = listeners[i]; + } + } + indexed.sort(numsort); + while (indexed[z] < 0) { + l = queue[indexed[z++]]; + out.push(l.apply(scope, args)); + if (stop) { + stop = oldstop; + return out; + } + } + for (i = 0; i < ii; i++) { + l = listeners[i]; + if ("zIndex" in l) { + if (l.zIndex == indexed[z]) { + out.push(l.apply(scope, args)); + if (stop) { + break; + } + do { + z++; + l = queue[indexed[z]]; + l && out.push(l.apply(scope, args)); + if (stop) { + break; + } + } while (l) + } else { + queue[l.zIndex] = l; + } + } else { + out.push(l.apply(scope, args)); + if (stop) { + break; + } + } + } + stop = oldstop; + current_event = ce; + return out; + }; + // Undocumented. Debug only. + eve._events = events; + /*\ + * eve.listeners + [ method ] + * Internal method which gives you array of all event handlers that will be triggered by the given `name`. + > Arguments + - name (string) name of the event, dot (`.`) or slash (`/`) separated + = (array) array of event handlers + \*/ + eve.listeners = function (name) { + var names = name.split(separator), + e = events, + item, + items, + k, + i, + ii, + j, + jj, + nes, + es = [e], + out = []; + for (i = 0, ii = names.length; i < ii; i++) { + nes = []; + for (j = 0, jj = es.length; j < jj; j++) { + e = es[j].n; + items = [e[names[i]], e[wildcard]]; + k = 2; + while (k--) { + item = items[k]; + if (item) { + nes.push(item); + out = out.concat(item.f || []); + } + } + } + es = nes; + } + return out; + }; + /*\ + * eve.on + [ method ] + ** + * Binds given event handler with a given name. You can use wildcards “`*`” for the names: + | eve.on("*.under.*", f); + | eve("mouse.under.floor"); // triggers f + * Use @eve to trigger the listener. + ** + > Arguments + ** + - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards + - f (function) event handler function + ** + = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. + > Example: + | eve.on("mouse", eatIt)(2); + | eve.on("mouse", scream); + | eve.on("mouse", catchIt)(1); + * This will ensure that `catchIt` function will be called before `eatIt`. + * + * If you want to put your handler before non-indexed handlers, specify a negative value. + * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”. + \*/ + eve.on = function (name, f) { + name = String(name); + if (typeof f != "function") { + return function () {}; + } + var names = name.split(comaseparator); + for (var i = 0, ii = names.length; i < ii; i++) { + (function (name) { + var names = name.split(separator), + e = events, + exist; + for (var i = 0, ii = names.length; i < ii; i++) { + e = e.n; + e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}}); + } + e.f = e.f || []; + for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) { + exist = true; + break; + } + !exist && e.f.push(f); + }(names[i])); + } + return function (zIndex) { + if (+zIndex == +zIndex) { + f.zIndex = +zIndex; + } + }; + }; + /*\ + * eve.f + [ method ] + ** + * Returns function that will fire given event with optional arguments. + * Arguments that will be passed to the result function will be also + * concated to the list of final arguments. + | el.onclick = eve.f("click", 1, 2); + | eve.on("click", function (a, b, c) { + | console.log(a, b, c); // 1, 2, [event object] + | }); + > Arguments + - event (string) event name + - varargs (…) and any other arguments + = (function) possible event handler function + \*/ + eve.f = function (event) { + var attrs = [].slice.call(arguments, 1); + return function () { + eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0))); + }; + }; + /*\ + * eve.stop + [ method ] + ** + * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing. + \*/ + eve.stop = function () { + stop = 1; + }; + /*\ + * eve.nt + [ method ] + ** + * Could be used inside event handler to figure out actual name of the event. + ** + > Arguments + ** + - subname (string) #optional subname of the event + ** + = (string) name of the event, if `subname` is not specified + * or + = (boolean) `true`, if current event’s name contains `subname` + \*/ + eve.nt = function (subname) { + if (subname) { + return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event); + } + return current_event; + }; + /*\ + * eve.nts + [ method ] + ** + * Could be used inside event handler to figure out actual name of the event. + ** + ** + = (array) names of the event + \*/ + eve.nts = function () { + return current_event.split(separator); + }; + /*\ + * eve.off + [ method ] + ** + * Removes given function from the list of event listeners assigned to given name. + * If no arguments specified all the events will be cleared. + ** + > Arguments + ** + - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards + - f (function) event handler function + \*/ + /*\ + * eve.unbind + [ method ] + ** + * See @eve.off + \*/ + eve.off = eve.unbind = function (name, f) { + if (!name) { + eve._events = events = {n: {}}; + return; + } + var names = name.split(comaseparator); + if (names.length > 1) { + for (var i = 0, ii = names.length; i < ii; i++) { + eve.off(names[i], f); + } + return; + } + names = name.split(separator); + var e, + key, + splice, + i, ii, j, jj, + cur = [events]; + for (i = 0, ii = names.length; i < ii; i++) { + for (j = 0; j < cur.length; j += splice.length - 2) { + splice = [j, 1]; + e = cur[j].n; + if (names[i] != wildcard) { + if (e[names[i]]) { + splice.push(e[names[i]]); + } + } else { + for (key in e) if (e[has](key)) { + splice.push(e[key]); + } + } + cur.splice.apply(cur, splice); + } + } + for (i = 0, ii = cur.length; i < ii; i++) { + e = cur[i]; + while (e.n) { + if (f) { + if (e.f) { + for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) { + e.f.splice(j, 1); + break; + } + !e.f.length && delete e.f; + } + for (key in e.n) if (e.n[has](key) && e.n[key].f) { + var funcs = e.n[key].f; + for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) { + funcs.splice(j, 1); + break; + } + !funcs.length && delete e.n[key].f; + } + } else { + delete e.f; + for (key in e.n) if (e.n[has](key) && e.n[key].f) { + delete e.n[key].f; + } + } + e = e.n; + } + } + }; + /*\ + * eve.once + [ method ] + ** + * Binds given event handler with a given name to only run once then unbind itself. + | eve.once("login", f); + | eve("login"); // triggers f + | eve("login"); // no listeners + * Use @eve to trigger the listener. + ** + > Arguments + ** + - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards + - f (function) event handler function + ** + = (function) same return function as @eve.on + \*/ + eve.once = function (name, f) { + var f2 = function () { + eve.unbind(name, f2); + return f.apply(this, arguments); + }; + return eve.on(name, f2); + }; + /*\ + * eve.version + [ property (string) ] + ** + * Current version of the library. + \*/ + eve.version = version; + eve.toString = function () { + return "You are running Eve " + version; + }; + (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve)); +})(this); + +(function (glob, factory) { + // AMD support + if (typeof define == "function" && define.amd) { + // Define as an anonymous module + define(["eve"], function (eve) { + return factory(glob, eve); + }); + } else if (typeof exports != 'undefined') { + // Next for Node.js or CommonJS + var eve = require('eve'); + module.exports = factory(glob, eve); + } else { + // Browser globals (glob is window) + // Snap adds itself to window + factory(glob, glob.eve); + } +}(window || this, function (window, eve) { +// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +var mina = (function (eve) { + var animations = {}, + requestAnimFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + setTimeout(callback, 16); + }, + isArray = Array.isArray || function (a) { + return a instanceof Array || + Object.prototype.toString.call(a) == "[object Array]"; + }, + idgen = 0, + idprefix = "M" + (+new Date).toString(36), + ID = function () { + return idprefix + (idgen++).toString(36); + }, + diff = function (a, b, A, B) { + if (isArray(a)) { + res = []; + for (var i = 0, ii = a.length; i < ii; i++) { + res[i] = diff(a[i], b, A[i], B); + } + return res; + } + var dif = (A - a) / (B - b); + return function (bb) { + return a + dif * (bb - b); + }; + }, + timer = Date.now || function () { + return +new Date; + }, + sta = function (val) { + var a = this; + if (val == null) { + return a.s; + } + var ds = a.s - val; + a.b += a.dur * ds; + a.B += a.dur * ds; + a.s = val; + }, + speed = function (val) { + var a = this; + if (val == null) { + return a.spd; + } + a.spd = val; + }, + duration = function (val) { + var a = this; + if (val == null) { + return a.dur; + } + a.s = a.s * val / a.dur; + a.dur = val; + }, + stopit = function () { + var a = this; + delete animations[a.id]; + a.update(); + eve("mina.stop." + a.id, a); + }, + pause = function () { + var a = this; + if (a.pdif) { + return; + } + delete animations[a.id]; + a.update(); + a.pdif = a.get() - a.b; + }, + resume = function () { + var a = this; + if (!a.pdif) { + return; + } + a.b = a.get() - a.pdif; + delete a.pdif; + animations[a.id] = a; + }, + update = function () { + var a = this, + res; + if (isArray(a.start)) { + res = []; + for (var j = 0, jj = a.start.length; j < jj; j++) { + res[j] = +a.start[j] + + (a.end[j] - a.start[j]) * a.easing(a.s); + } + } else { + res = +a.start + (a.end - a.start) * a.easing(a.s); + } + a.set(res); + }, + frame = function () { + var len = 0; + for (var i in animations) if (animations.hasOwnProperty(i)) { + var a = animations[i], + b = a.get(), + res; + len++; + a.s = (b - a.b) / (a.dur / a.spd); + if (a.s >= 1) { + delete animations[i]; + a.s = 1; + len--; + (function (a) { + setTimeout(function () { + eve("mina.finish." + a.id, a); + }); + }(a)); + } + a.update(); + } + len && requestAnimFrame(frame); + }, + /*\ + * mina + [ method ] + ** + * Generic animation of numbers + ** + - a (number) start _slave_ number + - A (number) end _slave_ number + - b (number) start _master_ number (start time in general case) + - B (number) end _master_ number (end time in gereal case) + - get (function) getter of _master_ number (see @mina.time) + - set (function) setter of _slave_ number + - easing (function) #optional easing function, default is @mina.linear + = (object) animation descriptor + o { + o id (string) animation id, + o start (number) start _slave_ number, + o end (number) end _slave_ number, + o b (number) start _master_ number, + o s (number) animation status (0..1), + o dur (number) animation duration, + o spd (number) animation speed, + o get (function) getter of _master_ number (see @mina.time), + o set (function) setter of _slave_ number, + o easing (function) easing function, default is @mina.linear, + o status (function) status getter/setter, + o speed (function) speed getter/setter, + o duration (function) duration getter/setter, + o stop (function) animation stopper + o pause (function) pauses the animation + o resume (function) resumes the animation + o update (function) calles setter with the right value of the animation + o } + \*/ + mina = function (a, A, b, B, get, set, easing) { + var anim = { + id: ID(), + start: a, + end: A, + b: b, + s: 0, + dur: B - b, + spd: 1, + get: get, + set: set, + easing: easing || mina.linear, + status: sta, + speed: speed, + duration: duration, + stop: stopit, + pause: pause, + resume: resume, + update: update + }; + animations[anim.id] = anim; + var len = 0, i; + for (i in animations) if (animations.hasOwnProperty(i)) { + len++; + if (len == 2) { + break; + } + } + len == 1 && requestAnimFrame(frame); + return anim; + }; + /*\ + * mina.time + [ method ] + ** + * Returns the current time. Equivalent to: + | function () { + | return (new Date).getTime(); + | } + \*/ + mina.time = timer; + /*\ + * mina.getById + [ method ] + ** + * Returns an animation by its id + - id (string) animation's id + = (object) See @mina + \*/ + mina.getById = function (id) { + return animations[id] || null; + }; + + /*\ + * mina.linear + [ method ] + ** + * Default linear easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.linear = function (n) { + return n; + }; + /*\ + * mina.easeout + [ method ] + ** + * Easeout easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.easeout = function (n) { + return Math.pow(n, 1.7); + }; + /*\ + * mina.easein + [ method ] + ** + * Easein easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.easein = function (n) { + return Math.pow(n, .48); + }; + /*\ + * mina.easeinout + [ method ] + ** + * Easeinout easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.easeinout = function (n) { + if (n == 1) { + return 1; + } + if (n == 0) { + return 0; + } + var q = .48 - n / 1.04, + Q = Math.sqrt(.1734 + q * q), + x = Q - q, + X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1), + y = -Q - q, + Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1), + t = X + Y + .5; + return (1 - t) * 3 * t * t + t * t * t; + }; + /*\ + * mina.backin + [ method ] + ** + * Backin easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.backin = function (n) { + if (n == 1) { + return 1; + } + var s = 1.70158; + return n * n * ((s + 1) * n - s); + }; + /*\ + * mina.backout + [ method ] + ** + * Backout easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.backout = function (n) { + if (n == 0) { + return 0; + } + n = n - 1; + var s = 1.70158; + return n * n * ((s + 1) * n + s) + 1; + }; + /*\ + * mina.elastic + [ method ] + ** + * Elastic easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.elastic = function (n) { + if (n == !!n) { + return n; + } + return Math.pow(2, -10 * n) * Math.sin((n - .075) * + (2 * Math.PI) / .3) + 1; + }; + /*\ + * mina.bounce + [ method ] + ** + * Bounce easing + - n (number) input 0..1 + = (number) output 0..1 + \*/ + mina.bounce = function (n) { + var s = 7.5625, + p = 2.75, + l; + if (n < (1 / p)) { + l = s * n * n; + } else { + if (n < (2 / p)) { + n -= (1.5 / p); + l = s * n * n + .75; + } else { + if (n < (2.5 / p)) { + n -= (2.25 / p); + l = s * n * n + .9375; + } else { + n -= (2.625 / p); + l = s * n * n + .984375; + } + } + } + return l; + }; + window.mina = mina; + return mina; +})(typeof eve == "undefined" ? function () {} : eve); +// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var Snap = (function(root) { +Snap.version = "0.4.0"; +/*\ + * Snap + [ method ] + ** + * Creates a drawing surface or wraps existing SVG element. + ** + - width (number|string) width of surface + - height (number|string) height of surface + * or + - DOM (SVGElement) element to be wrapped into Snap structure + * or + - array (array) array of elements (will return set of elements) + * or + - query (string) CSS query selector + = (object) @Element +\*/ +function Snap(w, h) { + if (w) { + if (w.nodeType) { + return wrap(w); + } + if (is(w, "array") && Snap.set) { + return Snap.set.apply(Snap, w); + } + if (w instanceof Element) { + return w; + } + if (h == null) { + w = glob.doc.querySelector(String(w)); + return wrap(w); + } + } + w = w == null ? "100%" : w; + h = h == null ? "100%" : h; + return new Paper(w, h); +} +Snap.toString = function () { + return "Snap v" + this.version; +}; +Snap._ = {}; +var glob = { + win: root.window, + doc: root.window.document +}; +Snap._.glob = glob; +var has = "hasOwnProperty", + Str = String, + toFloat = parseFloat, + toInt = parseInt, + math = Math, + mmax = math.max, + mmin = math.min, + abs = math.abs, + pow = math.pow, + PI = math.PI, + round = math.round, + E = "", + S = " ", + objectToString = Object.prototype.toString, + ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i, + colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i, + bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, + reURLValue = /^url\(#?([^)]+)\)$/, + separator = Snap._.separator = /[,\s]+/, + whitespace = /[\s]/g, + commaSpaces = /[\s]*,[\s]*/, + hsrg = {hs: 1, rg: 1}, + pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, + tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, + pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig, + idgen = 0, + idprefix = "S" + (+new Date).toString(36), + ID = function (el) { + return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36); + }, + xlink = "http://www.w3.org/1999/xlink", + xmlns = "http://www.w3.org/2000/svg", + hub = {}, + URL = Snap.url = function (url) { + return "url('#" + url + "')"; + }; + +function $(el, attr) { + if (attr) { + if (el == "#text") { + el = glob.doc.createTextNode(attr.text || attr["#text"] || ""); + } + if (el == "#comment") { + el = glob.doc.createComment(attr.text || attr["#text"] || ""); + } + if (typeof el == "string") { + el = $(el); + } + if (typeof attr == "string") { + if (el.nodeType == 1) { + if (attr.substring(0, 6) == "xlink:") { + return el.getAttributeNS(xlink, attr.substring(6)); + } + if (attr.substring(0, 4) == "xml:") { + return el.getAttributeNS(xmlns, attr.substring(4)); + } + return el.getAttribute(attr); + } else if (attr == "text") { + return el.nodeValue; + } else { + return null; + } + } + if (el.nodeType == 1) { + for (var key in attr) if (attr[has](key)) { + var val = Str(attr[key]); + if (val) { + if (key.substring(0, 6) == "xlink:") { + el.setAttributeNS(xlink, key.substring(6), val); + } else if (key.substring(0, 4) == "xml:") { + el.setAttributeNS(xmlns, key.substring(4), val); + } else { + el.setAttribute(key, val); + } + } else { + el.removeAttribute(key); + } + } + } else if ("text" in attr) { + el.nodeValue = attr.text; + } + } else { + el = glob.doc.createElementNS(xmlns, el); + } + return el; +} +Snap._.$ = $; +Snap._.id = ID; +function getAttrs(el) { + var attrs = el.attributes, + name, + out = {}; + for (var i = 0; i < attrs.length; i++) { + if (attrs[i].namespaceURI == xlink) { + name = "xlink:"; + } else { + name = ""; + } + name += attrs[i].name; + out[name] = attrs[i].textContent; + } + return out; +} +function is(o, type) { + type = Str.prototype.toLowerCase.call(type); + if (type == "finite") { + return isFinite(o); + } + if (type == "array" && + (o instanceof Array || Array.isArray && Array.isArray(o))) { + return true; + } + return (type == "null" && o === null) || + (type == typeof o && o !== null) || + (type == "object" && o === Object(o)) || + objectToString.call(o).slice(8, -1).toLowerCase() == type; +} +/*\ + * Snap.format + [ method ] + ** + * Replaces construction of type `{}` 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: + #
    + #
  • Color name (red, green, cornflowerblue, etc)
  • + #
  • #••• — shortened HTML color: (#000, #fc0, etc.)
  • + #
  • #•••••• — full length HTML color: (#000000, #bd2300)
  • + #
  • rgb(•••, •••, •••) — red, green and blue channels values: (rgb(200, 100, 0))
  • + #
  • rgba(•••, •••, •••, •••) — also with opacity
  • + #
  • rgb(•••%, •••%, •••%) — same as above, but in %: (rgb(100%, 175%, 0%))
  • + #
  • rgba(•••%, •••%, •••%, •••%) — also with opacity
  • + #
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (hsb(0.5, 0.25, 1))
  • + #
  • hsba(•••, •••, •••, •••) — also with opacity
  • + #
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • + #
  • hsba(•••%, •••%, •••%, •••%) — also with opacity
  • + #
  • hsl(•••, •••, •••) — hue, saturation and luminosity values: (hsb(0.5, 0.25, 0.5))
  • + #
  • hsla(•••, •••, •••, •••) — also with opacity
  • + #
  • hsl(•••%, •••%, •••%) — same as above, but in %
  • + #
  • hsla(•••%, •••%, •••%, •••%) — also with opacity
  • + #
+ * 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 + ""; + 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 instantiates. It's a part of SVG with which ordinary web developers may be least familiar. + /*\ + * Element.use + [ method ] + ** + * Creates a `` element linked to the current element + ** + = (Element) the `` 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 `` area + ** + = (Element) the element + \*/ + elproto.toDefs = function () { + var defs = getSomeDefs(this); + defs.appendChild(this.node); + return this; + }; + /*\ + * Element.toPattern + [ method ] + ** + * Creates a `` 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 `` 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 `` 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 `` 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 += ""); + } else { + type && (res += "/>"); + } + return res; + }; + } + elproto.toDataURL = function () { + if (window && window.btoa) { + var bb = this.getBBox(), + svg = Snap.format('{contents}', { + 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 `` 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. + * + #

Here is short list of commands available, for more details see SVG path string format or article about path strings at MDN.

+ # + # + # + # + # + # + # + # + # + # + # + #
CommandNameParameters
Mmoveto(x y)+
Zclosepath(none)
Llineto(x y)+
Hhorizontal linetox+
Vvertical linetoy+
Ccurveto(x1 y1 x2 y2 x y)+
Ssmooth curveto(x2 y2 x y)+
Qquadratic Bézier curveto(x1 y1 x y)+
Tsmooth quadratic Bézier curveto(x y)+
Aelliptical arc(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
RCatmull-Rom curveto*x1 y1 (x y)+
+ * * _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 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 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 `` 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: `()`. The `` 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 `` 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 . + \*/ + 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 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. on start, drag.end. on end and drag.move. 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.` on start, + * `drag.end.` on end and `drag.move.` on every move. When element is dragged over another element + * `drag.over.` 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.` 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 `` 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(''), + | 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('\', { + 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('', { + 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('', { + 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('', { + 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('', { + 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('', { + 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; + } +// + return Snap.format('', { + 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('', { + 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('', { + 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 `` 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 `` container will be used. + - el (object) @optional alignment element + - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` + = (object) this element + > Usage + | el.align(el2, "top"); + * or + | el.align("middle"); + \*/ + Element.prototype.align = function (el, way) { + return this.transform("..." + this.getAlign(el, way)); + }; +}); + +return Snap; +})); diff --git a/web/pgadmin/misc/templates/explain/js/explain.js b/web/pgadmin/misc/templates/explain/js/explain.js new file mode 100644 index 000000000..fd9a5e593 --- /dev/null +++ b/web/pgadmin/misc/templates/explain/js/explain.js @@ -0,0 +1,691 @@ +define ( + 'pgadmin.misc.explain', + ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'backbone', 'snap.svg'], + function($, _, S, pgAdmin, Backbone, Snap) { + +pgAdmin = pgAdmin || window.pgAdmin || {}; +var pgExplain = pgAdmin.Explain; + +// Snap.svg plug-in to write multitext as image name +Snap.plugin(function (Snap, Element, Paper, glob) { + Paper.prototype.multitext = function (x, y, txt, max_width, attributes) { + var svg = Snap(), + abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + isWordBroken = false, + temp = svg.text(0, 0, abc); + + temp.attr(attributes); + + /* + * Find letter width in pixels and + * index from where the text should be broken + */ + var letter_width = temp.getBBox().width / abc.length, + word_break_index = Math.round((max_width / letter_width)) - 1; + + svg.remove(); + + var words = txt.split(" "), + width_so_far = 0, + lines=[], curr_line = '', + /* + * Function to divide string into multiple lines + * and store them in an array if it size crosses + * the max-width boundary. + */ + splitTextInMultiLine = function(leading, so_far, line) { + var l = line.length, + res = []; + + if (l == 0) + return res; + + if (so_far && (so_far + (l * letter_width) > max_width)) { + res.push(leading); + res = res.concat(splitTextInMultiLine('', 0, line)); + } else if (so_far) { + res.push(leading + ' ' + line); + } else { + if (leading) + res.push(leading); + if (line.length > word_break_index + 1) + res.push(line.slice(0, word_break_index) + '-'); + else + res.push(line); + res = res.concat(splitTextInMultiLine('', 0, line.slice(word_break_index))); + } + + return res; + }; + + for (var i = 0; i < words.length; i++) { + var tmpArr = splitTextInMultiLine( + curr_line, width_so_far, words[i] + ); + + if (curr_line) { + lines = lines.slice(0, lines.length - 2); + } + lines = lines.concat(tmpArr); + curr_line = lines[lines.length - 1]; + width_so_far = (curr_line.length * letter_width); + } + + // Create multiple tspan for each string in array + var t = this.text(x,y,lines).attr(attributes); + t.selectAll("tspan:nth-child(n+2)").attr({ + dy: "1.2em", + x: x + }); + return t; + }; +}); + +if (pgAdmin.Explain) + return pgAdmin.Explain; + +var pgExplain = pgAdmin.Explain = { + // Prefix path where images are stored + prefix: '{{ url_for('misc.static', filename='explain/img') }}/' +}; + +/* + * A map which is used to fetch the image to be drawn and + * text which will appear below it + */ +var imageMapper = { + "Aggregate" : { + "image":"ex_aggregate.png", "image_text":"Aggregate" + }, + 'Append' : { + "image":"ex_append.png","image_text":"Append" + }, + "Bitmap Index Scan" : function(data) { + return { + "image":"ex_bmp_index.png", "image_text":data['Index Name'] + }; + }, + "Bitmap Heap Scan" : function(data) { + return {"image":"ex_bmp_heap.png","image_text":data['Relation Name']}; +}, +"BitmapAnd" : {"image":"ex_bmp_and.png","image_text":"Bitmap AND"}, +"BitmapOr" : {"image":"ex_bmp_or.png","image_text":"Bitmap OR"}, +"CTE Scan" : {"image":"ex_cte_scan.png","image_text":"CTE Scan"}, +"Function Scan" : {"image":"ex_result.png","image_text":"Function Scan"}, +"Foreign Scan" : {"image":"ex_foreign_scan.png","image_text":"Foreign Scan"}, +"Gather" : {"image":"ex_gather_motion.png","image_text":"Gather"}, +"Group" : {"image":"ex_group.png","image_text":"Group"}, +"GroupAggregate": {"image":"ex_aggregate.png","image_text":"Group Aggregate"}, +"Hash" : {"image":"ex_hash.png","image_text":"Hash"}, +"Hash Join": function(data) { + if (!data['Join Type']) return {"image":"ex_join.png","image_text":"Join"}; + switch(data['Join Type']) { + case 'Anti': return {"image":"ex_hash_anti_join.png","image_text":"Hash Anti Join"}; + case 'Semi': return {"image":"ex_hash_semi_join.png","image_text":"Hash Semi Join"}; + default: return {"image":"ex_hash.png","image_text":String("Hash " + data['Join Type'] + " Join" )}; + } +}, +"HashAggregate" : {"image":"ex_aggregate.png","image_text":"Hash Aggregate"}, +"Index Only Scan" : function(data) { + return {"image":"ex_index_only_scan.png","image_text":data['Index Name']}; +}, +"Index Scan" : function(data) { + return {"image":"ex_index_scan.png","image_text":data['Index Name']}; +}, +"Index Scan Backword" : {"image":"ex_index_scan.png","image_text":"Index Backward Scan"}, +"Limit" : {"image":"ex_limit.png","image_text":"Limit"}, +"LockRows" : {"image":"ex_lock_rows.png","image_text":"Lock Rows"}, +"Materialize" : {"image":"ex_materialize.png","image_text":"Materialize"}, +"Merge Append": {"image":"ex_merge_append.png","image_text":"Merge Append"}, +"Merge Join": function(data) { + switch(data['Join Type']) { + case 'Anti': return {"image":"ex_merge_anti_join.png","image_text":"Merge Anti Join"}; + case 'Semi': return {"image":"ex_merge_semi_join.png","image_text":"Merge Semi Join"}; + default: return {"image":"ex_merge.png","image_text":String("Merge " + data['Join Type'] + " Join" )}; + } +}, +"ModifyTable" : function(data) { + switch (data['Operaton']) { + case "insert": return { "image":"ex_insert.png", + "image_text":"Insert" + }; + case "update": return {"image":"ex_update.png","image_text":"Update"}; + case "Delete": return {"image":"ex_delete.png","image_text":"Delete"}; + } +}, +'Nested Loop' : function(data) { + switch(data['Join Type']) { + case 'Anti': return {"image":"ex_nested_loop_anti_join.png","image_text":"Nested Loop Anti Join"}; + case 'Semi': return {"image":"ex_nested_loop_semi_join.png","image_text":"Nested Loop Semi Join"}; + default: return {"image":"ex_nested.png","image_text":"Nested Loop " + data['Join Type'] + " Join"}; + } +}, +"Recursive Union" : {"image":"ex_recursive_union.png","image_text":"Recursive Union"}, +"Result" : {"image":"ex_result.png","image_text":"Result"}, +"Sample Scan" : {"image":"ex_scan.png","image_text":"Sample Scan"}, +"Scan" : {"image":"ex_scan.png","image_text":"Scan"}, +"Seek" : {"image":"ex_seek.png","image_text":"Seek"}, +"SetOp" : function(data) { + var strategy = data['Strategy'], + command = data['Command']; + + if(strategy == "Hashed") { + if(command.startsWith("Intersect")) { + if(command == "Intersect All") + return {"image":"ex_hash_setop_intersect_all.png","image_text":"Hashed Intersect All"}; + return {"image":"ex_hash_setop_intersect.png","image_text":"Hashed Intersect"}; + } + else if (command.startsWith("Except")) { + if(command == "Except All") + return {"image":"ex_hash_setop_except_all.png","image_text":"Hashed Except All"}; + return {"image":"ex_hash_setop_except.png","image_text":"Hash Except"}; + } + return {"image":"ex_hash_setop_unknown.png","image_text":"Hashed SetOp Unknown"}; + } + return {"image":"ex_setop.png","image_text":"SetOp"}; +}, +"Seq Scan": function(data) { + return {"image":"ex_scan.png","image_text":data['Relation Name']}; +}, +"Subquery Scan" : {"image":"ex_subplan.png","image_text":"SubQuery Scan"}, +"Sort" : {"image":"ex_sort.png","image_text":"Sort"}, +"Tid Scan" : {"image":"ex_tid_scan.png","image_text":"Tid Scan"}, +"Unique" : {"image":"ex_unique.png","image_text":"Unique"}, +"Values Scan" : {"image":"ex_values_scan.png","image_text":"Values Scan"}, +"WindowAgg" : {"image":"ex_window_aggregate.png","image_text":"Window Aggregate"}, +"WorkTable Scan" : {"image":"ex_worktable_scan.png","image_text":"WorkTable Scan"}, +"Undefined" : {"image":"ex_unknown.png","image_text":"Undefined"}, +} + +// Some predefined constants used to calculate image location and its border +var pWIDTH = pHEIGHT = 100. + IMAGE_WIDTH = IMAGE_HEIGHT = 50; +var offsetX = 200, + offsetY = 60; +var ARROW_WIDTH = 10, + ARROW_HEIGHT = 10, + DEFAULT_ARROW_SIZE = 2; +var TXT_ALLIGN = 5, + TXT_SIZE = "15px"; +var TOTAL_WIDTH = undefined, + TOTAL_HEIGHT = undefined; +var xMargin = 25, + yMargin = 25; +var MIN_ZOOM_FACTOR = 0.01, + MAX_ZOOM_FACTOR = 2, + INIT_ZOOM_FACTOR = 1; + ZOOM_RATIO = 0.05; + + +// Backbone model for each plan property of input JSON object +var PlanModel = Backbone.Model.extend({ + defaults: { + "Plans": [], + level: [], + "image": undefined, + "image_text": undefined, + xpos: undefined, + ypos: undefined, + width: pWIDTH, + height: pHEIGHT + }, + parse: function(data) { + var idx = 1, + lvl = data.level = data.level || [idx], + plans = [], + node_type = data['Node Type'], + // Calculating relative xpos of current node from top node + xpos = data.xpos = data.xpos - pWIDTH, + // Calculating relative ypos of current node from top node + ypos = data.ypos, + maxChildWidth = 0; + + data['width'] = pWIDTH; + data['height'] = pHEIGHT; + + /* + * calculating xpos, ypos, width and height if current node is a subplan + */ + if (data['Parent Relationship'] === "SubPlan") { + data['width'] += (xMargin * 2) + (xMargin / 2); + data['height'] += (yMargin * 2); + data['ypos'] += yMargin; + xpos -= xMargin; + ypos += yMargin; + } + + if(node_type.startsWith("(slice")) + node_type = node_type.substring(0,7); + + // Get the image information for current node + var mapperObj = (_.isFunction(imageMapper[node_type]) && + imageMapper[node_type].apply(undefined, [data])) || + imageMapper[node_type] || 'Undefined'; + + data["image"] = mapperObj["image"]; + data["image_text"] = mapperObj["image_text"]; + + // Start calculating xpos, ypos, width and height for child plans if any + if ('Plans' in data) { + + data['width'] += offsetX; + + _.each(data['Plans'], function(p) { + var level = _.clone(lvl), + plan = new PlanModel(); + + level.push(idx); + plan.set(plan.parse(_.extend( + p, { + "level": level, + xpos: xpos - offsetX, + ypos: ypos + }))); + + if (maxChildWidth < plan.get('width')) { + maxChildWidth = plan.get('width'); + } + + var childHeight = plan.get('height'); + + if (idx !== 1) { + data['height'] = data['height'] + childHeight + offsetY; + } else if (childHeight > data['height']) { + data['height'] = childHeight; + } + ypos += childHeight + offsetY; + + plans.push(plan); + idx++; + }); + } + + // Final Width and Height of current node + data['width'] += maxChildWidth; + data['Plans'] = plans; + + return data; + }, + + /* + * Required to parse and include non-default params of + * plan into backbone model + */ + toJSON: function(non_recursive) { + var res = Backbone.Model.prototype.toJSON.apply(this, arguments); + + if (non_recursive) { + delete res['Plans']; + } else { + var plans = []; + _.each(res['Plans'], function(p) { + plans.push(p.toJSON()); + }); + res['Plans'] = plans; + } + return res; + }, + + // Draw an arrow to parent node + drawPolyLine: function(g, startX, startY, endX, endY, opts, arrowOpts) { + // Calculate end point of first starting straight line (startx1, starty1) + // Calculate start point of 2nd straight line (endx1, endy1) + var midX1 = startX + ((endX - startX) / 3), + midX2 = startX + (2 * ((endX - startX) / 3)); + + //create arrow head + var arrow = g.polygon( + [0, ARROW_HEIGHT, + (ARROW_WIDTH / 2),ARROW_HEIGHT, + (ARROW_HEIGHT / 4), 0, + 0, ARROW_WIDTH] + ).transform("r90"); + var marker = arrow.marker( + 0, 0, ARROW_WIDTH, ARROW_HEIGHT, 0, (ARROW_WIDTH / 2) + ).attr(arrowOpts); + + // First straight line + g.line( + startX, startY, midX1, startY + ).attr(opts); + + // Diagonal line + g.line( + midX1-1, startY, midX2, endY + ).attr(opts); + + // Last straight line + var line = g.line( + midX2, endY, endX, endY + ).attr(opts); + line.attr({markerEnd: marker}) + }, + + // Draw image, its name and its tooltip + draw: function(s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer) { + var g = s.g(); + var currentXpos = xpos + this.get('xpos') , + currentYpos = ypos + this.get('ypos'), + isSubPlan = (this.get('Parent Relationship') === "SubPlan"); + + // Draw the subplan rectangle + if (isSubPlan) { + g.rect( + currentXpos - this.get('width') + pWIDTH + xMargin, + currentYpos - yMargin, + this.get('width') - xMargin, + this.get('height'), 5 + ).attr({ + stroke: '#444444', + 'strokeWidth': 1.2, + fill: 'gray', + fillOpacity: 0.2 + }); + + //provide subplan name + var text = g.text( + currentXpos + pWIDTH - ( this.get('width') / 2) - xMargin, + currentYpos + pHEIGHT - (this.get('height') / 2) - yMargin, + this.get('Subplan Name') + ).attr({ + fontSize: TXT_SIZE, "text-anchor":"start", + fill: 'red' + }); + } + + // Draw the actual image for current node + var image = g.image( + pgExplain.prefix + this.get('image'), + currentXpos + (pWIDTH - IMAGE_WIDTH) / 2, + currentYpos + (pHEIGHT - IMAGE_HEIGHT) / 2, + IMAGE_WIDTH, + IMAGE_HEIGHT + ); + + // Draw tooltip + var image_data = this.toJSON(); + image.mouseover(function(evt){ + + // Empty the tooltip content if it has any and add new data + toolTipContainer.empty(); + var tooltip = $('
',{ + class: "pgadmin-tooltip-table" + }).appendTo(toolTipContainer); + _.each(image_data, function(value,key) { + if(key !== 'image' && key !== 'Plans' && + key !== 'level' && key !== 'image' && + key !== 'image_text' && key !== 'xpos' && + key !== 'ypos' && key !== 'width' && + key !== 'height') { + tooltip.append( '' + key + '' + value + '' ); + }; + }); + + var zoomFactor = graphContainer.data('zoom-factor'); + + // Calculate co-ordinates for tooltip + var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft()); + var toolTipY = ((currentYpos + pHEIGHT) * zoomFactor - graphContainer.scrollTop()); + + // Recalculate x.y if tooltip is going out of screen + if(graphContainer.width() < (toolTipX + toolTipContainer[0].clientWidth)) + toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH*zoomFactor)); + //if(document.children[0].clientHeight < (toolTipY + toolTipContainer[0].clientHeight)) + if(graphContainer.height() < (toolTipY + toolTipContainer[0].clientHeight)) + toolTipY -= (toolTipContainer[0].clientHeight + ((pHEIGHT/2)*zoomFactor)); + + toolTipX = toolTipX < 0 ? 0 : (toolTipX); + toolTipY = toolTipY < 0 ? 0 : (toolTipY); + + // Show toolTip at respective x,y coordinates + toolTipContainer.css({'opacity': '0.8'}); + toolTipContainer.css('left', toolTipX); + toolTipContainer.css( 'top', toolTipY); + }); + + // Remove tooltip when mouse is out from node's area + image.mouseout(function() { + toolTipContainer.empty(); + toolTipContainer.css({'opacity': '0'}); + toolTipContainer.css('left', 0); + toolTipContainer.css( 'top', 0); + }); + + // Draw text below the node + var node_label = (this.get('Schema') == undefined ? + this.get('image_text') : + this.get('Schema')+"."+this.get('image_text')); + var label = g.g(); + g.multitext( + currentXpos + (pWIDTH / 2), + currentYpos + pHEIGHT - TXT_ALLIGN, + node_label, + 150, + {"font-size": TXT_SIZE ,"text-anchor":"middle"} + ); + + // Draw Arrow to parent only its not the first node + if (!_.isUndefined(pYpos)) { + var startx = currentXpos + pWIDTH; + var starty = currentYpos + (pHEIGHT / 2); + var endx = pXpos - ARROW_WIDTH; + var endy = pYpos + (pHEIGHT / 2); + var start_cost = this.get("Startup Cost"), + total_cost = this.get("Total Cost"); + var arrow_size = DEFAULT_ARROW_SIZE; + // Calculate arrow width according to cost of a particular plan + if(start_cost != undefined && total_cost != undefined) { + var arrow_size = Math.round(Math.log((start_cost+total_cost)/2 + start_cost)); + arrow_size = arrow_size < 1 ? 1 : arrow_size > 10 ? 10 : arrow_size; + } + + + var arrow_view_box = [0, 0, 2*ARROW_WIDTH, 2*ARROW_HEIGHT]; + var opts = {stroke: "#000000", strokeWidth: arrow_size + 1}, + subplanOpts = {stroke: "#866486", strokeWidth: arrow_size + 1}, + arrowOpts = {viewBox: arrow_view_box.join(" ")}; + + // Draw an arrow from current node to its parent + this.drawPolyLine( + g, startx, starty, endx, endy, + isSubPlan ? subplanOpts : opts, arrowOpts + ); + } + + var plans = this.get('Plans'); + + // Draw nodes for current plan's children + _.each(plans, function(p) { + p.draw(s, xpos, ypos, currentXpos, currentYpos, graphContainer, toolTipContainer); + }); + } +}); + +// Main backbone model to store JSON object +var MainPlanModel = Backbone.Model.extend({ + defaults: { + "Plan": undefined, + xpos: 0, + ypos: 0, + }, + initialize: function() { + this.set("Plan", new PlanModel()); + }, + + // Parse the JSON data and fetch its children plans + parse: function(data) { + if (data && 'Plan' in data) { + var plan = this.get("Plan"); + plan.set( + plan.parse( + _.extend( + data['Plan'], { + xpos: 0, + ypos: 0 + }))); + + data['xpos'] = 0; + data['ypos'] = 0; + data['width'] = plan.get('width') + (xMargin * 2); + data['height'] = plan.get('height') + (yMargin * 2); + + delete data['Plan']; + } + + return data; + }, + toJSON: function() { + var res = Backbone.Model.prototype.toJSON.apply(this, arguments); + + if (res.Plan) { + res.Plan = res.Plan.toJSON(); + } + + return res; + }, + draw: function(s, xpos, ypos, graphContainer, toolTipContainer) { + var g = s.g(); + + //draw the border + g.rect( + 0, 0, this.get('width') - 10, this.get('height') - 10, 5 + ).attr({ + stroke: '#FFEBCD', 'strokeWidth': 1.2, + fill: '#FFF8DC', fillOpacity: 0.5 + }); + + //Fetch total width, height + TOTAL_WIDTH = this.get('width'); + TOTAL_HEIGHT = this.get('height'); + var plan = this.get('Plan'); + + //Draw explain graph + plan.draw(g, xpos, ypos, undefined, undefined, graphContainer, toolTipContainer); + } +}); + +// Parse and draw full graphical explain +_.extend( + pgExplain, { + // Assumption container is a jQuery object + DrawJSONPlan: function(container, plan) { + var my_plans = []; + container.empty(); + var curr_zoom_factor = 1.0; + + var zoomArea =$('
', { + class: 'pg-explain-zoom-area btn-group', + role: 'group' + }).appendTo(container), + zoomInBtn = $('', { + class: 'btn pg-explain-zoom-btn badge', + title: 'Zoom in' + }).appendTo(zoomArea).append( + $('',{ + class: 'fa fa-search-plus' + })), + zoomToNormal = $('', { + class: 'btn pg-explain-zoom-btn badge', + title: 'Zoom to original' + }).appendTo(zoomArea).append( + $('',{ + class: 'fa fa-arrows-alt' + })) + zoomOutBtn = $('', { + class: 'btn pg-explain-zoom-btn badge', + title: 'Zoom out' + }).appendTo(zoomArea).append( + $('', { + class: 'fa fa-search-minus' + })); + + // Main div to be drawn all images on + var planDiv = $('
', + {class: "pgadmin-explain-container"} + ).appendTo(container), + // Div to draw tool-tip on + toolTip = $('
', + {id: "toolTip", + class: "pgadmin-explain-tooltip" + } + ).appendTo(container); + toolTip.empty(); + planDiv.data('zoom-factor', curr_zoom_factor); + + var w = 0, h = 0, + x = xMargin, h = yMargin; + + _.each(plan, function(p) { + var main_plan = new MainPlanModel(); + + // Parse JSON data to backbone model + main_plan.set(main_plan.parse(p)); + w = main_plan.get('width'); + h = main_plan.get('height'); + + var s = Snap(w, h), + $svg = $(s.node).detach(); + planDiv.append($svg); + main_plan.draw(s, w - xMargin, yMargin, planDiv, toolTip); + + var initPanelWidth = planDiv.width(), + initPanelHeight = planDiv.height(); + + /* + * Scale graph in case its width is bigger than panel width + * in which the graph is displayed + */ + if(initPanelWidth < w) { + var width_ratio = initPanelWidth / w; + + curr_zoom_factor = width_ratio; + curr_zoom_factor = curr_zoom_factor < MIN_ZOOM_FACTOR ? MIN_ZOOM_FACTOR : curr_zoom_factor; + curr_zoom_factor = curr_zoom_factor > INIT_ZOOM_FACTOR ? INIT_ZOOM_FACTOR : curr_zoom_factor; + + var zoomInMatrix = new Snap.matrix(); + zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor); + + $svg.find('g').first().attr({transform: zoomInMatrix}); + $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor}); + planDiv.data('zoom-factor', curr_zoom_factor); + } + + zoomInBtn.on('click', function(e){ + curr_zoom_factor = ((curr_zoom_factor + ZOOM_RATIO) > MAX_ZOOM_FACTOR) ? MAX_ZOOM_FACTOR : (curr_zoom_factor + ZOOM_RATIO); + var zoomInMatrix = new Snap.matrix(); + zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor); + + $svg.find('g').first().attr({transform: zoomInMatrix}); + $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor}); + planDiv.data('zoom-factor', curr_zoom_factor); + zoomInBtn.blur(); + }); + + zoomOutBtn.on('click', function(e) { + curr_zoom_factor = ((curr_zoom_factor - ZOOM_RATIO) < MIN_ZOOM_FACTOR) ? MIN_ZOOM_FACTOR : (curr_zoom_factor - ZOOM_RATIO); + var zoomInMatrix = new Snap.matrix(); + zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor); + + $svg.find('g').first().attr({transform: zoomInMatrix}); + $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor}); + planDiv.data('zoom-factor', curr_zoom_factor); + zoomOutBtn.blur(); + }); + + zoomToNormal.on('click', function(e) { + curr_zoom_factor = INIT_ZOOM_FACTOR; + var zoomInMatrix = new Snap.matrix(); + zoomInMatrix.scale(curr_zoom_factor, curr_zoom_factor); + + $svg.find('g').first().attr({transform: zoomInMatrix}); + $svg.attr({'width': w * curr_zoom_factor, 'height': h * curr_zoom_factor}); + planDiv.data('zoom-factor', curr_zoom_factor); + zoomToNormal.blur(); + }); + }); + + } + }); + + return pgExplain; +}); diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index 0f4a0a276..73fea1db4 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -68,11 +68,54 @@ body { Toggle Dropdown