From e128b3fc5605887b83db67a88f0c92e9a9131b4a Mon Sep 17 00:00:00 2001 From: Steven Garrity Date: Fri, 24 Feb 2012 12:50:10 -0400 Subject: [PATCH] Initial setup of Collusion page Bug 730012 - [Collusion] Build Collusion Landing Page on mozilla.org for Gary for TED --- apps/collusion/__init__.py | 0 apps/collusion/models.py | 3 + .../templates/collusion/collusion.html | 401 ++ apps/collusion/templates/collusion/demo.html | 197 + apps/collusion/tests.py | 16 + apps/collusion/urls.py | 7 + apps/collusion/views.py | 8 + media/css/collusion.less | 105 + media/img/collusion/ford-foundation.png | Bin 0 -> 15039 bytes media/img/collusion/nodes.png | Bin 0 -> 26042 bytes media/js/collusion/collusion-addon.js | 14 + media/js/collusion/d3.geom.js | 825 ++++ media/js/collusion/d3.js | 3586 +++++++++++++++++ media/js/collusion/d3.layout.js | 1817 +++++++++ media/js/collusion/demo.js | 87 + media/js/collusion/graphrunner.js | 455 +++ media/js/collusion/sample-tracking-info.json | 781 ++++ media/js/collusion/trackers.json | 2581 ++++++++++++ settings/base.py | 11 + urls.py | 1 + 20 files changed, 10895 insertions(+) create mode 100644 apps/collusion/__init__.py create mode 100644 apps/collusion/models.py create mode 100644 apps/collusion/templates/collusion/collusion.html create mode 100644 apps/collusion/templates/collusion/demo.html create mode 100644 apps/collusion/tests.py create mode 100644 apps/collusion/urls.py create mode 100644 apps/collusion/views.py create mode 100644 media/css/collusion.less create mode 100644 media/img/collusion/ford-foundation.png create mode 100644 media/img/collusion/nodes.png create mode 100644 media/js/collusion/collusion-addon.js create mode 100644 media/js/collusion/d3.geom.js create mode 100644 media/js/collusion/d3.js create mode 100644 media/js/collusion/d3.layout.js create mode 100644 media/js/collusion/demo.js create mode 100644 media/js/collusion/graphrunner.js create mode 100644 media/js/collusion/sample-tracking-info.json create mode 100644 media/js/collusion/trackers.json diff --git a/apps/collusion/__init__.py b/apps/collusion/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/collusion/models.py b/apps/collusion/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/apps/collusion/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/collusion/templates/collusion/collusion.html b/apps/collusion/templates/collusion/collusion.html new file mode 100644 index 0000000000..6e8401905b --- /dev/null +++ b/apps/collusion/templates/collusion/collusion.html @@ -0,0 +1,401 @@ +{% extends "base.html" %} + +{% block page_title %}Collusion{% endblock %} +{% block body_id %}collusion{% endblock %} + +{% block site_header_nav %} + +{% endblock %} + +{% block extrahead %} + {{ css('collusion') }} +{% endblock %} + +{% block content %} + +
+

Introducing Collusion

+

Discover who’s tracking you online

+
+ +
+ + + +

Collusion is an experimental add-on for Firefox and allows you, for the first time, to see all the third parties that are tracking your movements across the Web. It will show, in real time, how that data creates a spider-web of interaction between companies and other trackers.

+ + + View Our Demo + see how you’re being tracked + + + + Download + the Collusion add-on for Firefox + + +
+ +
+ +
+ +
+ +

Take control of your data

+

We recognize the importance of transparency and our mission is all about empowering users — both with tools and with information. The Ford Foundation and Mozilla have teamed up to develop Collusion into a fully functioning feature for Firefox - that will enable users to not only see who is tracking them across the Web, but also to turn that tracking off when they want to.

+ +

Telling the global tracking story

+

Your data can be part of the larger story. When we launch the full version of Collusion, it will allow you to opt-in to sharing your anonymous data in a global database of web tracker data. We’ll combine all that information and make it available to help researchers, journalists, and others analyze and explain how data is tracked on the web.

+ +

Building user awareness

+

Through our work with the Ford Foundation, we’ll be building outreach campaigns to help people understand online data tracking — both the benefits and the issues — so they can make their own choices about how they want to be tracked (or choose not to be tracked at all).

+ +

Collusion is about choice

+

Not all tracking is bad. Many services rely on user data to provide relevant content and enhance your online experience. But most tracking happens without user’s consent and without their knowledge. That’s not okay. It should be you who decides when, how and if you want to be tracked. Collusion will be a powerful tool to help you do that.

+ +
+ +
+ +
+

Want to hear more?

+

Get Mozilla updates:

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+

We will only send you Mozilla-related information.

+ +
+ +

Supported by

+ Ford Foundation + +
+ +
+ +
+ +{% endblock %} diff --git a/apps/collusion/templates/collusion/demo.html b/apps/collusion/templates/collusion/demo.html new file mode 100644 index 0000000000..78b08ec1b4 --- /dev/null +++ b/apps/collusion/templates/collusion/demo.html @@ -0,0 +1,197 @@ +{% extends "base.html" %} + +{% block page_title %}Collusion Demo{% endblock %} +{% block body_id %}collusion-demo{% endblock %} + +{% block site_header_nav %} + +{% endblock %} + +{% block extrahead %} + {{ css('collusion') }} + +{% endblock %} + +{% block js %} + {{ js('collusion') }} + +{% endblock %} + + +{% block content %} + +

Collusion Demo

+
+ +
+ +
+
+
+ +
+
+ +{% endblock %} diff --git a/apps/collusion/tests.py b/apps/collusion/tests.py new file mode 100644 index 0000000000..501deb776c --- /dev/null +++ b/apps/collusion/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/apps/collusion/urls.py b/apps/collusion/urls.py new file mode 100644 index 0000000000..2313f430af --- /dev/null +++ b/apps/collusion/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls.defaults import * +from views import collusion, demo + +urlpatterns = patterns('', + (r'^demo/$', demo), + (r'^$', collusion), +) diff --git a/apps/collusion/views.py b/apps/collusion/views.py new file mode 100644 index 0000000000..ccab2a5ad7 --- /dev/null +++ b/apps/collusion/views.py @@ -0,0 +1,8 @@ +import l10n_utils +from django.conf import settings + +def collusion(request): + return l10n_utils.render(request, "collusion/collusion.html") + +def demo(request): + return l10n_utils.render(request, "collusion/demo.html") diff --git a/media/css/collusion.less b/media/css/collusion.less new file mode 100644 index 0000000000..7d983168c1 --- /dev/null +++ b/media/css/collusion.less @@ -0,0 +1,105 @@ +@import "sandstone/variables.less"; +@import "sandstone/mixins.less"; + + +hgroup { + text-align: center; + padding-top: @baseLine * 2; + padding-bottom: @baseLine * 2; + + h2 { + font-size: 32px; + } +} + +#primary { + padding-top: @baseLine * 2; + padding-bottom: @baseLine * 2; + @shadow: 0 1px 1px rgba(0,0,0,0.1), 0 0 0 1px #fff; + .box-shadow(@shadow); + background: #fff; + #gradient > .radial(center, 45px, ellipse, farthest-corner, #f5f1e8 0%, #ffffff 100%); + .clearfix; + height: 13em; + margin-bottom: @baseLine * 3; + + img { + float: right; + margin-top: -@baseLine * 3; + max-width: 50%; + position: relative; + right: -15px; + } + + p { + margin-right: 40%; + } + + .button, + .button-blue { + height: auto; + line-height: @baseLine; + font-size: 18px; + padding: @baseLine / 2 @gridGutterWidth; + width: @gridColumnWidth * 3; + margin-right: @gridGutterWidth; + margin-bottom: @baseLine / 2; + float: left; + + small { + display: block; + } + } + + } + + #secondary { + padding-bottom: @baseLine * 3; + } + + +#updates { + margin-bottom: @baseLine * 2; + p { margin: 0; } + .field { + .clearfix; + padding: @baseLine / 4 0; + + input[type=email] { + width: (@gridColumnWidth * 3) + (@gridGutterWidth * 2) - 18px; + } + + .title { + font-size: 14px; + } + + small { + color: @textColorTertiary; + } + } +} + +line.bold { + stroke: #000; +} + +marker { + fill: #000; +} + +g.node.unrelated-domain circle.visited { + stroke: #a0a0e0; +} + +g.node.unrelated-domain circle.site { + stroke: #b0b0b0; +} + +g.node.unrelated-domain circle.tracker { + stroke: #d6a0a0; +} + +g.node.unrelated-domain circle.round-border { + fill: #e0e0e0; +} + diff --git a/media/img/collusion/ford-foundation.png b/media/img/collusion/ford-foundation.png new file mode 100644 index 0000000000000000000000000000000000000000..f2c64b7dd3304489cb0a1affb1689ee1fc5600c8 GIT binary patch literal 15039 zcmaL8W0WRAvo6{_ZF?Hi_O$)BZQHhOPTRI^P209@+qQ4dclO@r{|@ ztc)D>@LjDetsU51xd{J*%l=*e*G)@^{~r=Z3ogR{qm-(YEWUt^y%9b$4IQ-s9Sa>k z3q1`zBNG4spu%UMqo<>#W1yvHpr!|~GcvH#(c%BsMffev-q4s`UQqbIWPP`|2u&Rw zZP{sQU0hsfTo`F=>`iFt+1S|rVK6XIe^XF9xLG^uxl&s@5dAlTppk=ty_v0}nT<96 zKSVu!8z)CD!f#Fg&k(F^{|DCE;lIrEZ7^C_JzH9O8oGZ&`foxhssDdcE35yZc5sw8 z`oH@Ae*!xwy4f1h${RV@IN2L~Pnq0`^9FjyCp+Ha3?3ZADpA8%G-lQyW`+ z0R?7!DLn%->woS4&`U|Ni&;B3>RB5Yi3xHMek0JBnHjP(3If>Z_=Fjm0raf&^g{Hk z06Ka$L1955Rz7C7Z}a~*ub_>Ala-ORJ6t=gq z!vD{Z+0FiUUFiQ;y#L}g{NHt9_+NQxzm=i=H?jY(iT>}dZ}0h6{U5jeZv2nq8(DvQ zyZyIYlWqC80|5aT#RT~jU02ULAUqY7R_Q+189I#PMu_7#37}#EHWQ1M5z*!cf?D-~ zVXB^2Wi5}(j#q_LRCxbVK~s?9OT}(V{6U-*y)t?o?|9;Hajn80{PXI4b=mp#mE3pr zPCC9mmvwi4#CF6GN8IzptJnG>{R-0cl#n&6@EI8R)nz#)pO_1&+AjmurIH1F>JjT+ zReYb2b=#FNZPcq2Th>!1oChCwO0*CyIIb{dTL4`Ee(l2ZsdGy3{14&@kzK%RG{B0G zIY^P?liFuX$Q-8BN_1M-b1|oXERX3@+F4O76r%T;>GYwm^oGv#9Kw;R7{>y)QT4lKS1e};PsyvMr19yt_JcEC-Phe zQEEu>=`Nu^J!yDb;8v^K(pu_YGP1p6v+SYsnmTi#p;cNsryTahVxdrz6If_%C~q^l zhEMoCZ?dN_Tx)X*?xS7N(?()dWX#Zob?;`vP+3Ce-fGEomKGevEkzAK{78et5Z!^f z$t12=(5O;({THivIj!CyFIA!!picdc)O=@1NT<*OGD`VV;b{O#GH}Q)&X}+Bv-6Qvf zEr#RXl5tS++nG^!xee(lv~T>ZUOd$wwMYZcs3^hwX%lU3sMZLg^vN3_coloeRapEE zfSHr(jo{yBPuQ<94M)It+P-A5o8C08roe!v=Nyty`x(hJI_dE;Cugq>8~E&nH@Rmn z4*%m3f3pK81K3%1l^b^>qU$JdTdMTCr zu>!WMsbuAMTDF5~OGF$0d<9Q_!^la?xpjn~#!!AvY=WersiMq+!$P3vR=lcxqmNAr zD`Y|n-Hk=g8icPa`s?;?V*PSg5z;NxcZ`R%uS`c8e_!4wQ7M(ae`>N)E#_JI-SfL&H7XJ zdL8symOf5e_o!Mo)b(7<6(mxPy@qTszm!K&X>(ye)YX1S=HNR39;|ZVC*6d;n<+=< zeJe`OMJZNBQLWeYrq8ILG515g1Cvz80Bq!q1vNr-F^`dW{`beT7quP7q7(!Aeb?5P zTbwN{PnD9I zO~~x0t2i>)Z`JA2$x&_F`Bf5VZCG#gf_fN{NteDQYkqxQF&=KNp}ifqqR@?C?vSf)w9Vz~iAxS=$dtqSZsO zIE?-DU!yM@O2`0i9tmPWn(|pCa-%&s%NLb|KagB$aX?}m<|gk#vC;1UGi7P$m3Z* z8%UH9+f>SEGXeITROD|So9@QbkVbjX_>(+OG(y5~up>)W1bvE-ePL3}{zhd@6WA)H zaD!J{FKSbo207Ar+@rGIhURNEgg{4gXj1nKe?MDnLiXsbkqeG%#y=O#&7_Q<)bi45 zVA@z=p@O|j{tl=7no#o1dC!>@tUN{&+TwqXZ9jvydU%CcIlMHwZ%&x)mXiZ}E6K5g zd)Qp?+YeU5JVYE-l`KO`Wl4jh>L4Ou>x53_BBJd{5Jesl(1 zkT*~u(3V}LJIVs(noZ5WbZE+4lP8}};GHQmcp9zORN%+$?p+P5ivXyIa&r^<(Q1G} zlfI5J_EP9Y!v=mKFM&KtpjR8leFfgQpSp&mEel zbiX~IN6dnhnxA?z5g$wVb_~m;V9eqm8EgRHW#y3_Ss~Foe6TI{^E#v@-0maC=1tg) zd`!LdFc0kR&qPNnoCY#Uz^Xr?8k*S|pX$Pnc8eX{&3u|ZQqatDG5V3ZOix?Z>Yian z9d34_p0e*01znxq*NrI5=%(T>r_nQ%Cupn&B4jFSNJ~RQl$;r}F0^?SNSh zBp-Oh>vpNTtXV_OuK)JsZEvW5q2}(hDKmTlyK_#0lT+M{8_e;zv8lytXbqb%MhzZM z)EI!-5;$E*qO-o+R~P{uS&E)>j5YSpSuEWLDm{5okYtYA1l$9kv#H7C7{H zCAMVR(16+E)hWDx$*)>si+9j*s^i`6+Pt(;IC`@7g+Fx1;3vGGtn|pGq^v)3oQNJ0 z>TpTAbs>zgu)W@UGK-myrC}rco^qQ=o>Fy z8EaVsKZBc$vCXPI*+U-8_=2G%`u>M5K9CQo)L#adD@J^g>hK;%ag^>aI@IsMy-O@j zds7|kAqJC`ss3K0VV+RF6KeECb+UtdYI8lP*4UO*NX@i4kAq5n^;>+RNw$ltxY@EX zag^|?Ov${p(MvaJDj1lH#YOgr8(rIDHC_e|m4+MyY1?y>*cTJRes2-nzNiL*t*-;%-{B-I}`6s8t6IZFaKll&eoh5CU+rY8C;C64e zzlL+0sLJ^Oe7`~5%{uDKj~AKK#;UE+`i3^M>Ja8Z>)NPRSIgOmq3pB7q|7#$Sn`Yq z6%_@!>;8)-Ll(tk0rF=HDH&(q;|S8>%Xp|cEhZj^gBAJ}~v%33Cut_vLYEZww zi1>7IN91o(idfWwLTfp?reF6GU-w7e0<`VNK!q#Ak}M8qtyf`LnQl$mg@**hjt2Yj z-s&4PuG=-iyO-{})xi7DkAttL;|Mz5G}a4oKFF-h0+N)EHe0uZKHkkJy-bNEqDI@*wwF4H~e~!zzIe6 zq$Yd{T25cVHYtaeX6Ak_DqiTBbH&5K{YDfV-tE_ml%y;3{D_8XNZj6HB?84JJ!fcq zkeQd$7cjh@!h}?BJH_h$BW3P~JrL)1H}pMV5}lmDCQ-U$dk>_rExJ2*cjM)q&&7jR zW(<_Uk4Gn?vwXcss9C+TG*5d`gS?0-4W9Sd8G&}64*V`_4!h`Ppn>y7| z@X+>jf8+f@UrLnM(Nep=j-0cH(4nh%DRtq=eF{<_Zsn&7ON$lzsiLZ&!Clh&j+max zD64o<@#%;|w*-GN*52io?4~Om_IuMjC@6|%*}XlZrCH`@#A!P9%c({xTEKFLHIF4h zf9FFm7ve2M&o9(&8M>Q1B(~{d-FjIA@3@VwgRGMNgqy{ZW$x)&O>ky3i_~*(gs|2W zz1(2P%?leQ`m1y6J!wZ#xh=_%ky!>?RFWY5^0~=hsiv0t*C#9_T= z(vpclsWSTNt;4ny@C`rbUaPzQaycAj=ayB0y#6eZ0BAcKkyCad{@~+Dz+RrPR&9yX z=yV0^G8t1oo4fL1az9>{h$s>Df8E~wZ%LHz_@HtToC1o@BM_B}AW@Y%TLJ`QJP z;P68j4*#n2?{EOB_qa8$861xC9SYy`d<~BgUz8H+!9i5>a4M$u z?&r_?cRm8^s@q02y=sxGvfYYZ4<;BrM=&uPYVp?3oXO5V?fl($bdKy?wAdCNhyBoL zTMvBM>`KH&#@8`np{WE!-LDowu2&5lb`LwA?05{@y-_`fv57mT4uHFOc(33@hGsA+ zDu`{uCYF?wk8DbG?_h?&Bi#LW$@PLwiUTviD__I$Sm@U*&hXh6;azl?uozI{`zBLr z_8@#yO=HtpuKWb7xJ_>ua!eFsj_7)s4WRX|Q+)N_NC~{kbY(C`@Hnn%Jg%p~LuyRA zkH?Z=u(ZroC$=#VlOv)IhR86M(Os7QzMVb46G)#SBoGCK%0~BPNvRdW#2^Vrbt%49 zt9)$=5p-Z6{jyddp6A7Z{A^9n?T^qx^E&g zN$+~Uc5r>oIyr6Xe9d)nzv8Y9Dl98#6h31a@+B&zo&kt^wG;nL2jpNrEgt)-{}epBNk z(&PDg%y2}`?+y)0{@0LH7BYw7dO*RE@uaBhMU7k9bHFoqymgwK3zl<)bR(T21&)9i z4EyVi1Z1cQt87ig_yuZpU79RedPX8-$E`yRX2jj_ZiwfLcjL$UI6)$$}t< zNuMVJXB2sUAaQ6+Ts(+B4Yo?Syi5=X$4=SrMa*c<)ccn=8&+-iYq{g&R+f(k+D<6U ziA8DRTW3Q@F;MU%8J=e#QcVspeH>@i&V7{T+r>4hr~pN`>a+>vvogIsnpsM^c+7yY zjh{oMim%=R!4R(qcwWeXQV`~Fb^*un^MbE;oKyI*BR~3AH&+vG<~N$s&&teochVYi zee(yCa+FD%5+U0w_o`bL=^f7pM(#|u4qUAt`*fr0inZK79Kj$Y`}y$?^U(%1-3cn5 z&$BgWuJr;9c~EIP7-T?t1hfl{eN)7!8UV0T$MOLng#XmaFI)Va!I=S>OwZ+*w=h+Q|HLc^E%)|YCN^A5PS$wWTqtpvS1K5G%8l7w8$EK zH>=4mD!88?LRpH^G3XIB7y>4tNitbP+2nwLiOWS>1Mjj8&#+c^2HT2yKBhX}s@w6FqC!ztJG}(sVYpasLiuRi<029!$6f-Z zA&ECDdee-V);=F~cEnIqvL!uV!q8d-N2Ft%*^)va`kLk$PoflmDrKnPzfIN%r#!50 zN1Z&vcKMV+sMA#elmGJgM_ods+eH+puF?wqa}E9QWd zhuI^V1b6}S4-1!NMd0*qMdt~l2Rel39$E;4>%P72MdNVztb$Zieex0t(!6m#=B^Z1 z05Y7+>s8;sEgetI=Gy=HsvwghqUotdE0LhJi9+3Dgb0b!a^gEwI7o_qcbhL``Ua27 z%|uPMB_uTAh$Q)>QsbC(JN=4^P{-nRE%SKxQ}HH>snL0a3Lcb4N6TBj;j(;6Rk=7R zNT!v>@dS?1FMp(1*d~^xLUl5O-7dfkU;Su#VId|;8Ye-~nBHt-%X+z-5N0n#zrXYOf)sA7e5b~O z6#F7QsTn!88O1vj1feUIn9rnfMqZ@DV@%llsE9fy%Pjcp((`XB;tL<_krjaxq0j)}G5Z{43sjw8sB-O4yG9lOJs*PuT0RM~+`dqsf|R+f^)RluI%5>PB)w$A^0l ztRX3Es4?`;a}NJ@jCOdSncs6(mf}{B+rrphvC;%Fef!` z?gD!BEt(%_pTZAFnE#qBG>LOVkXd;4D-y=3lW_6r9uoQR+;w-f>UO95bN>OQ0Ckz{ zm8a(g*kkpcq~>Q#YFc+4_XscGcxFA_1Q7?>r=97NNp3j`Ron4CL}uYaX48JYkJ{)m z#dp5EEF99f&OCCS4${e@c=1sS=Je>q(nxi2jul*`S&+E z`|DQV{K3{+@SprzQaA~CJQW#JCVf>-glZh>8cY|J6SpZZ1wX%?=bn@~Dg`1akapaN z%Wx57P#aQ?-j0vLSLnBQKo{pDpCI&0ogbq8k&ucsFxL)3u=B?_sK2i*XR4l2D6ZdY znYe&}m6G|w$R1b$Jdsm@CZ$9)Txk_~q61RK!BF^%LgItq1rA9H)l5T30m*TpGCybW z^%q0M+AG4ZoluuF-6DXMgDK2=*pI<1QuS31C z_aVhO%DJDBp9z2UxF(+54@IO-FuvTO~PqM9#$0eTJ;s!<<= zVNU#Sbm%XzZd`Bdp3Nr86tUL=Jn~HgaK%v+drdDyw{aA$A!O~WyrhI$8d@u0gCc*4 zV5Aqq>AC-~s{k**&1?^PJ?|8vb~b@M|8TpjW-5}ULlhSsyPa^`aoZWXV(Hz|$;u8a zJsDxO`Fz@MrL5U>rpO`#DW){@T>KIu7hsI~FYcdRtQr2(1y|WXpQ%lZDv|@F4 z<*7@Y-pPh24CNeS(&f>cVj7}bO=qYq8aU8C1f7EtQjL^f3NJ!w2$erup4(CdZLG}} zW77UOx6Kr?b%?e_62kp(mK*FOQR;FdZzjE88*@>#+UNwt=V7Wa&(_%XMQ2+ z=YMaK?^4+?)26mOCg=`t%Wq?@E5f$&($4`TBE-BTAfZT1LQLBfgCaR;alhkd)WlRW zQ5|$k_6w>oMEqioBZ8A&6}r>`zNziXQwbVZ3>TZtBiD;_cPn{%M9AP;biR|29|FPZ zlbfuB^eKO30=ZN+@6p&4$-i49Pgtlb~UPiW0}@FF2bW=veDjJa^`nKSC@X zHyrE*9Oue?5mk7*-7+`I0#Z!vUpMU+DtfYCD!vjd+0^`pKQA=}q735eHSJ?iF8`i( zyc2L*nP}mbzp~rI{Wa_@cCX7%E=tbR{+nU|UBPWP#Asv5VkfRtML=IY>6X)=W+jl) zrc=!Gtp$C>O9Q|P6C5cf(u2$i;U(I2l!G!x?}HI2d|P;Fp{Pf<7y{n-7L5)jtEvx z=HjPDecoldCQjvGEy@u71|~$`tmXAijk2?_aD@OGJaj-ymRbz)-4L0UWv&*P5&QUf z)~@|wo}Apw@%i;{vw|TR<b1mU&;tt{Xi|2 zy~Jjdeu3Q8YX`+y=rN47!ucfQz*38*ltdm)W1~uW_^WDBk^B&1D1qa4rbj|xU_J6e zi{|+10-EZ^7{&XaKQ@63>V|sGC|Mwt(>Evs`2E!LcYL&;GOZW)B~+`|V7wsUe?rB! zp_z#p<a$Hs4c5g!c6 z-WDce`-56YdP4WX$SSE@@}~^yTIHwsV=mD~+uXRVFAkpCW6*i-pBNzhAi$GLI@j|x z<1;Q?CoU+O6C;GOdE}-nyNuOlT?gzb^N95lW$dO_QzX5L>v?xn;tJ>2NGeZh7Bwb783XQ z^Ug>vV0vyE{7Keq!bi|B;O+~(OYWtlOb9z+$0Nv?8iX(c>Y6v7nwm@>jhtWuh z3CS5Njjlio8#71se1o~)1s1(*!+Vc|%tO{XU7OSE$3b~W0)0Km(v>^Z%G77hPNqnt zB*Ot{P8?z*Y4nZPx@pmmd21Dlg`Y7IK2Y6w! z_Jpw3*v4a2m;|$ub2}R3J~rY=@ZWfKyiMUz5-^ksr14{r0N!`Z zlIhGG>}>figuAJa*fW85FND2pxS^$H7iJv}2Hu)=Sk~EhIw)M!$T_}?`p@{ zAsqD<8jcQHFt8YG!t_YXkcY0~Y;AO}ag+~Dlc}k9lQ1^De_@8~y@H}(Eow^!vJ~>> zOr|qGXR}3~0bK@%k~eX8NiM_HSZ=$s{iOgp9?y;dwb!0<&T7CP#B09=WH6LsN&kZL z_G`qgsAz-BwZ#$1_gt-kJIMsz64s6G-mn_!-(v;P5(}Z|_XrnMRdSUpwH%8DM%`CQ zye2gg$x)ccgz;#u@F{G`TH7!BYt|cQTkkO*BRP4^e*4XPA(u2kduo*x*osp2ww?eJ!t_@7wu@*-I4*pLcyEbmqH8Vtb@I<@;`5 zyIR5&vXE;b>6pgt-q`@Vo%fcRvrbJ2wH4{+_aKdSRzN~dgyglgA^;)ZpcqEau*U?h zLfk0b^RA+|h4E@n*{hY@ZI#PsM98pKj@uVwzE)l%{VaYn{flX?jV_ulxn`%28hb1) zuC7jr$Ghq0X*s2(U1;gbG7Z=fQ6DqO_T5U<)h6^w%Lktc(;V6SANJ$f2-u=X42XA- zJDqu)^^T#>`;NMl7MsqA_ZT=)Vdz%hJC}KD2aVC zt&k}V_|}0_sq3H9nqyUP>ATvl*93=w1K~Zo3_%NDcVhQ-ljF`pMvQoeobV{G#u8w{ z+@69lqG*HYWxxUz-w_W8f*uG?K1PH3tmC7G{>I~hzf@!g>P)-n`A+Af1|B)>`8e`a zKU3Drc~qUu2XIMJe%roONy$IRgk!2mQ1=gz^6vgEC!ulm|H-r?h&+;`#E9ty-g}y9 zWSgoyC5Y{HKB@gGBtslg3Bz?@gt1ssTzewf9J&)P>B9Y5@4OYwBjVF`PdSZI#Bt@O z-`NuzlMw_;7=1#{3wqSQ-+UM1Ul{@Ez&+d9_)lRpXj_**4V)QYQrW z#C4Z9mYQLg!2V|^C#E|vB&Xym%s@&{^Re7IGwZL}gUSu@=AbEp>{j z`ka51(OI?($fN7tWi>4v^6%o8j(%-=9x^w-pN5a%ayhzlitY1gJhR8At-(}vIXF8= zYVu=>D*^^!ad4X`?Z(pJqGYzbaEiv8$%YIYS$0|#Hv|y~X^(SC%TXHmuZ-rb=__?w zIB#CwaCOh2pjyziS%myO7DF!#T}8eZ&?HltcHtfG{bH_rbuw#GKQ-td&GHB3rK^93 z@(b&es(+m=0H_$rbSmD?GTM6f%)uMfH!@d!7E{-O?8B5bBKFA9ZGO3a9FjUa+l1dA zQE_3fcELMez5b}j!eP~)=&nDq_FdZZ%>$0lsePntYqKYUl{qK-_+1Vp z-LTaC+7X`Go3NwluFu1V_-?r&(p0C_mgG<1g})3D1uUaPuSlIWfQ3+HlnJ0Ubj_D? z-Qf*BUGb_sf9TLBn6lms4!;-75GkJ8atA{#&E=Kl4V8-y*%Hk3v)4=i>y?nYH1#+x zB9WKfZop3EZu7g&>jM028ftHx%`03`A5`F`^J2Zx=`1zv6#h6d`^O#kvqyJoniJ|< zy=Wfd!BgnZ)Oh3y=v~q!c@bLbc3*ELk>KLqZER!Y?KN-Tep@8dQ&fG@MVy1Be(=d~2!>oAZ59J|O%_T$-Y4_hq< zXmigka8@%|DklwHjduv0C+kO|QWdP}BwMVEP7BOjh!5^Bj#SK;Hj7%-&nEEzbP}JB z&^pSR;0`(-BQ7&afJ@#w-b;Y@dZI#VxsCeDTjr6D!a{bLWHzUlu{pnaEoenrX*s!p zGWA=GMx!mg!ww00Z-43B8C@-5Uk@U(U!G1-JUMwpDbyPrV<>&H)WQ~>gUBJr6s_(7 zHe+6yXqRgfNhoiif4+xv-zqJS1M)8o76)P#69@;xDd)?hD<9pnvQxcPEkN)07xZHK zBvLQ5+3W#&Zj$;-VCl1?g7qjZLw0G_H%7a)5r{s^XVzj%Ah)<73GF3gOxbMZyz1>g-JPv z{buD4E20+bx!*Ado)5*_iU*!v{pw!F_2)t{S`foZSb|9a_Z9NpE=p1A34BZFgbquh2ane-idsw%K}gn&GY@qE%E{6g9ey zl)NTkqh-M5SFd5@nA+%0s<843E!;q(XQtap#-Y4H@Ib7eDF?KN^(bm|qr#J4OIw5+1^&BlV8*EJ*G z@pa{{Hg~JRp=!?ZkDE59ar7%%J^$S}Tl=ze+bEgFKI$ha1Sy|uF%?gFXs;5IB)u2c z@V?7wbG7XjTjlIsiDTI)Bz1%HptUcHs+PwpwIDc3<(`0Y?}*>n?BYprH2E`S4MReV zRmwc|EJHAdy7u z*W@x0jtV)DW-v$-QzKX9=Ki}A^awA6WcszWjbnN5=`C@0r7ASl-^@GkXn}LnF9{3x2 z3^#oPa33X(Ba>p7S(Jm~^}WfJph&Q+eV*;sZa_TrAFVcs`go!GfK zk3ZiILV$`(Zo$hj!6Pvb(;fFBy0>A-n22&m;#jTbn#L+i)x^HfxjihTRM-RUEc*pi z?HAn%3`#qq!u@8j(QH|#J9tzZ!D6Oe>&LF1eKFX@;T>lWjHOH#99HL}^c72HBgMKm zt_yKh$qlH;{Eg}Rv}8&3JR~utu1uv-r2VmiZYf!@IC_I+A)*1Hhbv5up$;k2@x(Wm zBe&O-vz5;m8hHiU&}Gozhg6`&S&*|% zk-Ig>pI(e*OcWj_^U|igG4X6Opav+YN|lJg34v6{+209MzRaf?hycu*Y(D(t9Iy~8 z)iJpD*$Jqtu8=?*VSrQB7j20PTUs)tH1AkIp;(I7znqBZGVJ>#pHoa9))XSs9-*5X zw&X)X?Yc$$r9|jGOnFfn(P8>593CvLe>nA z8xARpA0Vk}$D(lPm9topH&*9!4qS2bF6_S7*SBl&vvp_m^!XX=MQPhlZ?8)UiN+3` zl*ENcsCC#)SV*NfQ1X{;F}nLPw{ZqD+|e2lp$^~|H_~ce`&bqh*f>@b?pZv0G>HfU z$C1V}KT&8)8q(CytA7kxi!3m=)m0j~8dz1b(Q`SV6NKTF+>5_5K#~M=!e8`o4#3-# zl}ppI)ysI5RWNNH-TaN}FIY^d*t|$}^Dw;Vx(&jmsA^8iL0< zH80CI5^o|S2A8k%STeppT^p%Ahqv^Ao=8bJ40_eetgKut7_Z=>NHoVuG_m17%JK|g zYN~Ac4q8|R?n{oE0z_5}2gO0tw>g!(^K{6Mm*P5o-1Lga(eux5^4KVff^rfGVfR~3 zL;RBEl$;4e^@!=2DOH?(Z1^)ua>moi!F)t<T2wBdgo)JWw@aWpsZ-hpxVV3K zo0?OYK;=>%&9p2gL9-xUBcrK+wX4J7Ho-vVo+U{9PKdN~YBUq%Lh5|QxL&~Bvn7fc zZE=3V)R`clGp)~I#M;pf=K)otI1o+-r>Gzm8efU-h9-znoOfc+oc@r&2c_t|6ag*e z4JfXTgWd_sJfkR8UROL&Cj>YxM8|O{MSfN1AoGEq?!qoqqexBOrn~MesfhhPO-EWI zN9T1+W+Rt7lO4|xHq5wR5JrsMEJchAZao)hk&zZViO5}*KmzPe|IWki9p&Aqm=)7% z*)PT}VzxcRqFCLsttv0zw>=!CMgSv%%V^n^V7uB3O*DST4G_WCr%hDxpDFI~M9lz6 z5L=G8Kz(i{uPeoQQ>u!qb!IGfhTgQs?ve;i+5X}}9rsgAk#0c~T@f%sU@vvN(J}fA znQsue-T#I1U}~N3FEMzAZzLE;u5qQTK-;$Rh*4*&Y` z+D;U3tuV4PH+iyDGr8Xdd}zn>3h+@2esy{Sh1VD89RvuIEhrHa z@>y3BxsMj|_WY-?+yH4t2D)!6N6g{LfuQC;DoED0ae+f$ag;))A;xA=&aklKlq(V) z#agkkk(e{N)ju_6^E9F3X`?s~m%723G6_S1a&99u_u$?0U9H>E>EB1r#wu{6#3{5D z=0(u!XHbvLO{@gtT@Y}NypaPt!$Um`c8iyz@jH3 z&^OVZ$cm69>Pxz!tR5yFjW`5fMwRtmbwzXCz8Ybr7C`z%5)-;>o?sUrOai*L9LNRV zBbhpsx8 zmztRR-XyjHoJ5-bj9vWzsri5?g!^-%vh{OZMq(>6DuBy1p*E~`;tR({^~2``3?AsH zlltlVUEJP!=lA=HPI54_{<2@+og(1#EcwDyU=30(_T>{O?vC1%KaU~`?lzX=^AJI- zot59-6^n<8wU99>4D~KL>DTB6Tmr{0LunR+r@f2dx9Y<8*{QRxBK^^D0kUk`6j&a5 z2ypDM-v(FCD-+tJ)`?WwPmRhtH(%*>hUL zh(7loEinEO!{#37IDjBEe#oE?UFv45>J($R-GhR7Nz$B zt=QD(5`(wFI?%WZKs>CU0;*|%0x!hSy(z))X$*B~1N*JJt)>=cBa+^pa@!l^9@74u zF7@TMkg0rf{I0I%mBh8}Onht0t5dAxqQy|A+x;l8P^LY|EGc}I?iVn8P!JG*ypd?& z%P)UenZ!l8wIkRUIlX4~2OWHwbH!Nc`49tSikwyhg@>_FsC@Lx40&8*-oW zZt;}^Gah#z_muKaaoh8}YE5S%g90RArUp>zt;3`RGk37X4 zk52K}W+Ors``N^0tLWCvf9X$rPSI4@86U`8Y^8#eki7u8fWr2W04?8z@Hidmj)Xe@ zylYI7(3~HW?LSxGOyW>Mk8jo=zjMOy2=1@%51UcnRjf&?f=*V2_=1spJvA$ zpm4+fp>XFg3a6}D!WH``$0ug`gukm*1;PaWm@?H|d@;2Gn7leniD5XQNoRJ2cUoZk zUxv{Ykz?R8kxr*H-emDsx>VP>RzMwNjXUIj3_B2jTRJ;;&~*868v7sW^wBRQT_BWJ W)b(LCEP;P1rD8(Tf>r#we*X_snZ@)ZEgJpZvVFlbqDjgr8iUU5-i4LDba3@~fAVsj`>6im{iqF}Df1 zpa3bK2Mr8MLeNJZ_POi9@p znHY?jIG9K|m>HQ_**G~lzmT#pF*5;}SOCl{49uK7tSmfCOr-z*kb_!tGBM*(5|j9M zTc9U?atjw12Oa>x-QAthot4qv$sEAU&CUHGgN20wRD!|T)6T`vgTc<3;y)6^Or4FL zEFD}d?d?cEBpMpoySng`gEakD7i=B=E7s2W-(~_C4B%nt0AOZh`smVs3d+g-{};8j z{cmY!7bVmGN8kUeU}qIi2UCEOsk6PSlQC%E%qTvZa^MklGBtFucT%ypxA~716)fys z?42#_9Y{r$*-7OLjVu=XKX4Z#!n8Cz-Vb{!ow=Y$;~7zF2T;p z%*D(s&dkNh#LO)wAtufx%+3um|9|R=*&Dmsn%cSir>@C=>q;>F&$=J3VCw*CSFBB#DrBmmQLE?(o|-D@po7sMVRGe2&a=xexDr7Gs2Xb z35VG~+!O*{0g*351g*j&CQ^HIBgnW=x^Xc*6&=7z)69j0(9nX-PO$MUP`wO}JkO3Q z-Zh)X%LVS;R9#iwv0U6wZ+-S#UH6sUHQZ0@?-vL5ZoMM9!_60L8tWRhXw`U`H^Y2svdJvkYJdWD+$K9Yq_Vs>fg(o4UMk@Z@xme29`tx0|AP;7TN+#}qUx4bOz zr$$|+iJT<2De)QG8;c_rAlITU;}MTl(vKRK3F51ndF90pAC-h`xsc;I#j&Bp?g^=z zKfkn_1P-3dv6&x1f4HS-FHn~ZID+ z?>!#_+OuGU_=RF0H zRL1HGNnK66Q_Y5OgwRY;h{a69~My~;CI~lm`;FjxKU;i=lq{9ZwWbL7`1*<9`(X* zq;YYK8+vKlHd>y?kf78rgOAU_2PKmPq3Q0@gr8e7KLUIf{bc~faGkH!n}Zf=u5i~_ z(#YdT&|D%y4ovBy;4$$GvHTLAxjtqbvHUqW_DhYncE91{)*Yz@VJOJ2FnYrrQmCF- zxL8XVwJ@ulL4=4rOgwDe40=HHJY z;$8a`2KGo~UZ2_>j(LB~YO$T!=3uDNn|k|l-Eez30EtfGzpD{@D$Nd@II&gW=e+)yM>gSyjLTMU4RWGQh#O4 zgj-QPac(6J#=7mvg0Dx~{z`*ZQHzWle`zCA!9XcQRI zOcwIeS$mKT=Gt3)CIkh`BymQ52u%8R`}5>6^6+pI?pyAdn?sm@q-zCb-rzONh9I=( z+BpYDGzx`Ow6)d6*jLL`OvAZ57ElU_`^x&u`XWm7QUlQA+*Gf>%1(M7OfIwmGv;y0 z7&gINg|lDamA-g+985}8y`_OB-Q1RlPsx36gl$crPl+t^7>cZP;A@o4lst9EMfaK> zOi$GHO}Yb1LmsT;<=JZg`Ic@ge(L$EwTTs6sKIiTJGtOm$L=CUq>uOwsq`#$b2AV#LBy>kgM<_9l>$rl~!3PKl;c|J!Pa4zIEPpTvJmL%!G~?3?ZT%mx~fGcCggA zm!kdWXbKInED>bY!kP}vC8RXR$Uy) zk0)c0`N{i;6A}4b%4T8tIy_m~XXfxbnfdR=II9y&Qh1VES08)jkwJ5!FnOV9qU@o4 z4(!# zls-DX*5h)qVE0g@5Tfp6>J}bHlmNS&-?;5KZCD&vM7U$nwThZ2C<~LAkEfuJ_E;ip z$#qprG`&DAMCSQP|9MIvB79#f^2vB#5_iMV8?W?yv;Nm&9h=JNo3BK_+GS7fG`z8$ z&X-TLS!gQ-aEQ*$XED68i#Z`jBf0I)`@qY3g{18Q%m^jMp*neW|CgBh#Sw$x>K6qo z{v7k0klB!s2Fta!x_U-QNG_alOF8Gx{34U&G?niVyuU+}Nj_#B`^rz-<-cCF_s~9z zL$=EnBgV1W=n%c!4vbmK12I=u0DXQSuB{JN!i!b$k~8b!4^RvBob)BNwBx(m1D)GH z7i<2i-(@fbjQdU!|Aa^^Jz79mUUH6STc<7ZTv=H;olgvF(MF&Q zAu_4xM5y@B{=HG%WbEA;FarclDS1%Ev)M4xr7NFbrnPhgWX+9dh_~kWW*mK{*YaT@ zG`_E&z#l*q15Oa~{nnu8N7YL&xpVnzTw=UCV6cmWHLafBS(C%4esyi!-xh!3pkui@ znVw)2+w%s;a6Pd$L$x)_u%RHuMdRw5_#btX7Xg z5b#+LjwTGfw3${+4$(TmqYogj*g zRQl4`?hy&Y-;s*4!53${Ga9AUWXs|#eBp`41^FMRrdHb03M+MaQ`7NfXy|omr^>n{`mxbjEd8%rpfv%sDm?ZD zf&t>8e#<9ODl!F|4BuK>nnz2?WiPOj8JcjGItMQ-Of4R=^+PhcwqE!0?sKcDyZ^3h z30i#uk7xcv&>axB2=k#^j)i-vbP}yNus`kgBlJD1q;LtJVfurwYYF+eWDUH zeQYH;Q)6b4xPrf^35(IBIrwdTqA*@JsBwJ6 zDi*Zvb5*07%&W?xF5d^T1YyxxihWIJQvs4flM4Jq&Ev#fEO-9uocXaLWnt@34pR$% zot?uxzV+ysjQXJT&c=E9=zCnP&N7s_t05D)vJU~& zkJ-!Ok1|`(?c#Xk!)6_rM396#qg*~H`p@uA9g0ck;)@GQke9pz!?E2KfUg*)q?H06 z2bMLaXyB_m!GjVLXpOrJ1TVND(^uz!`B)SkR#zndHkW}UhPQ@{5XJ?x^=U<8;po?+m{!8Q73V?dj_P-@5`yBtfd%@R(^L|a?0Pz>H0^t#L z`7{Tu&)&Bw48Q^0DUqMFiDH_)sevD7LHi1O&POj7rmgrFCO50!yDo+Nv&mB>Q&LHY zigTcj)HnSSqK-lk56}g2JmMz|47uXoR>&u17j)UZCUEgGZr>PH&@PM9ZG$wMM}JWf z5StHY;p=t7bK((CYTZ5eDX5CmUqgB1V3H?mi30?5BYfye-X~hHQbE_6V(^;}d6)w* zrTs_2=es@ZHYOPMa8rTGb#b_T@G4g$ImI>yb8{|`_@{f;ay;5XW?OiFMsEN~W3MDYuvU94Fd6F7Jb8uLcfqIrGUlyH{ShM-k>QTvt%iLrE1 zINg?pqa9aM(+2Y?a-KRHJWqb&&)_xOdg|``{H^XJiu4MZSCIWXICM2M`ZXQ3-|pY{ z%bRLzswtj}(8D zPV(x&jLO^jCvs?3)92XuD{~mAuE$JB@B;!=WkZLDkYjn&bi1Fy1j3{N_0@DUW)l&^ z279YjE~C}AE1lVOio7Stn;vZ9Lj8V4l%~m+l>y+~y1(+ncz|Hb98ya1Q-}rRj$5+Z z#YJ`oN2lh_v0}ro>KA)oj%AoC;qi|kA?zba=#;jrJ1@Hy_OV5S1vel$SHb7NcJtg6yq0lk6SWnG5xH7N zYSHfzVV010Zl>yVI!xkyz;80IvKIMRBOFf0{rQv{P^aJ*i*S2Sb2-f*qi{Q*ude0` z;r?}Nc`fCQQ!vvO(kpYl`UyU`K3E2b^y^o{VZ0>mwRjF=-yR}+abBzng%VCf+ z;I5bRBW5yw-26ngT#QyO{!F+54see!R96?p2Ej_c%C{jwX5)ywG3U>*0tJEWdjYkC z5fMoot`$>|P|BmnU3Uw$+iX0*Zr99aSaHokD*Vb|R zZm6W3MNBTU_uTq05?^;|Wc4UXPb(7D_wz-2mhd)dRXsha`#3$jAuulSf~od?=&1WI zOyG~Be=0*bDk9aWx$Y4SlQ+5@_!KyGRros<0A0vke=p)Ad>(<(v(~(|Aw12;((1yq z9p%4l#61_bB#8SYn{Jdrl9m*_34>AOWV&U1Fmc^i^@O`A@MZrHfqZCENn6Q?`uE$b zs9mlVS-(PgwTsKQZ=99FVa8c1zVa;hF9yAw7kQ*|3XJ;j5?ni+!&U{M2dEjw5Q z1$XX}Ga?XwD=GKz#Qu_(LLM-Xdt8i=%r}0>39tmU63@qEeuR+le2d_1ZcVr`b$eWX zb+OsiOOt}*dnrEn8r1zy*wQ~7YY1KiPD#%{ni`14_*fjO(6Tu~qpvh`s`+m)S4 zEN&3&hu2_#6)!-@1-Fs{H?D(zMWCZXo`+A*K3W2}vWK(Y)#C-uPTg+6^oPT{a~?+p z2UE<~mU>0gHB;B=rq!YY|`% zLgE+Ny)~ANTEBY5K~*R>FK2%{*)e$FdsFbSepF)867kKvf$nQ{m$c(8Et(AHJxA2O zJsg7n6YxtuyO@@6ov*>4WDvKy;w6u z7`BO-6sAWe0iQ^bTsarTrxV)`1MNPGFDQJF7Jt#&ggv@}Jo57f=%u+^fuzvGaV0 z@Xxe%Qg;Dj0zf`c$B(P4G3sEW^h!%#v}32BJC3YW;q(YHDs~Y*oKI1pp-kZB>mTPQ zczq*@$}zn-$)Ne?#tx(Pzs3EeB53vH3htuz?X$AsMgpL2I{$yeK?LroVim7U@qj3x zRtHyWkyKRb4d)KQiCG3v5#wRXf;rBDLpcd$=X{fatKXsd9Jc>QTTU25g?*-i4;HW- zuR1_3b1CF$ncnw-cg5&nI1(B>grKJs?AP656kh04(9!dyBuPkiH?;qNM>bR9tysCp z?e4@{vvbFTChZVn_QD7i-W;@||0cO6Yw|?gG|-aza|rG|K)XzFfbnG;I|?$WaZZYv z=e4Be3d3*trg!(jfFCee%kvkq0m~i`yEYd(s#l!#5rih3+;x^G<8r!Bw|TmBf36i{ zbE{SITaS2UiVl8Yvfv)qG?nuEm0tu!K}l8x?dBjCJY37YA-*IU4Zi+R4@m`=^I5J5 zUMjfRX;`mc;@@Cqha?S-9MM#=#OeZ2&SWUA}Xz6rS_C<`36RBm}5SwQ_V+)x5Yk@jZ8d&ACaXq6;7X>?WMuOQ8^B zSJsk;Wa`sp9px^vlyBfoT%$MKfeFmxO`6)r%h$ZtRXgFfSZ&6h_@Z9W(*1fosH{^^rA2*-<3*x6V2LqWo0 z@i3aNsckuNn+B8QXw42?_4*v$@_ZJ)bQPH1@2&4Wc5(C_wi)h{-}z!=%STo9nd%Zt zYD-x@W@)5p&%<)46za>T2(S~R29@5;)6YSNDT-rl5aqmwR`r`1`}=(~0euv7m| zO;3L}!ty#)gC1D@SF5Y&O;plEl0xn)WvGLrLnkUhL9@JNIik%OA-^>c6fVhj z5PwN}{tsR--F?5#*>_2IQd=?XWipW?j-*#h&Y%5IQN}zYj;8PJk#T|#M=w&9B370~ zYugeepr<7a{Dk!c@OiwMT0NY?6GfDi_yRsKh>^SV_sFKcc1T(Oc`I~XhpCDC1OE&r z7t99B{z=Jz$Ll6+Uh3?o5HVd(bDPOA#uUcP2s@Mi0X2xslG&m7xLm7O!*g3L-(6ir z$L{ZtcGYTpF2=tscN1{uCGRje_$+549*#VwBJc@(C58U;C+N*DGB>6UW_<1=Woa0> z^8W9)cWcz}bsaxV#yLBmrwH;t#SPWEYH%Bhv{JWLGQ0%~G5kFmFYA9=T6RB4qru#` zTZ4mhr&&5ZoFfu4o3pcs-xAYZy^M_|nRwnfP(;nWI!KY8t`kuPPQlw-B5=`1N^4K+ zqLXFOdi>>4HulbENk0B&>?5ZLCPA?Nt4CWFNaFXxoSc+2S)8gT?ela4XHAlI9AO*?vwZC_vRTD$f9`=_mEoX5bZYFpsNNd==V(PMY$@Tz3tKRd56(mtqFw7@Ukh2ZdNRuG!*&1r?#Qj~Q#fc>3F zf&QA;PfV*^RF3Fch0X!|rsegMwr9p1ZSzavU#DkJuA_Xbhu62)1NR7v9YOuv z6t}swnVATF40H)MXC6sVELvDUZ5aitbL6%k5w_x7RdeQGXltuyrn$KGOI*A;VbrQY ziE4y~vfgd*u?4Keu1#R{H}HlBDLL~wuk}$?9%rT)}2C5)XjK+Y@t%)Q1b9y1^J?0Sm|Y% z8ocRLD$(@Pol7iZ%p;1TL?S>Tjbv-k-}-wjn$e{S3_%(yeGt+TQhfIwI=o;|v}K&B zi*_V5G7*p1H^JA2O(-~Rd5t6T!E^dYaJQlT%;h(E6axmJs6YZfPTzJ62IqAwSL48O z`)fwEwl>hd=M{Kdv^_PZFW1feYm4-HGJzw~>c-;xxGi19K!54ZNPnA!xI}2aN-xQ- zB)wCT^wn~Cm6}x)=SzMTwlsz&T`&CDPPUL1hYck)?#gGnqvVsdc3vzT9A!N{V*dLT z=bK4U6h$YeYYOx{#xe|aiU%8g?|qU{FRQ=qG9-H6P65~rg`_402*Lfux-Ri(*z=|j z4ix78#Q0$XZ=WJXJqY{Xk=SxNbl{(-zr?n`avVJ~6JWGZu(rFUKP+{f!}G9ExSZZYbEZ@KIz z7&^3hL(As+Ekp4LS9F6H^+T6K(b$`Ldxp%QAku+X+#ePEog-EN|EF>=Y0C3DfndkF zYkImw0m6myZzx>Ho@h6N+N3Nmojf_#;_6;=_QH1TpVuVn*`kE+#?0vyC7MQ>XoBj@ z!tE+P%}XrF-FvXE#RK*;Lx6TaD_MGQz-lQkbP&G51Kymt0?Y}ha<`Z1;0zNlY3tTzRv`J73)JTZEP zmju)VFy+nPbE}o%Z@xTgC7EVF#j;g%)tej1${(PYpvbP5E0-#1Y2nY#%_-U0G0x6& zl1$rHC-Re7GCeM_R3mdroJY_ut_Ey_E=C&i010I!!D{X`zG@$4;n84ZkJ{W%FOC!M6 zi3EKgZ`0H1r=UYvg0gL2D@2f_uW{B23AcoYzp=}@+5H?rtH%fc+uRWCf`(%e9K*fkM}<)g&7&4`NU}j|FOU z^S$2Ddm#zCj~XcHQj=jkAPdjbZP$4Y%F8ybe_%m8;9AL@tZI1FMw2+Gwtn{!ff8ly zFDuzBUDwC=9xC&kaU*EU_It$n5Y+Nn79L)kCl+U#BH1n;{7CFGY)5%xc@#H^$xNUn zi+5!w1Ob#uM#r+x5nR7mk+`{lOIW!Xq<|@x2VM=8*!ujFTh9AcZ@;XCTMhv zfqfW&eCgGe1IjpU+rBf3Sb0V~&^j{v5e!Gl3?p0L>fpTj7FW zqz-x6K>Gx81D8vU6r$I+EGPyuY#W9y>1Gc_+nY)d%(500*;hi+D`E0wGUa=y+>@-E zu<2+mIc!tDN1jm%U$je$_ViXQXps6)OcXH_ z@Q4PDnrT(Ucr{QoeKYF%#mp#tsB@^e3oy$@B)jbWKOAI_CmX?z{97K5P3wz0vvLF? zf#ZO%TjK+pFdX7j?)RjXy^Me|^uidrT+4@jVKNp~ZE(jAno&d` zC}KrU`Tc@nu=`*=(#{0-+f49kCytb~zSkkuQoDDPrKg#h8OGmhzxeD;!IX8J*|e5M zPlBUL1kPG9_pc4UxkOYj9CbQkPw(vX8`MB#?dj07H7&?HB$tbLpW=@N}K} z{tHc}?yg#)-wBWKy&fM^m2N6Pe=Ya{f-I}CT}VhkkSICixlx1I1tmm&Q&l)Rg7Auh zi(Tt3k-eZn>ROuWPHrk(Y5nfEO1_>I;Q?`PRf~O!^=)@Nw5wS+xxU>Ml>A5dzX2hkM%6~J)4%Ia^ zEpMig!X74DDgL6OB3i|?q>XFFT@z`x$Sc`+xnWvUJ5UxarsCHVmj}osu>U=wkH9!! zfSWBuGp&|LGR0TSnF(hR42=!4q+MRapy5~qGpqW(E!w(z!FzU5yZm}mPrVfsfgqwh zj$YQ`^ZwlFTME-S=CujUbBxn@fmY6i-H-66u89k_@z@kkqkJNX&i4uuhMEdm)@9RX z|D5V-jtuEe>@gD!5_nrFB~vQU>{(+_>Wt`JGcGE_iOo^@HatB1=P2_zh~%ZqVp%jI z7)%j1xeu?bmUei=^S-HaXvMv)I{Fx>^6bMk>O+{ohNb8>9j+9=$33%9D888LnxU#f zrb;DsRTbZfuzOpMFdfMAEf+2+e>Y9S&ZkuVwh1;cE-pz3JV%F%&86t}(AdMfKF4pZ z_TujS^;|-mScNEt{o-om6|!VgN9{_Pe!3QhD26(MkrY0gXk%6EgaqbxQS{UX^}>zn zjqj|<7I~iAs{5zhfW;GvS1{oKq_GpJ3zdq~Uo;8{Tp!P#=tJcNWew9AVM$B7J!&h$ zNj-JHQ~q3iQ4lPTZ{|&loY!CTy?w_ilg+q#Sq!84JVU z6#Y>#v6Hj@^*rm9N z4*T&Ho08N=2Yb}-%Lwr)t|epU7@LNsQ)h{Ba}o*Hq(?3+id|A$26-^6C^S`%i!Lbf zAfdR&IA@qC^n7#~{L~ycD>cInIx3kj=b_a|b119cB$MF9%q*D`!VSIMsoqq9 zfeSca5BeM52M&fJ5lGaVPn77ky5Yg28h#<|@ZACzr$q=y;pd4Przb3hg<%-rMvxxU z`;*A4UJJq$RXyG0hEXVf|4J4fSq2|w+uc{s0wtllWJ_KQ$H7QA8V2Yntu%Ejt_b=n zbLuvhAFp2*GcKQ<(#wQQ-8nQ~Gzy2mO?t&2?8B>p_&L~2Q{s|joiLBdEjd6@F171x z0w3V(LWs=7`)ep-D{J?9sq;WQyw_Huj+aY+95vLZD^UOvDv|6`MFqW;g#}Z}=dI(V zUzLXV+wRWHKBNL!2>Wbw-9DKeEAN}il|y{L+m(E?`YWnMzo}Ke&b3w^4$1HvHc-F{ zC5qt?(G6`I(oHoVYoE;In)lRBmfsae#*u&9k6mtqPheHa6W4NrPTesno7g22pMFwq zn21f>Btv=BlXd}al9!QFr?aTm6~}*6P#>Y}*`zY+mZ{h3%{1BT_n`e%<~z{9mggmj zi2&)sQxQutJO(_QOC7?I)d3Ns-qLDfm@GUA4v#*8P8}f3o~^u+_+H)dBJZ7(5DWg3 zgPIlch~ZH|nQa}Tb|lsbtEW?!G0b=7hPm>kZMd|9fv0T$PgoB24B&P8FyZ4Gzfj;(2XA{&9ijcV7Ejt9SdGj>&AoML-Ac4W&k@SGYkgh>N%SkYzcJ z@~YE)q;lp4E9)|aHozfNBY5N05uQ7phGx*?{{B32-t=;KR7qe}DbSdP+hB98*s@nl z1OszvV zznQ`$4$V=KhvVW7U76&nBiMSFn@Ha#$w7zi{2|--06sWZKuF0o7w`8r-HD8sTU|F- z4&r9eOp-XdO%eoGUgz!{Ke;94&X=X@>6OnR-l}VRRqY+WzikpJ6Kl73rT=wpluDoV zL_`-moEj1rBwUP^xRGMsg1EdKus8kt=Sg^l`01c#QJJvrbaEZJ!G2m|v0uFkOZRs6 zu%ebCt<4sZ`j;Pw21ZRl8jAA6O~HV)GOI80wR<#twvrFHP`j1~23cJ8S8n$Pmc+&e z0&PIa-KP)#3||cXSI8>jQel1Q+wLw{T33R zgpK-F-0ftJ*3Ru(KhyDJ-?&v9i=E4YwH(_-+ET{&gY2g%I{z2 z=Y(M4;l0_TTE2Rhc3#UTHgZxD3Bj24w7EpY=8`h-3jj2XAfb0IrZEU`X~xDm&W9n>u&uvhf#Z;{f|IhFlEbJ6o_r>zpS>{hJn$*8^*hpRTu+QnYzx@>#f zQ|~-Avxj!X1a8-;4|FpBjH z$uP^4O+Vu03!4OCm4N3}Er5^*>Zd>ndps$2Nk?HKiGNf_P?;kWhVSn}Az$H7sJ4S} z_;BPu1$Lt>1$iq+;w~{TTJMMsrB9hf2$wfy53PbJ$nXLl&U2QwUV7MRVt26if$jGT zbACJ0ER|Ez$zSTSg-T=w2}cPN9@1V4Bz@;?R;~O5DIh5VpP#*Af4{mZFJ}cx8`nZ4 z8jg!hA>k}@rjFs+dOxRpVU{&1elQTpecy_dOTYsg#7(R>kt$5r(a+`d-XHe_xs;9; zJl2(`leLau%4FSVcQ1OUHkQuT!CD~>~Eq~jdeWoSSX@E)s!@dmpS^{^!u4W}mj-T*=JGHw7Q+sb)I(k*Obit%o>q>|chdF2kN~?sj zKt`N;p0ZPD9(Luv^j;v++@GCt`ovE)KRq~7Xph8y6TFRm1sBpZDryw!H81+3)iTrY zrSd0>NkOECf-l&go+V!?6DSPZezY4pT&1o;g-BFXWEi>%yc0WwbWC%wFf7E>!@j=3##S)LVr7Gq z`Q;+*vBhHrFE_7zGY7@ilXHgOYaqjS3BO1)>c1+-YB$tuuwE8AeC=lNi;uAT9fg*2E5RJBP2QfJw2I4mckya zNW$S=XmEE_0?1&50`H*sbV2V!(lCe~&V?J(ELx$}KuwY9d!&H@zagGmKcy`9qtGKUt}L^R<%8|V{4Is(KPps zt7;*PRR2w;)NSVz)vY&JhH@i@!6>O6I{K1wa*4%VVEP1GHw2gchqM{PJ{2PGTao>Z zn|Et&Gar(uo*Daez08qNg}QTEwywGh>`~Y|{mWt~Z*ZGc;Z7-99^F(Qh-kVp$4j<( zCf3RiiW0sb9V5)+;MD2@cLwn-_c$^S0+Vy`zQTll8z+$UJSNOI6;N3~em&9Locq;Py#y*s!POATfc zak#ulUi0K3L=eNUBeKpt*==|hVN<^=;4zOAd0mp+Ci2uiN!R!LWq(P zJM`rlqoVFCgRu1rzt!s7aX%Uad;&l6X~SSAmn}M=Y$=;f%2D!`3kRS+I+lf@96W`> zT*m|Hc%V@2n1w66`ssp??ybqHwaUj0G|L6nyx#4AWieL83fMmB@H%R*9m$eS2^nT~ zml_cO)GS~t;)YBBX5DO9t9jcxupRlTSFx)9(e~j=WOuw6Q)Zcgk^v7 zPgZJbD@wVb!EG`>LWdnaxluj#zgld_{H@8q;?koZZUu5LMWL(2puta4qNx=`rl1oi z>RXt^19tiQ-yQ-%LScWllis0>SG9vm=8*I0x^8fEb_tcl4eb!TMo)DF^t*caZN7Zn z7y)rP5s(^Cs;Tt%a3r2#G4(rWaX^I9hf`~0C*+9S_}VcGnW}HS!sE=aFv;!eN%Q4I zdyY{9`$5U;htN;qdgS_q63^<_xbvH(FDtBRFB!1p!_0U4-yW1)J_&V;ie!(Z27;K7 zd~=b1BY{MFc&7xPDj&jU`GST3d~9E@@13q(2hAGn^V(4?ZCiqm|qoJ+zXwom-hbf1HG%$$AuapznkbzEnJ2C;fLGx9YYhu zX8i*+?PL+;up^CrAQSeG%2Tatj7M>S-=9Wj-sYC!fV`wuxUcL)2fLb`rc#1l+}XU-g1>Mf)7O=vp*WkMN$dsl3u~r>2+a_#L%4z>hy+iY%uY?7MgpwA1ZdTBoSDMg#oyb9{?1)VEQJgr zB93pz%~zSN%U2)sZhL>^j7InKHHxaJ8#=yhv#4KA6_a`Vfx~=o9rX4w zVY$D@x!cVCCj3&EIUUQ!Csv*@gQwtg^wDxe=$$-uYL5iP(^CeVbBvm@Pf#i{VQmS& zY-g&G&AE_tWsnl=Ak$&zB z&)u{%ViD3QEUp4k1R+9BC^t7aVskt4oB^QSgG$Fx$SF9AQ@yX2HcNvaTp)m38HRF8U>Geo835`bS$}4-u2Z8xumgk#E6|+*ipde9 zg!ipQAmt1LRh@BRQBiQm%au$;O^w~ukd?Ky<#vCxirO5TG4^8xl%Dfnq{h@Ww75L0110=IY3fcER%{4wSaE3P#1s=0PUOt zkCa5bZdF5GpsQ<4<$q>!I3wT!;p5Yxs59iSC5~rF0<)*Kx9L0ZK^+e&_j|y$_1T`` z`zJ&F=^4b14ne@d|9*Z_KT(3fXpvs*a@M^6*B>0RtgI|N$M4OP9sscaiS&Wz+m}tx z8m~agoTnar;}+tAqV{@_P8t_^b;M_-?tIu3^w}H!(GJmnsj8;-SO4t_r=qsD6aml{ zch>ptdo)w794Wuh>}>wj2k;eseY#~$0&&yYrc4MxEZC{|1qP%ZYqyjmfH1vK$NZF{ zAN}1qe9WuyRV2GHKHkUi~%r)7vY zprP9~F3rH9wq(e6)s7RC$Q*z0=|SR*TeZ8-?Cu2Nq0xmAu@wR&<0*k3L% zOhHi2!PN+W)7S5A(Om9MSXY4tI&;j9z_VwYwQoYD{3Qu=r7V&dRYM@q_l>mgJOBU*51!uRdUnYts7Ov1ivb&dofrf`TiKbnk?pK4zYM5ufHxY^FEI7 z{yXH{ZbTsbXkWSX5&sY3fJLD9;C__n{fHzr;XZHgFhGkb`Y>ZnkbOe}8XCIaWiXEH z&2({bF|?-ugif(p&wc5{hXN(*P0EC2U-c+@kTH(Ff14940kR) z5UDKt4t&j68o0A|9OvVkx}!0fIz+O1^B^I<6Q{r}Y3PX{M3QbJ;k>+6UUH;?p##&? zUbMa|K8>dHV?f$LiIF?AUA zJJ&)(L#dAq-j4h$g1$pCg``K;VTTa&105TfQn8ZCwbj+w?9$-cxY{&OecLqDY zvzzSV5PxK6KRevZdzb}mZEaCojagO1;Ij{ZOG%3%=3i=fyV&fJQz=qPA!Yttb`Y28? zJOn-v2Jsc?2*He@?KgZ73{t@ebOktWM|ap-y~i2aI|p041cc0?W}FPlJ2|`Jx6v)4_f?=D+20{(;^?t zWquc;q~R_O$yb-9!#5e2zwJ0EHvnhq;&B4^_k`;q!W-gwDA1Nc+%az+lQIjvdXXT* z$Xr4Kya>p(>5nC`_wd4646*4!d*&iUrjWn*D+9O%yzdLXTUG^aQ!2!wS!eSs9=}w4 zL(^|i(Kj6^7Tm+bjhxlo~2+aOAb<$Tp8%S!{^dr}wY?THQ%#CO#3sKd>Jp$|`j<-zI6!__Quw#(l)ioNdpbs(`)bQbEi3H6j5W5eKBjW!Q zcGgi*z3twoy9H?&=@F4enn9FKDQT2O2??dUyK|6k0YN$jrMo*sa7f9a``zR7JI`9r zS?9dxKUn+bo_ls&_jT>-`~8q7tC+tVFu*pSbjv39mTCDzFZ~G+?P+WYjdiBQ@>wB@ z%DWZ#6+}1toP9n^KS?_H3H#eZwTF{pCA;3?(ji0TV{+7q@W=YL2zek8YQz zsa|(Cm$GY^S;`&Vz{@p$sW+=$!ZBr?)L-O2ky^M(UI|AVVgKZG`yMWEJX2 z=zZipvES~L$1Y{qg(F>DifYu4B*weqt14l}%4cVUFP3)lNpZ10stL#v-{__|oDASP$r87HVxYBg2WgNywK@wkHX- z$q8mePiv9|l{~H@F_}=Bz`t5SY^Ql<^huGbUJy`?STX{(G! z+eDGxwiK^Kjj70{UF1M$wYzo@zVP~9Z?_m_w*9BtL`jlp{JVSSjRb#x@`5+tCEp#3 z{87H}_Z~P9VNct|DR;HYHpZiOe}!knU4!VS0cDYyo1b=LB*n~JJLEC;b)bCmBuh@e zftK0M!p6yrrkb3&owBgx6yX#>7y;2ki@Z{ya#KrB=O7AP$Disf{vN+Paw|#EL~#YbzDr<;GI)DepvW(ALhg*x31g=tqBl77}ZbH>AOT zKj+#3SHZ^+W>*hbh4&}kQA(0Pym-KwoxNhJX&as`$Ar_RXc^izG1k%_)wSGgTx+`V z9kjh>ytQXx^8UBv3ZRE%%eN;V8%*Jt-MEvKY>qsTw#Uxe@nm<*VFcIU2b7y#M~;k1 z9o?eKyS+XooMDvhAHJCG*)=iUybWk2 zuYQ3ShZ}~WNU#~svMv=yLzHUjihi`(UXu49-S%9D`kB4qgT(1(!}f=+w!qn`QR3gbs)R7#xfo?U=wqjuetqE5y?yT?|d(6%S zS|JI+8uPyyr?@UH7@$9@{?$(?xI*VRvhqbdN3@z&zDzu?v zOo_}9SWXcoU(+Z3tIn^zUA6SSeroqR_zeriZn%6|c1yF9T`l(D<`udGC&X`bNt|!D z+A}_Peqkik{fQi9(}tg)HuPK06y4lXF<)kaC@Xb!FtDQBJMyDCt=rNwwps(dKqzlB zR26ORVNh5p!(%jyS*U(MK8k9vIuT|$TBoExlx4=k;|txH%rd$wh1O%0do_^xI)#pT z$US9=9l0Xa;-7mX%)kb6;p|hxRSc2e)YA2o_s3Uz*kYH0XFq!Amou9nmNh)eLC3!B zs2J8Xt#s0(Kv6;V5S2;JBxkN&A(4%mH(;1_<*({__w6LYzI)N8c2g`+JeP;WI1mG; zTHdCR4iof4-BwnFmpr%jQwhl0f*};IU&DWL)v);W+7U7FMK;A7#D?8nXEN0+H8o$H zH5uV>T=3z|n$nJgcnr2NP$~G`#5H@}a*qZFIpLvs>5}Bw+{}Eiaw)n`7YXukaq2F3 z3it+2Rv6(E^_?4IX-N}wGu_=A))l12?+9F=Di}0*+7+F`S4oPOlVeQSYw*tWcXQ4J?G{WE1qd+3b=2PMaCx z!nMFIy{{IOUEpkkZ2uSvUeF{W9{EMb2w^l>a1#VbWX z{9F@m);>B?3VAXMc%s@|F)4pvSKJ$S?`Q?gmdc8D6GuWKKkTDQBV*P*DDxe^a#l@P zm)tpCf6%1Me*Ws{lS@zaXt3U5xAkmbf25Ek5FsiIo=M^EVDxRdK{)W~XaUg%7NrDQ z&IWw7V@d6^o;%+=dNKtj8U{P>Eq$%W{a^LVAc<&jA*B02vs&XYh1s-rvcTl_cWKa5 zZ%a%uq6Ji6;hM~NJbatpA2N@*9xCe>+;z%CNjAU3*v|FlriwVX&v_uG6VD7zhW2Om zCod&Kvzp^M3puf#$la?ru|zmGCPev11rQqaZ}rtXa24LIJV*F6?6=&DeOTlE>cx6V z(f>*{I)><(Ue77;dJ53qzw|q_&uLK~5IMiwt51S;?z^yjq@jpxo+97WJo-$imEJBF zXTWa0Ndaf?tJKt8nc_&efQG}4Jm|~XV*9y`e?93ds?2VGk)_6vN1l73w7l=j#mV7j zOlDg8WZ*_ShA7b;@LS`^`#eN*vBtnztX~v4Z}ahWs^a&3(q}DSD_?bUZKRd(Eq)hQ z0WL59s2C&_3KD(kCQE8wLZ0oL;iTL(x05J28^-|krG-9?4X((S!$gV_*2cz5hSr2szrx;BS&b`|$G;1al`}F=*75|9x z4sTAon>l^>@-=nf@eEY_6~JdIDRVT@TOYrrD7+S;Ox>_|?GHty$$CZnDyw^yU$pJ~dYx7yR|qQ+Q$zdOE0&xUdm+7aM>=X*;Tfj(K#}gU;=EUatd*%LPJ9 zhFu)|_cW}?{xWVFI(MX3$g!fJJ*W4$%N&ph+o*5{ty7I6!o(|?U>PXz;xIM$JsN?D z$jvI+0QYZC2EMj)Y}tRPY*htyJa4kIQBq6#oFOWraa=^4CYcrT+0%aLF0G|G10i(} z#cRI^V7nQ;SASlt>T`;XP@)o!v))i1xW6!S3ilAfq&`H8+T4YBg7gx>64y zihTP`N%JbM=3 z6qj#IOG(2-&xXW!U+f^x;HC#1zB>^-@(`W^lGky#)@0e*T1XDLKPh%m-!&6ZjXX6n ztOT!Zu9WhAQ(BVbRQ4HXhg5Z+*yYH|Vak`kVir$U(WQ{+jzc?I)o=Fd0g{ETAv zOxcS|CiuPOh5pE=cynQ`q(M0LpRY@@Dy1!hr<%*DlKLgp)`U736Ji%XJzaiPG?c*d zI?i+Ck3Evqy(G*#FqSWKwLnFkRqdaN+OI3`-Hf2B;%diFQqYKv`mH5U75UQy_$F|_ z${IVT%DO2YZ7Z<4BE(rtnwh7zhOEaJ40SxK(K9H=nAZ&OsfiNI)2@CSx6y@v^rVz( zB4AW)lZ&;N*v8EZ99TTx$n0?tr^D6HWK!N4|Fk4sS-_%s994&JrmMj?RqC}OSy%Gc zeC)CF-T(9<5*tSPfTBcRFxQlPWm`o&J2WNn0-j5Po7X)}Tl zpRw#y*GG2=O{uV3W)!WH|BX>`6@KU;6>nQF zMPSM(FJ{d-m6(ui0VRRDcc1{u^_J*ry5SX^905 z(Z5Z2bhi9(?+Sx2n0od1{a_zJVp1b=CT~fJGgi_v$q{XOUoT)Q8WwMI*{yUzyD4nT z2zh#eYHhZrG`?Zl%s%XDfj*9G$0MZ-13ue@VPRFQW!&mnyFvzhm_%P#HF0?EQ=#EK?L;ne-`eZk@!W?zb|-fD z=NWpemakO=CSU_1S`h*kfK_1p9I`|01zsRf?o4G^q`GCBpDBSsTo?3=iG-rA_v3&DC-I`* zZ5_SgH&@Q2Y$;+>B!$lB7bH`|o5y{yw$jeNs==dsJm0f#=^1n_9w+@~p?EuUnin0HJ^PNPQSyBG zQ)%Uw-P&@=sYkj>R+w~ODrJR^AR-`uMYJb-TfMy~>u6-T@v3U*IJBV+-phjRU6w%f4^uCebvO-bd)fZ7b9LxTL=EBB@*w>O9FD zOXWJIWpbBIME6=jMegdikQ7!XEGmUlDoH}#A5F51b|Y&IX@7+!?}Oo5_x zT17>jcn2KJxgUbaPQ?O$lrpz@|Kko}??#Ko`odG=1N^I+#KrpYlwO9&FqF~8wTOqn zBGETHL|k_{xbG3uouT71y3*0;DV*z+4b=ceYmXATUAR{&H5#3sevE;d@zw}1zD5Zx zePW!3ic`<{L&0FuY`Wea01-vsG(D{cm|+8Z+W8cGrZW&`4%~q%CbBNdCJKJf0Byl@ z6L-L%s*jX#_M}*&1G(-8q6gbEQ}B`D5g)yqC*yr;;Pys_*0BDnTba?54r}He;s{%$ zcEBUHcj}bT&hMSoaGsHx)N-Dek4iziaL>L~wjj_ZF$h0M55y*Q&@6~IVJ(Nr&aJUp zxMsE-)|aqOUGQEm*v~)_&m>f{vq;WW8IuO4C)X8ye1_7VCD6xkqNmpfR<)&Esbw=9 z<;cAh`>1}mGwpJCdk*Ly@UF|tY8d9fS!o2$dIr$QLD5lDLi2Cd)?s{?b-svBH-cvo z2)aa*%*IalDY1kcYth?#JH^d^V&t0gVzT zfG4Z&vH9ZaUmNE2!o^?QlL?_9Q7+9G+yY@%ONl_=f3Pm2K?FQk;hx1)jAS8970!9; zehsIp@{MBS_G+;&v;IRaP>N7w*)FYzDXJU=3%Db1JKnk8%-3(vNd5l-Kv4l=gXRc6xg@DcaMX+y0=QWSF>yF4SuXxlB5)Q9zgf5c zq@k)kT?ri+fHvUc!II%RZ@}6a>E)cTxGrQd?f}PL0uNwmoB<~--P4K{Qmo|{fx-KK zGND}0=*r8*3||{$rt0a`f3nlisATy@Q=w%8hy)}khUgw10d3m_=1Ub4i?19907Rw~ zIe8&kGMUzkw31{DzS#2e=M!2|h7Bz3q6&^>Q`YT&1nVe;j=Cqk*T)w{iQ=}`dlB8G zzk!(eYr4o7-@i!^%~?-=R6zxs;#~i1T0uQ~r(m;s${%Q1ujW{J%#WnztB?VRU3fga zKwgPmypUv1x(|RnW&Z8|W35*9_3@1O0(?&|b^%A?*ciZUi{B` z!kjKucBO?|pA9KG^3G=kEv$9A0QlJKxW6*DZanv-5~fLvkdj|W!~?~lqS6j5+bCid zm7{sI68Wm`sF_7X+99%|=24Uy4Ycy3$ygME2A<5k!GH7odNUn3L?tz|(qRfT)>y^6Vu-Nev=p;;AZd}N;zcs`~&FC zBRgmBttL~EKj=s_|Aq&^V0}QB zn8XhR{_bA~&0Ji7qbLXqkN@SUV-Y15(Dr}&Bi)#afK5uso0T>>Obr2Lk&$t1z%u07(T*1PlygM=mCjk=Y)(m=Cy_vwzmh9YvqE4u2dNZDL~1 z@2>%P8GDb}oL^jidwmeTep&UiCfGq7z=h`9K}Z;^Jl=G%LIKR^)Yiq^qR#YF0}cWp zkA3~U?}SO&^q-GR-lpZ49Hi2?lSz7+=f#CbvrA(>&y>oQuo$&k-e>wPb=+QMMGTwZ z;uEiHq!VGVJ-XKCL~z~`m+t|3Y($N567i)I--77eqG$8d8=J#3td?0$c4asDFPG0W z@}2Ws%IwzM9E`qM3~G*Fjov_k&MP2dlm!Kx*gr_Tewj|5uW65PYvVRfO}{J>Za{)4 z;l09pNMex)uRCb2{(quDm1y)d14%`nriq%x^_hmkM-&0%Bi@R%hWtnY_ zF~|4wAhcEmvg~KC(BPX%z+&LDZ@={QZE>?Hy-2q&i~MioC%XTg{1mQ*iEu`e(Gc$l zFDq)uOq}#-RP-9h942J(d7=S$pb~&SRKZc__&(TA$@ich5BNM%J4dJq6hgA{kHOHW z)?8sKocR0S&iToAfD84LUW&o80euCf0(n11l{{FPeCt#Jdk>^q6PQ)4Z~xsgcM*PH zpFY?VZP15-yo`XVjEu2EFJ)`+*HBusb^PiN2?{@}i*nEXTwv!-Pf2ZBcHW<>{vGY8 zZ0e&*4d5N(*L=LO?7R{wpNd;_S&5tfccd$3pXpbG82~7pK95Vg@YMTW9{EwYYyV~5 zLX7uuH0h|W^KM!-0mGZx?NgC^FQ>u~sT1y)>ix4t6=T!fI|j;~12y;`olT;my9*JE z7qm+a4Q9V}ys=iFtdcQ4*MWJiQ(WEU$D;M@f%#r~{ zy@cUS>XcIo(q9e#`mVZmkk6Pk*V8F04>joAE|Ex8yJ}r`cgXLzyPd??OB=iH$JR^v zv9DfbORCI70|MiGE|QVHa;@~;gpg2eVXVWWU=F9249y! zLrZJ2{{0TDwJ()6(kt{Hr>WxKIH${bb0Bo;2k@V!!+ma_+eaCCK@{)rfCSu+G|CY0 zsm`&N4-E33NoySW^zD!V;3H(|14KDZTq39aG#=L$M>h7*rPt{t>c}jO&xe^7U%Y7* z^rtX2%1w$smro44EOFUVX{Ilho&gyh&u~xftXF%}+S-u6Jn^g;ZL8jpT#WCnHnkt@~4QYPp3oI@+m;;Qr zwPy=!*6@99ohp#cNLD8zcLi%!p>2ME%=E0KGmh3Pu<6ZV?#9~_G}KkDmTd2t3yH0r z^4nWKfNk{;y%yNS0D7$jNK=4(%#IL`q^sd_HI$ITy8BARnZUs@XaAkp-Gv-;Yj+=m z^ZD|d%wD{IQ*DvE*1xH?vGw9z2LSElTi-9V?vGx_mRE?%mZ%E}deE~j1VGolO3jTv z1d4^I0<7;%?e`L_9}j+h>5mizH@5xUp^%I1(2^7N9N7k9@}EA4<(f6+J9HR>WYUOPCqgPvD`oN`@-VSt3Z5L1e`J-FAos&8^?pB zijimUH5^;}9*MfGhSSxdr0Z(!lv*a2_R3(d_+}#wcG7L#efxZF8_G0KW8SIwQV=*s zz@OOADOVQ2BpuF1vW~mdx?ps@Oo(pqjJ#kUu(9I`_cfs#%n%wMEUIP>tNF+aMDRZA zpzcMtbpTM-w3v3jIA(yGtOoXrDxM(7oW$tk$3*jDz2xM~f`Rg6MYZ(y-5X@^t(Cu= z7dYLF74A8V-|8NRYBDWAD!;paT6*J@hclhZt+j%r@0P)sGzI`zlEU<+#d>cT=G^MI ze=?(DMelJGj6}LGb7L5vYJiLrDM+98Z2bqm`-*kgPzT_+2>kkBdWFMBRb5!ncMPn%;d2p1EC%+P>Y>Ojw?9YAoY@%%Ek9 zqDeSv7!tp$xHHesb}Fr3?yxyp{J@|-EoD@IqpjV%?!TWG(iaIAjtqLZ%hV-r73 zQ6vu1Eh?4BI!W860=B;j9Ur0L8`o2pri5%p{*7q!(BF!GLc*qH7*_z0PN?8kJW)Z% zs7c#G7~vFp8pWBewuMBE)o312YX`9)-@fmc3AKAf-?RdA0UPy99Du1&)k{c;qHoFh zHYp-DM+AstOl5gONk>B)r7UPV3to~40N7nx7T@WzIGaBWsi_WL;_TR{KHd7@=`gjr z<0f!eYHGG75I^%-;c^*Q{0sLl6v*)o>;H>oQ(dB_;qAS8wYF9l8B3?ftZf|5B9YcY zF#)e2?1iuYO=qkLFH6zTl;c>}yH8F`P8^#mm1`E|WFt>%9zW*0hh|=fYkjiN2N(Cu z!9?{=8j%oIAYPZjDgXCUYw7_hTYbIw^JV`~|IJGzOU^II3wbAk_fZ%Q&s;Oyv7a8Hinm%n^sK_}@vt4Ss z@ih!duLa_v;3X<5j(RDi5QBo0&6d@F==V+;4>;L#l&il=l??h=aN)p1WiMF83%Cle z{k}DKJ;K2Cz(h@u6&L*)6~TE za&(NYe!&cxyB8~AKCS^sxc1-TvSHbB%q>`US*g8eRbNy-Hx2lIcii6Y literal 0 HcmV?d00001 diff --git a/media/js/collusion/collusion-addon.js b/media/js/collusion/collusion-addon.js new file mode 100644 index 0000000000..73dd7ffe77 --- /dev/null +++ b/media/js/collusion/collusion-addon.js @@ -0,0 +1,14 @@ +var CollusionAddon = (function() { + var self = { + isInstalled: function() { + return ('onGraph' in window); + }, + onGraph: window.onGraph, + importGraph: window.importGraph, + resetGraph: window.resetGraph, + saveGraph: window.saveGraph, + getSavedGraph: window.getSavedGraph + }; + + return self; +})(); diff --git a/media/js/collusion/d3.geom.js b/media/js/collusion/d3.geom.js new file mode 100644 index 0000000000..9d8bc31a06 --- /dev/null +++ b/media/js/collusion/d3.geom.js @@ -0,0 +1,825 @@ +(function(){d3.geom = {}; +/** + * Computes a contour for a given input grid function using the marching + * squares algorithm. Returns the contour polygon as an array of points. + * + * @param grid a two-input function(x, y) that returns true for values + * inside the contour and false for values outside the contour. + * @param start an optional starting point [x, y] on the grid. + * @returns polygon [[x1, y1], [x2, y2], …] + */ +d3.geom.contour = function(grid, start) { + var s = start || d3_geom_contourStart(grid), // starting point + c = [], // contour polygon + x = s[0], // current x position + y = s[1], // current y position + dx = 0, // next x direction + dy = 0, // next y direction + pdx = NaN, // previous x direction + pdy = NaN, // previous y direction + i = 0; + + do { + // determine marching squares index + i = 0; + if (grid(x-1, y-1)) i += 1; + if (grid(x, y-1)) i += 2; + if (grid(x-1, y )) i += 4; + if (grid(x, y )) i += 8; + + // determine next direction + if (i === 6) { + dx = pdy === -1 ? -1 : 1; + dy = 0; + } else if (i === 9) { + dx = 0; + dy = pdx === 1 ? -1 : 1; + } else { + dx = d3_geom_contourDx[i]; + dy = d3_geom_contourDy[i]; + } + + // update contour polygon + if (dx != pdx && dy != pdy) { + c.push([x, y]); + pdx = dx; + pdy = dy; + } + + x += dx; + y += dy; + } while (s[0] != x || s[1] != y); + + return c; +}; + +// lookup tables for marching directions +var d3_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN], + d3_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN]; + +function d3_geom_contourStart(grid) { + var x = 0, + y = 0; + + // search for a starting point; begin at origin + // and proceed along outward-expanding diagonals + while (true) { + if (grid(x,y)) { + return [x,y]; + } + if (x === 0) { + x = y + 1; + y = 0; + } else { + x = x - 1; + y = y + 1; + } + } +} +/** + * Computes the 2D convex hull of a set of points using Graham's scanning + * algorithm. The algorithm has been implemented as described in Cormen, + * Leiserson, and Rivest's Introduction to Algorithms. The running time of + * this algorithm is O(n log n), where n is the number of input points. + * + * @param vertices [[x1, y1], [x2, y2], …] + * @returns polygon [[x1, y1], [x2, y2], …] + */ +d3.geom.hull = function(vertices) { + if (vertices.length < 3) return []; + + var len = vertices.length, + plen = len - 1, + points = [], + stack = [], + i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + + // find the starting ref point: leftmost point with the minimum y coord + for (i=1; i= (x2*x2 + y2*y2)) { + points[i].index = -1; + } else { + points[u].index = -1; + a = points[i].angle; + u = i; + v = j; + } + } else { + a = points[i].angle; + u = i; + v = j; + } + } + + // initialize the stack + stack.push(h); + for (i=0, j=0; i<2; ++j) { + if (points[j].index !== -1) { + stack.push(points[j].index); + i++; + } + } + sp = stack.length; + + // do graham's scan + for (; j 0; +} +// Note: requires coordinates to be counterclockwise and convex! +d3.geom.polygon = function(coordinates) { + + coordinates.area = function() { + var i = 0, + n = coordinates.length, + a = coordinates[n - 1][0] * coordinates[0][1], + b = coordinates[n - 1][1] * coordinates[0][0]; + while (++i < n) { + a += coordinates[i - 1][0] * coordinates[i][1]; + b += coordinates[i - 1][1] * coordinates[i][0]; + } + return (b - a) * .5; + }; + + coordinates.centroid = function(k) { + var i = -1, + n = coordinates.length - 1, + x = 0, + y = 0, + a, + b, + c; + if (!arguments.length) k = 1 / (6 * coordinates.area()); + while (++i < n) { + a = coordinates[i]; + b = coordinates[i + 1]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [x * k, y * k]; + }; + + // The Sutherland-Hodgman clipping algorithm. + coordinates.clip = function(subject) { + var input, + i = -1, + n = coordinates.length, + j, + m, + a = coordinates[n - 1], + b, + c, + d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = coordinates[i]; + c = input[(m = input.length) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + a = b; + } + return subject; + }; + + return coordinates; +}; + +function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); +} + +// Intersect two infinite lines cd and ab. +function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], + y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], + x13 = x1 - x3, + x21 = x2 - x1, + x43 = x4 - x3, + y13 = y1 - y3, + y21 = y2 - y1, + y43 = y4 - y3, + ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); + return [x1 + ua * x21, y1 + ua * y21]; +} +// Adapted from Nicolas Garcia Belmonte's JIT implementation: +// http://blog.thejit.org/2010/02/12/voronoi-tessellation/ +// http://blog.thejit.org/assets/voronoijs/voronoi.js +// See lib/jit/LICENSE for details. + +/** + * @param vertices [[x1, y1], [x2, y2], …] + * @returns polygons [[[x1, y1], [x2, y2], …], …] + */ +d3.geom.voronoi = function(vertices) { + var polygons = vertices.map(function() { return []; }); + + // Note: we expect the caller to clip the polygons, if needed. + d3_voronoi_tessellate(vertices, function(e) { + var s1, + s2, + x1, + x2, + y1, + y2; + if (e.a === 1 && e.b >= 0) { + s1 = e.ep.r; + s2 = e.ep.l; + } else { + s1 = e.ep.l; + s2 = e.ep.r; + } + if (e.a === 1) { + y1 = s1 ? s1.y : -1e6; + x1 = e.c - e.b * y1; + y2 = s2 ? s2.y : 1e6; + x2 = e.c - e.b * y2; + } else { + x1 = s1 ? s1.x : -1e6; + y1 = e.c - e.a * x1; + x2 = s2 ? s2.x : 1e6; + y2 = e.c - e.a * x2; + } + var v1 = [x1, y1], + v2 = [x2, y2]; + polygons[e.region.l.index].push(v1, v2); + polygons[e.region.r.index].push(v1, v2); + }); + + // Reconnect the polygon segments into counterclockwise loops. + return polygons.map(function(polygon, i) { + var cx = vertices[i][0], + cy = vertices[i][1]; + polygon.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + return polygon.sort(function(a, b) { + return a.angle - b.angle; + }).filter(function(d, i) { + return !i || (d.angle - polygon[i - 1].angle > 1e-10); + }); + }); +}; + +var d3_voronoi_opposite = {"l": "r", "r": "l"}; + +function d3_voronoi_tessellate(vertices, callback) { + + var Sites = { + list: vertices + .map(function(v, i) { + return { + index: i, + x: v[0], + y: v[1] + }; + }) + .sort(function(a, b) { + return a.y < b.y ? -1 + : a.y > b.y ? 1 + : a.x < b.x ? -1 + : a.x > b.x ? 1 + : 0; + }), + bottomSite: null + }; + + var EdgeList = { + list: [], + leftEnd: null, + rightEnd: null, + + init: function() { + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.leftEnd.r = EdgeList.rightEnd; + EdgeList.rightEnd.l = EdgeList.leftEnd; + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); + }, + + createHalfEdge: function(edge, side) { + return { + edge: edge, + side: side, + vertex: null, + "l": null, + "r": null + }; + }, + + insert: function(lb, he) { + he.l = lb; + he.r = lb.r; + lb.r.l = he; + lb.r = he; + }, + + leftBound: function(p) { + var he = EdgeList.leftEnd; + do { + he = he.r; + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); + he = he.l; + return he; + }, + + del: function(he) { + he.l.r = he.r; + he.r.l = he.l; + he.edge = null; + }, + + right: function(he) { + return he.r; + }, + + left: function(he) { + return he.l; + }, + + leftRegion: function(he) { + return he.edge == null + ? Sites.bottomSite + : he.edge.region[he.side]; + }, + + rightRegion: function(he) { + return he.edge == null + ? Sites.bottomSite + : he.edge.region[d3_voronoi_opposite[he.side]]; + } + }; + + var Geom = { + + bisect: function(s1, s2) { + var newEdge = { + region: {"l": s1, "r": s2}, + ep: {"l": null, "r": null} + }; + + var dx = s2.x - s1.x, + dy = s2.y - s1.y, + adx = dx > 0 ? dx : -dx, + ady = dy > 0 ? dy : -dy; + + newEdge.c = s1.x * dx + s1.y * dy + + (dx * dx + dy * dy) * .5; + + if (adx > ady) { + newEdge.a = 1; + newEdge.b = dy / dx; + newEdge.c /= dx; + } else { + newEdge.b = 1; + newEdge.a = dx / dy; + newEdge.c /= dy; + } + + return newEdge; + }, + + intersect: function(el1, el2) { + var e1 = el1.edge, + e2 = el2.edge; + if (!e1 || !e2 || (e1.region.r == e2.region.r)) { + return null; + } + var d = (e1.a * e2.b) - (e1.b * e2.a); + if (Math.abs(d) < 1e-10) { + return null; + } + var xint = (e1.c * e2.b - e2.c * e1.b) / d, + yint = (e2.c * e1.a - e1.c * e2.a) / d, + e1r = e1.region.r, + e2r = e2.region.r, + el, + e; + if ((e1r.y < e2r.y) || + (e1r.y == e2r.y && e1r.x < e2r.x)) { + el = el1; + e = e1; + } else { + el = el2; + e = e2; + } + var rightOfSite = (xint >= e.region.r.x); + if ((rightOfSite && (el.side === "l")) || + (!rightOfSite && (el.side === "r"))) { + return null; + } + return { + x: xint, + y: yint + }; + }, + + rightOf: function(he, p) { + var e = he.edge, + topsite = e.region.r, + rightOfSite = (p.x > topsite.x); + + if (rightOfSite && (he.side === "l")) { + return 1; + } + if (!rightOfSite && (he.side === "r")) { + return 0; + } + if (e.a === 1) { + var dyp = p.y - topsite.y, + dxp = p.x - topsite.x, + fast = 0, + above = 0; + + if ((!rightOfSite && (e.b < 0)) || + (rightOfSite && (e.b >= 0))) { + above = fast = (dyp >= e.b * dxp); + } else { + above = ((p.x + p.y * e.b) > e.c); + if (e.b < 0) { + above = !above; + } + if (!above) { + fast = 1; + } + } + if (!fast) { + var dxs = topsite.x - e.region.l.x; + above = (e.b * (dxp * dxp - dyp * dyp)) < + (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b)); + + if (e.b < 0) { + above = !above; + } + } + } else /* e.b == 1 */ { + var yl = e.c - e.a * p.x, + t1 = p.y - yl, + t2 = p.x - topsite.x, + t3 = yl - topsite.y; + + above = (t1 * t1) > (t2 * t2 + t3 * t3); + } + return he.side === "l" ? above : !above; + }, + + endPoint: function(edge, side, site) { + edge.ep[side] = site; + if (!edge.ep[d3_voronoi_opposite[side]]) return; + callback(edge); + }, + + distance: function(s, t) { + var dx = s.x - t.x, + dy = s.y - t.y; + return Math.sqrt(dx * dx + dy * dy); + } + }; + + var EventQueue = { + list: [], + + insert: function(he, site, offset) { + he.vertex = site; + he.ystar = site.y + offset; + for (var i=0, list=EventQueue.list, l=list.length; i next.ystar || + (he.ystar == next.ystar && + site.x > next.vertex.x)) { + continue; + } else { + break; + } + } + list.splice(i, 0, he); + }, + + del: function(he) { + for (var i=0, ls=EventQueue.list, l=ls.length; i top.y) { + temp = bot; + bot = top; + top = temp; + pm = "r"; + } + e = Geom.bisect(bot, top); + bisector = EdgeList.createHalfEdge(e, pm); + EdgeList.insert(llbnd, bisector); + Geom.endPoint(e, d3_voronoi_opposite[pm], v); + p = Geom.intersect(llbnd, bisector); + if (p) { + EventQueue.del(llbnd); + EventQueue.insert(llbnd, p, Geom.distance(p, bot)); + } + p = Geom.intersect(bisector, rrbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, bot)); + } + } else { + break; + } + }//end while + + for (lbnd = EdgeList.right(EdgeList.leftEnd); + lbnd != EdgeList.rightEnd; + lbnd = EdgeList.right(lbnd)) { + callback(lbnd.edge); + } +} +/** +* @param vertices [[x1, y1], [x2, y2], …] +* @returns triangles [[[x1, y1], [x2, y2], [x3, y3]], …] + */ +d3.geom.delaunay = function(vertices) { + var edges = vertices.map(function() { return []; }), + triangles = []; + + // Use the Voronoi tessellation to determine Delaunay edges. + d3_voronoi_tessellate(vertices, function(e) { + edges[e.region.l.index].push(vertices[e.region.r.index]); + }); + + // Reconnect the edges into counterclockwise triangles. + edges.forEach(function(edge, i) { + var v = vertices[i], + cx = v[0], + cy = v[1]; + edge.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + edge.sort(function(a, b) { + return a.angle - b.angle; + }); + for (var j = 0, m = edge.length - 1; j < m; j++) { + triangles.push([v, edge[j], edge[j + 1]]); + } + }); + + return triangles; +}; +// Constructs a new quadtree for the specified array of points. A quadtree is a +// two-dimensional recursive spatial subdivision. This implementation uses +// square partitions, dividing each square into four equally-sized squares. Each +// point exists in a unique node; if multiple points are in the same position, +// some points may be stored on internal nodes rather than leaf nodes. Quadtrees +// can be used to accelerate various spatial operations, such as the Barnes-Hut +// approximation for computing n-body forces, or collision detection. +d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var p, + i = -1, + n = points.length; + + // Type conversion for deprecated API. + if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); + + // Allow bounds to be specified explicitly. + if (arguments.length < 5) { + if (arguments.length === 3) { + y2 = x2 = y1; + y1 = x1; + } else { + x1 = y1 = Infinity; + x2 = y2 = -Infinity; + + // Compute bounds. + while (++i < n) { + p = points[i]; + if (p.x < x1) x1 = p.x; + if (p.y < y1) y1 = p.y; + if (p.x > x2) x2 = p.x; + if (p.y > y2) y2 = p.y; + } + + // Squarify the bounds. + var dx = x2 - x1, + dy = y2 - y1; + if (dx > dy) y2 = y1 + dx; + else x2 = x1 + dy; + } + } + + // Recursively inserts the specified point p at the node n or one of its + // descendants. The bounds are defined by [x1, x2] and [y1, y2]. + function insert(n, p, x1, y1, x2, y2) { + if (isNaN(p.x) || isNaN(p.y)) return; // ignore invalid points + if (n.leaf) { + var v = n.point; + if (v) { + // If the point at this leaf node is at the same position as the new + // point we are adding, we leave the point associated with the + // internal node while adding the new point to a child node. This + // avoids infinite recursion. + if ((Math.abs(v.x - p.x) + Math.abs(v.y - p.y)) < .01) { + insertChild(n, p, x1, y1, x2, y2); + } else { + n.point = null; + insertChild(n, v, x1, y1, x2, y2); + insertChild(n, p, x1, y1, x2, y2); + } + } else { + n.point = p; + } + } else { + insertChild(n, p, x1, y1, x2, y2); + } + } + + // Recursively inserts the specified point p into a descendant of node n. The + // bounds are defined by [x1, x2] and [y1, y2]. + function insertChild(n, p, x1, y1, x2, y2) { + // Compute the split point, and the quadrant in which to insert p. + var sx = (x1 + x2) * .5, + sy = (y1 + y2) * .5, + right = p.x >= sx, + bottom = p.y >= sy, + i = (bottom << 1) + right; + + // Recursively insert into the child node. + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + + // Update the bounds as we recurse. + if (right) x1 = sx; else x2 = sx; + if (bottom) y1 = sy; else y2 = sy; + insert(n, p, x1, y1, x2, y2); + } + + // Create the root node. + var root = d3_geom_quadtreeNode(); + + root.add = function(p) { + insert(root, p, x1, y1, x2, y2); + }; + + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); + }; + + // Insert all points. + points.forEach(root.add); + return root; +}; + +function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null + }; +} + +function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, + sy = (y1 + y2) * .5, + children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } +} + +function d3_geom_quadtreePoint(p) { + return { + x: p[0], + y: p[1] + }; +} +})(); diff --git a/media/js/collusion/d3.js b/media/js/collusion/d3.js new file mode 100644 index 0000000000..1601600544 --- /dev/null +++ b/media/js/collusion/d3.js @@ -0,0 +1,3586 @@ +(function(){d3 = {version: "1.24.0"}; // semver +if (!Date.now) Date.now = function() { + return +new Date; +}; +if (!Object.create) Object.create = function(o) { + /** @constructor */ function f() {} + f.prototype = o; + return new f; +}; +var d3_array = d3_arraySlice; // conversion for NodeLists + +function d3_arrayCopy(psuedoarray) { + var i = -1, n = psuedoarray.length, array = []; + while (++i < n) array.push(psuedoarray[i]); + return array; +} + +function d3_arraySlice(psuedoarray) { + return Array.prototype.slice.call(psuedoarray); +} + +try { + d3_array(document.documentElement.childNodes)[0].nodeType; +} catch(e) { + d3_array = d3_arrayCopy; +} +d3.functor = function(v) { + return typeof v === "function" ? v : function() { return v; }; +}; +// A getter-setter method that preserves the appropriate `this` context. +d3.rebind = function(object, method) { + return function() { + var x = method.apply(object, arguments); + return arguments.length ? object : x; + }; +}; +d3.ascending = function(a, b) { + return a < b ? -1 : a > b ? 1 : 0; +}; +d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : 0; +}; +d3.min = function(array, f) { + var i = -1, + n = array.length, + a, + b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; +}; +d3.max = function(array, f) { + var i = -1, + n = array.length, + a, + b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; +}; +d3.sum = function(array, f) { + var s = 0, + n = array.length, + a, + i = -1; + + if (arguments.length === 1) { + while (++i < n) if (!isNaN(a = +array[i])) s += a; + } else { + while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; + } + + return s; +}; +d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m;) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n;) { + zip[j] = arguments[j][i]; + } + } + return zips; +}; + +function d3_zipLength(d) { + return d.length; +} +// Locate the insertion point for x in a to maintain sorted order. The +// arguments lo and hi may be used to specify a subset of the array which should +// be considered; by default the entire array is used. If x is already present +// in a, the insertion point will be before (to the left of) any existing +// entries. The return value is suitable for use as the first argument to +// `array.splice` assuming that a is already sorted. +// +// The returned insertion point i partitions the array a into two halves so that +// all v < x for v in a[lo:i] for the left side and all v >= x for v in a[i:hi] +// for the right side. +d3.bisectLeft = function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = (lo + hi) >> 1; + if (a[mid] < x) lo = mid + 1; + else hi = mid; + } + return lo; +}; + +// Similar to bisectLeft, but returns an insertion point which comes after (to +// the right of) any existing entries of x in a. +// +// The returned insertion point i partitions the array into two halves so that +// all v <= x for v in a[lo:i] for the left side and all v > x for v in a[i:hi] +// for the right side. +d3.bisect = +d3.bisectRight = function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = (lo + hi) >> 1; + if (x < a[mid]) hi = mid; + else lo = mid + 1; + } + return lo; +}; +d3.first = function(array, f) { + var i = 0, + n = array.length, + a = array[0], + b; + if (arguments.length === 1) f = d3.ascending; + while (++i < n) { + if (f.call(array, a, b = array[i]) > 0) { + a = b; + } + } + return a; +}; +d3.last = function(array, f) { + var i = 0, + n = array.length, + a = array[0], + b; + if (arguments.length === 1) f = d3.ascending; + while (++i < n) { + if (f.call(array, a, b = array[i]) <= 0) { + a = b; + } + } + return a; +}; +d3.nest = function() { + var nest = {}, + keys = [], + sortKeys = [], + sortValues, + rollup; + + function map(array, depth) { + if (depth >= keys.length) return rollup + ? rollup.call(nest, array) : (sortValues + ? array.sort(sortValues) + : array); + + var i = -1, + n = array.length, + key = keys[depth++], + keyValue, + object, + o = {}; + + while (++i < n) { + if ((keyValue = key(object = array[i])) in o) { + o[keyValue].push(object); + } else { + o[keyValue] = [object]; + } + } + + for (keyValue in o) { + o[keyValue] = map(o[keyValue], depth); + } + + return o; + } + + function entries(map, depth) { + if (depth >= keys.length) return map; + + var a = [], + sortKey = sortKeys[depth++], + key; + + for (key in map) { + a.push({key: key, values: entries(map[key], depth)}); + } + + if (sortKey) a.sort(function(a, b) { + return sortKey(a.key, b.key); + }); + + return a; + } + + nest.map = function(array) { + return map(array, 0); + }; + + nest.entries = function(array) { + return entries(map(array, 0), 0); + }; + + nest.key = function(d) { + keys.push(d); + return nest; + }; + + // Specifies the order for the most-recently specified key. + // Note: only applies to entries. Map keys are unordered! + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + + // Specifies the order for leaf values. + // Applies to both maps and entries array. + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + + nest.rollup = function(f) { + rollup = f; + return nest; + }; + + return nest; +}; +d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; +}; +d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; +}; +d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({key: key, value: map[key]}); + return entries; +}; +d3.permute = function(array, indexes) { + var permutes = [], + i = -1, + n = indexes.length; + while (++i < n) permutes[i] = array[indexes[i]]; + return permutes; +}; +d3.merge = function(arrays) { + return Array.prototype.concat.apply([], arrays); +}; +d3.split = function(array, f) { + var arrays = [], + values = [], + value, + i = -1, + n = array.length; + if (arguments.length < 2) f = d3_splitter; + while (++i < n) { + if (f.call(values, value = array[i], i)) { + values = []; + } else { + if (!values.length) arrays.push(values); + values.push(value); + } + } + return arrays; +}; + +function d3_splitter(d) { + return d == null; +} +function d3_collapse(s) { + return s.replace(/(^\s+)|(\s+$)/g, "").replace(/\s+/g, " "); +} +// +// Note: assigning to the arguments array simultaneously changes the value of +// the corresponding argument! +// +// TODO The `this` argument probably shouldn't be the first argument to the +// callback, anyway, since it's redundant. However, that will require a major +// version bump due to backwards compatibility, so I'm not changing it right +// away. +// +function d3_call(callback) { + callback.apply(this, (arguments[0] = this, arguments)); + return this; +} +/** + * @param {number} start + * @param {number=} stop + * @param {number=} step + */ +d3.range = function(start, stop, step) { + if (arguments.length === 1) { stop = start; start = 0; } + if (step == null) step = 1; + if ((stop - start) / step == Infinity) throw new Error("infinite range"); + var range = [], + i = -1, + j; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j); + else while ((j = start + step * ++i) < stop) range.push(j); + return range; +}; +d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); +}; + +var d3_requote_re = /[\\\^\$\*\+\?\[\]\(\)\.\{\}]/g; +d3.round = function(x, n) { + return n + ? Math.round(x * Math.pow(10, n)) * Math.pow(10, -n) + : Math.round(x); +}; +d3.xhr = function(url, mime, callback) { + var req = new XMLHttpRequest; + if (arguments.length < 3) callback = mime; + else if (mime && req.overrideMimeType) req.overrideMimeType(mime); + req.open("GET", url, true); + req.onreadystatechange = function() { + if (req.readyState === 4) callback(req.status < 300 ? req : null); + }; + req.send(null); +}; +d3.text = function(url, mime, callback) { + function ready(req) { + callback(req && req.responseText); + } + if (arguments.length < 3) { + callback = mime; + mime = null; + } + d3.xhr(url, mime, ready); +}; +d3.json = function(url, callback) { + d3.text(url, "application/json", function(text) { + callback(text ? JSON.parse(text) : null); + }); +}; +d3.html = function(url, callback) { + d3.text(url, "text/html", function(text) { + if (text != null) { // Treat empty string as valid HTML. + var range = document.createRange(); + range.selectNode(document.body); + text = range.createContextualFragment(text); + } + callback(text); + }); +}; +d3.xml = function(url, mime, callback) { + function ready(req) { + callback(req && req.responseXML); + } + if (arguments.length < 3) { + callback = mime; + mime = null; + } + d3.xhr(url, mime, ready); +}; +d3.ns = { + + prefix: { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }, + + qualify: function(name) { + var i = name.indexOf(":"); + return i < 0 ? name : { + space: d3.ns.prefix[name.substring(0, i)], + local: name.substring(i + 1) + }; + } + +}; +/** @param {...string} types */ +d3.dispatch = function(types) { + var dispatch = {}, + type; + for (var i = 0, n = arguments.length; i < n; i++) { + type = arguments[i]; + dispatch[type] = d3_dispatch(type); + } + return dispatch; +}; + +function d3_dispatch(type) { + var dispatch = {}, + listeners = []; + + dispatch.add = function(listener) { + for (var i = 0; i < listeners.length; i++) { + if (listeners[i].listener == listener) return dispatch; // already registered + } + listeners.push({listener: listener, on: true}); + return dispatch; + }; + + dispatch.remove = function(listener) { + for (var i = 0; i < listeners.length; i++) { + var l = listeners[i]; + if (l.listener == listener) { + l.on = false; + listeners = listeners.slice(0, i).concat(listeners.slice(i + 1)); + break; + } + } + return dispatch; + }; + + dispatch.dispatch = function() { + var ls = listeners; // defensive reference + for (var i = 0, n = ls.length; i < n; i++) { + var l = ls[i]; + if (l.on) l.listener.apply(this, arguments); + } + }; + + return dispatch; +}; +// TODO align +d3.format = function(specifier) { + var match = d3_format_re.exec(specifier), + fill = match[1] || " ", + sign = match[3] || "", + zfill = match[5], + width = +match[6], + comma = match[7], + precision = match[8], + type = match[9], + percentage = false, + integer = false; + + if (precision) precision = precision.substring(1); + + if (zfill) { + fill = "0"; // TODO align = "="; + if (comma) width -= Math.floor((width - 1) / 4); + } + + switch (type) { + case "n": comma = true; type = "g"; break; + case "%": percentage = true; type = "f"; break; + case "p": percentage = true; type = "r"; break; + case "d": integer = true; precision = "0"; break; + } + + type = d3_format_types[type] || d3_format_typeDefault; + + return function(value) { + var number = percentage ? value * 100 : +value, + negative = (number < 0) && (number = -number) ? "\u2212" : sign; + + // Return the empty string for floats formatted as ints. + if (integer && (number % 1)) return ""; + + // Convert the input value to the desired precision. + value = type(number, precision); + + // If the fill character is 0, the sign and group is applied after the fill. + if (zfill) { + var length = value.length + negative.length; + if (length < width) value = new Array(width - length + 1).join(fill) + value; + if (comma) value = d3_format_group(value); + value = negative + value; + } + + // Otherwise (e.g., space-filling), the sign and group is applied before. + else { + if (comma) value = d3_format_group(value); + value = negative + value; + var length = value.length; + if (length < width) value = new Array(width - length + 1).join(fill) + value; + } + if (percentage) value += "%"; + + return value; + }; +}; + +// [[fill]align][sign][#][0][width][,][.precision][type] +var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; + +var d3_format_types = { + g: function(x, p) { return x.toPrecision(p); }, + e: function(x, p) { return x.toExponential(p); }, + f: function(x, p) { return x.toFixed(p); }, + r: function(x, p) { + var n = 1 + Math.floor(1e-15 + Math.log(x) / Math.LN10); + return d3.round(x, p - n).toFixed(Math.max(0, p - n)); + } +}; + +function d3_format_typeDefault(x) { + return x + ""; +} + +// Apply comma grouping for thousands. +function d3_format_group(value) { + var i = value.lastIndexOf("."), + f = i >= 0 ? value.substring(i) : (i = value.length, ""), + t = []; + while (i > 0) t.push(value.substring(i -= 3, i + 3)); + return t.reverse().join(",") + f; +} +/* + * TERMS OF USE - EASING EQUATIONS + * + * Open source under the BSD License. + * + * Copyright 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the author nor the names of contributors may be used to + * endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +var d3_ease_quad = d3_ease_poly(2), + d3_ease_cubic = d3_ease_poly(3); + +var d3_ease = { + linear: function() { return d3_ease_linear; }, + poly: d3_ease_poly, + quad: function() { return d3_ease_quad; }, + cubic: function() { return d3_ease_cubic; }, + sin: function() { return d3_ease_sin; }, + exp: function() { return d3_ease_exp; }, + circle: function() { return d3_ease_circle; }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { return d3_ease_bounce; } +}; + +var d3_ease_mode = { + "in": function(f) { return f; }, + "out": d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { return d3_ease_reflect(d3_ease_reverse(f)); } +}; + +d3.ease = function(name) { + var i = name.indexOf("-"), + t = i >= 0 ? name.substring(0, i) : name, + m = i >= 0 ? name.substring(i + 1) : "in"; + return d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1))); +}; + +function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; +} + +function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t))); + }; +} + +function d3_ease_linear(t) { + return t; +} + +function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + } +} + +function d3_ease_sin(t) { + return 1 - Math.cos(t * Math.PI / 2); +} + +function d3_ease_exp(t) { + return t ? Math.pow(2, 10 * (t - 1)) - 1e-3 : 0; +} + +function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); +} + +function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = 0.45; + if (arguments.length < 1) { a = 1; s = p / 4; } + else s = p / (2 * Math.PI) * Math.asin(1 / a); + return function(t) { + return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p); + }; +} + +function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; +} + +function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t + : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 + : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 + : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; +} +d3.event = null; +d3.interpolate = function(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))); + return f; +}; + +d3.interpolateNumber = function(a, b) { + b -= a; + return function(t) { return a + b * t; }; +}; + +d3.interpolateRound = function(a, b) { + b -= a; + return function(t) { return Math.round(a + b * t); }; +}; + +d3.interpolateString = function(a, b) { + var m, // current match + i, // current index + j, // current index (for coallescing) + s0 = 0, // start index of current string prefix + s1 = 0, // end index of current string prefix + s = [], // string constants and placeholders + q = [], // number interpolators + n, // q.length + o; + + // Reset our regular expression! + d3_interpolate_number.lastIndex = 0; + + // Find all numbers in b. + for (i = 0; m = d3_interpolate_number.exec(b); ++i) { + if (m.index) s.push(b.substring(s0, s1 = m.index)); + q.push({i: s.length, x: m[0]}); + s.push(null); + s0 = d3_interpolate_number.lastIndex; + } + if (s0 < b.length) s.push(b.substring(s0)); + + // Find all numbers in a. + for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { + o = q[i]; + if (o.x == m[0]) { // The numbers match, so coallesce. + if (o.i) { + if (s[o.i + 1] == null) { // This match is followed by another number. + s[o.i - 1] += o.x; + s.splice(o.i, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } else { // This match is followed by a string, so coallesce twice. + s[o.i - 1] += o.x + s[o.i + 1]; + s.splice(o.i, 2); + for (j = i + 1; j < n; ++j) q[j].i -= 2; + } + } else { + if (s[o.i + 1] == null) { // This match is followed by another number. + s[o.i] = o.x; + } else { // This match is followed by a string, so coallesce twice. + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } + } + q.splice(i, 1); + n--; + i--; + } else { + o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); + } + } + + // Remove any numbers in b not found in a. + while (i < n) { + o = q.pop(); + if (s[o.i + 1] == null) { // This match is followed by another number. + s[o.i] = o.x; + } else { // This match is followed by a string, so coallesce twice. + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + } + n--; + } + + // Special optimization for only a single match. + if (s.length === 1) { + return s[0] == null ? q[0].x : function() { return b; }; + } + + // Otherwise, interpolate each of the numbers and rejoin the string. + return function(t) { + for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; +}; + +d3.interpolateRgb = function(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, + ag = a.g, + ab = a.b, + br = b.r - ar, + bg = b.g - ag, + bb = b.b - ab; + return function(t) { + return "rgb(" + Math.round(ar + br * t) + + "," + Math.round(ag + bg * t) + + "," + Math.round(ab + bb * t) + + ")"; + }; +}; + +// interpolates HSL space, but outputs RGB string (for compatibility) +d3.interpolateHsl = function(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var h0 = a.h, + s0 = a.s, + l0 = a.l, + h1 = b.h - h0, + s1 = b.s - s0, + l1 = b.l - l0; + return function(t) { + return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t).toString(); + }; +}; + +d3.interpolateArray = function(a, b) { + var x = [], + c = [], + na = a.length, + nb = b.length, + n0 = Math.min(a.length, b.length), + i; + for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i])); + for (; i < na; ++i) c[i] = a[i]; + for (; i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; +}; + +d3.interpolateObject = function(a, b) { + var i = {}, + c = {}, + k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolateByName(k)(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; +} + +var d3_interpolate_number = /[-+]?(?:\d+\.\d+|\d+\.|\.\d+|\d+)(?:[eE][-]?\d+)?/g, + d3_interpolate_rgb = {background: 1, fill: 1, stroke: 1}; + +function d3_interpolateByName(n) { + return n in d3_interpolate_rgb || /\bcolor\b/.test(n) + ? d3.interpolateRgb + : d3.interpolate; +} + +d3.interpolators = [ + d3.interpolateObject, + function(a, b) { return (b instanceof Array) && d3.interpolateArray(a, b); }, + function(a, b) { return (typeof b === "string") && d3.interpolateString(String(a), b); }, + function(a, b) { return (b in d3_rgb_names || /^(#|rgb\(|hsl\()/.test(b)) && d3.interpolateRgb(String(a), b); }, + function(a, b) { return (typeof b === "number") && d3.interpolateNumber(+a, b); } +]; +function d3_uninterpolateNumber(a, b) { + b = 1 / (b - (a = +a)); + return function(x) { return (x - a) * b; }; +} + +function d3_uninterpolateClamp(a, b) { + b = 1 / (b - (a = +a)); + return function(x) { return Math.max(0, Math.min(1, (x - a) * b)); }; +} +d3.rgb = function(r, g, b) { + return arguments.length === 1 + ? d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) + : d3_rgb(~~r, ~~g, ~~b); +}; + +function d3_rgb(r, g, b) { + return new d3_Rgb(r, g, b); +} + +function d3_Rgb(r, g, b) { + this.r = r; + this.g = g; + this.b = b; +} + +d3_Rgb.prototype.brighter = function(k) { + k = Math.pow(0.7, arguments.length ? k : 1); + var r = this.r, + g = this.g, + b = this.b, + i = 30; + if (!r && !g && !b) return d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return d3_rgb( + Math.min(255, Math.floor(r / k)), + Math.min(255, Math.floor(g / k)), + Math.min(255, Math.floor(b / k))); +}; + +d3_Rgb.prototype.darker = function(k) { + k = Math.pow(0.7, arguments.length ? k : 1); + return d3_rgb( + Math.max(0, Math.floor(k * this.r)), + Math.max(0, Math.floor(k * this.g)), + Math.max(0, Math.floor(k * this.b))); +}; + +d3_Rgb.prototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); +}; + +d3_Rgb.prototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); +}; + +function d3_rgb_hex(v) { + return v < 0x10 ? "0" + v.toString(16) : v.toString(16); +} + +function d3_rgb_parse(format, rgb, hsl) { + var r = 0, // red channel; int in [0, 255] + g = 0, // green channel; int in [0, 255] + b = 0, // blue channel; int in [0, 255] + m1, // CSS color specification match + m2, // CSS color specification type (e.g., rgb) + name; + + /* Handle hsl, rgb. */ + m1 = /([a-z]+)\((.*)\)/i.exec(format); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": { + return hsl( + parseFloat(m2[0]), // degrees + parseFloat(m2[1]) / 100, // percentage + parseFloat(m2[2]) / 100 // percentage + ); + } + case "rgb": { + return rgb( + d3_rgb_parseNumber(m2[0]), + d3_rgb_parseNumber(m2[1]), + d3_rgb_parseNumber(m2[2]) + ); + } + } + } + + /* Named colors. */ + if (name = d3_rgb_names[format]) return rgb(name.r, name.g, name.b); + + /* Hexadecimal colors: #rgb and #rrggbb. */ + if (format != null && format.charAt(0) === "#") { + if (format.length === 4) { + r = format.charAt(1); r += r; + g = format.charAt(2); g += g; + b = format.charAt(3); b += b; + } else if (format.length === 7) { + r = format.substring(1, 3); + g = format.substring(3, 5); + b = format.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + + return rgb(r, g, b); +} + +function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), + max = Math.max(r, g, b), + d = max - min, + h, + s, + l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / d + 2; + else h = (r - g) / d + 4; + h *= 60; + } else { + s = h = 0; + } + return d3_hsl(h, s, l); +} + +function d3_rgb_parseNumber(c) { // either integer or percentage + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; +} + +var d3_rgb_names = { + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" +}; + +for (var d3_rgb_name in d3_rgb_names) { + d3_rgb_names[d3_rgb_name] = d3_rgb_parse( + d3_rgb_names[d3_rgb_name], + d3_rgb, + d3_hsl_rgb); +} +d3.hsl = function(h, s, l) { + return arguments.length === 1 + ? d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) + : d3_hsl(+h, +s, +l); +}; + +function d3_hsl(h, s, l) { + return new d3_Hsl(h, s, l); +} + +function d3_Hsl(h, s, l) { + this.h = h; + this.s = s; + this.l = l; +} + +d3_Hsl.prototype.brighter = function(k) { + k = Math.pow(0.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, this.l / k); +}; + +d3_Hsl.prototype.darker = function(k) { + k = Math.pow(0.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, k * this.l); +}; + +d3_Hsl.prototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); +}; + +d3_Hsl.prototype.toString = function() { + return "hsl(" + this.h + "," + this.s * 100 + "%," + this.l * 100 + "%)"; +}; + +function d3_hsl_rgb(h, s, l) { + var m1, + m2; + + /* Some simple corrections for h, s and l. */ + h = h % 360; if (h < 0) h += 360; + s = s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + + /* From FvD 13.37, CSS Color Module Level 3 */ + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + + function v(h) { + if (h > 360) h -= 360; + else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + + function vv(h) { + return Math.round(v(h) * 255); + } + + return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); +} +var d3_select = function(s, n) { return n.querySelector(s); }, + d3_selectAll = function(s, n) { return d3_array(n.querySelectorAll(s)); }; + +// Use Sizzle, if available. +if (typeof Sizzle === "function") { + d3_select = function(s, n) { return Sizzle(s, n)[0]; }; + d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); }; +} + +var d3_root = d3_selection([[document]]); +d3_root[0].parentNode = document.documentElement; + +// TODO fast singleton implementation! +d3.select = function(selector) { + return typeof selector === "string" + ? d3_root.select(selector) + : d3_selection([[selector]]); // assume node +}; + +d3.selectAll = function(selector) { + return typeof selector === "string" + ? d3_root.selectAll(selector) + : d3_selection([d3_array(selector)]); // assume node[] +}; + +function d3_selection(groups) { + + function select(select) { + var subgroups = [], + subgroup, + subnode, + group, + node; + for (var j = 0, m = groups.length; j < m; j++) { + group = groups[j]; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + subgroup.push(subnode = select(node)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + } + + function selectAll(selectAll) { + var subgroups = [], + subgroup, + group, + node; + for (var j = 0, m = groups.length; j < m; j++) { + group = groups[j]; + for (var i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + subgroups.push(subgroup = selectAll(node)); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + } + + // TODO select(function)? + groups.select = function(selector) { + return select(function(node) { + return d3_select(selector, node); + }); + }; + + // TODO selectAll(function)? + groups.selectAll = function(selector) { + return selectAll(function(node) { + return d3_selectAll(selector, node); + }); + }; + + // TODO preserve null elements to maintain index? + groups.filter = function(filter) { + var subgroups = [], + subgroup, + group, + node; + for (var j = 0, m = groups.length; j < m; j++) { + group = groups[j]; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + + groups.map = function(map) { + var group, + node; + for (var j = 0, m = groups.length; j < m; j++) { + group = groups[j]; + for (var i = 0, n = group.length; i < n; i++) { + if (node = group[i]) node.__data__ = map.call(node, node.__data__, i); + } + } + return groups; + }; + + // TODO data(null) for clearing data? + groups.data = function(data, join) { + var enter = [], + update = [], + exit = []; + + function bind(group, groupData) { + var i = 0, + n = group.length, + m = groupData.length, + n0 = Math.min(n, m), + n1 = Math.max(n, m), + updateNodes = [], + enterNodes = [], + exitNodes = [], + node, + nodeData; + + if (join) { + var nodeByKey = {}, + keys = [], + key, + j = groupData.length; + + for (i = 0; i < n; i++) { + key = join.call(node = group[i], node.__data__, i); + if (key in nodeByKey) { + exitNodes[j++] = group[i]; // duplicate key + } else { + nodeByKey[key] = node; + } + keys.push(key); + } + + for (i = 0; i < m; i++) { + node = nodeByKey[key = join.call(groupData, nodeData = groupData[i], i)]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_enterNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + delete nodeByKey[key]; + } + + for (i = 0; i < n; i++) { + if (keys[i] in nodeByKey) { + exitNodes[i] = group[i]; + } + } + } else { + for (; i < n0; i++) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_enterNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + } + for (; i < m; i++) { + enterNodes[i] = d3_selection_enterNode(groupData[i]); + updateNodes[i] = exitNodes[i] = null; + } + for (; i < n1; i++) { + exitNodes[i] = group[i]; + enterNodes[i] = updateNodes[i] = null; + } + } + + enterNodes.parentNode + = updateNodes.parentNode + = exitNodes.parentNode + = group.parentNode; + + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + + var i = -1, + n = groups.length, + group; + if (typeof data === "function") { + while (++i < n) { + bind(group = groups[i], data.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = groups[i], data); + } + } + + var selection = d3_selection(update); + selection.enter = function() { + return d3_selectionEnter(enter); + }; + selection.exit = function() { + return d3_selection(exit); + }; + return selection; + }; + + // TODO mask forEach? or rename for eachData? + // TODO offer the same semantics for map, reduce, etc.? + groups.each = function(callback) { + for (var j = 0, m = groups.length; j < m; j++) { + var group = groups[j]; + for (var i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) callback.call(node, node.__data__, i); + } + } + return groups; + }; + + function first(callback) { + for (var j = 0, m = groups.length; j < m; j++) { + var group = groups[j]; + for (var i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return callback.call(node, node.__data__, i); + } + } + return null; + } + + groups.empty = function() { + return !first(function() { return true; }); + }; + + groups.node = function() { + return first(function() { return this; }); + }; + + groups.attr = function(name, value) { + name = d3.ns.qualify(name); + + // If no value is specified, return the first value. + if (arguments.length < 2) { + return first(name.local + ? function() { return this.getAttributeNS(name.space, name.local); } + : function() { return this.getAttribute(name); }); + } + + /** @this {Element} */ + function attrNull() { + this.removeAttribute(name); + } + + /** @this {Element} */ + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + + /** @this {Element} */ + function attrConstant() { + this.setAttribute(name, value); + } + + /** @this {Element} */ + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + + /** @this {Element} */ + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); + else this.setAttribute(name, x); + } + + /** @this {Element} */ + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); + else this.setAttributeNS(name.space, name.local, x); + } + + return groups.each(value == null + ? (name.local ? attrNullNS : attrNull) : (typeof value === "function" + ? (name.local ? attrFunctionNS : attrFunction) + : (name.local ? attrConstantNS : attrConstant))); + }; + + groups.classed = function(name, value) { + var re = new RegExp("(^|\\s+)" + d3.requote(name) + "(\\s+|$)", "g"); + + // If no value is specified, return the first value. + if (arguments.length < 2) { + return first(function() { + if (c = this.classList) return c.contains(name); + var c = this.className; + re.lastIndex = 0; + return re.test(c.baseVal != null ? c.baseVal : c); + }); + } + + /** @this {Element} */ + function classedAdd() { + if (c = this.classList) return c.add(name); + var c = this.className, + cb = c.baseVal != null, + cv = cb ? c.baseVal : c; + re.lastIndex = 0; + if (!re.test(cv)) { + cv = d3_collapse(cv + " " + name); + if (cb) c.baseVal = cv; + else this.className = cv; + } + } + + /** @this {Element} */ + function classedRemove() { + if (c = this.classList) return c.remove(name); + var c = this.className, + cb = c.baseVal != null, + cv = cb ? c.baseVal : c; + cv = d3_collapse(cv.replace(re, " ")); + if (cb) c.baseVal = cv; + else this.className = cv; + } + + /** @this {Element} */ + function classedFunction() { + (value.apply(this, arguments) + ? classedAdd + : classedRemove).call(this); + } + + return groups.each(typeof value === "function" + ? classedFunction : value + ? classedAdd + : classedRemove); + }; + + groups.style = function(name, value, priority) { + if (arguments.length < 3) priority = ""; + + // If no value is specified, return the first value. + if (arguments.length < 2) { + return first(function() { + return window.getComputedStyle(this, null).getPropertyValue(name); + }); + } + + /** @this {Element} */ + function styleNull() { + this.style.removeProperty(name); + } + + /** @this {Element} */ + function styleConstant() { + this.style.setProperty(name, value, priority); + } + + /** @this {Element} */ + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); + else this.style.setProperty(name, x, priority); + } + + return groups.each(value == null + ? styleNull : (typeof value === "function" + ? styleFunction : styleConstant)); + }; + + groups.property = function(name, value) { + name = d3.ns.qualify(name); + + // If no value is specified, return the first value. + if (arguments.length < 2) { + return first(function() { + return this[name]; + }); + } + + /** @this {Element} */ + function propertyNull() { + delete this[name]; + } + + /** @this {Element} */ + function propertyConstant() { + this[name] = value; + } + + /** @this {Element} */ + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; + else this[name] = x; + } + + return groups.each(value == null + ? propertyNull : (typeof value === "function" + ? propertyFunction : propertyConstant)); + }; + + groups.text = function(value) { + + // If no value is specified, return the first value. + if (arguments.length < 1) { + return first(function() { + return this.textContent; + }); + } + + /** @this {Element} */ + function textConstant() { + this.textContent = value; + } + + /** @this {Element} */ + function textFunction() { + this.textContent = value.apply(this, arguments); + } + + return groups.each(typeof value === "function" + ? textFunction : textConstant); + }; + + groups.html = function(value) { + + // If no value is specified, return the first value. + if (arguments.length < 1) { + return first(function() { + return this.innerHTML; + }); + } + + /** @this {Element} */ + function htmlConstant() { + this.innerHTML = value; + } + + /** @this {Element} */ + function htmlFunction() { + this.innerHTML = value.apply(this, arguments); + } + + return groups.each(typeof value === "function" + ? htmlFunction : htmlConstant); + }; + + // TODO append(node)? + // TODO append(function)? + groups.append = function(name) { + name = d3.ns.qualify(name); + + function append(node) { + return node.appendChild(document.createElement(name)); + } + + function appendNS(node) { + return node.appendChild(document.createElementNS(name.space, name.local)); + } + + return select(name.local ? appendNS : append); + }; + + // TODO insert(node, function)? + // TODO insert(function, string)? + // TODO insert(function, function)? + groups.insert = function(name, before) { + name = d3.ns.qualify(name); + + function insert(node) { + return node.insertBefore( + document.createElement(name), + d3_select(before, node)); + } + + function insertNS(node) { + return node.insertBefore( + document.createElementNS(name.space, name.local), + d3_select(before, node)); + } + + return select(name.local ? insertNS : insert); + }; + + // TODO remove(selector)? + // TODO remove(node)? + // TODO remove(function)? + groups.remove = function() { + return groups.each(function() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + }); + }; + + groups.sort = function(comparator) { + comparator = d3_selection_comparator.apply(this, arguments); + for (var j = 0, m = groups.length; j < m; j++) { + var group = groups[j]; + group.sort(comparator); + for (var i = 1, n = group.length, prev = group[0]; i < n; i++) { + var node = group[i]; + if (node) { + if (prev) prev.parentNode.insertBefore(node, prev.nextSibling); + prev = node; + } + } + } + return groups; + }; + + // type can be namespaced, e.g., "click.foo" + // listener can be null for removal + groups.on = function(type, listener, capture) { + if (arguments.length < 3) capture = false; + + // parse the type specifier + var i = type.indexOf("."), + typo = i === -1 ? type : type.substring(0, i), + name = "__on" + type; + + // remove the old event listener, and add the new event listener + return groups.each(function(d, i) { + if (this[name]) this.removeEventListener(typo, this[name], capture); + if (listener) this.addEventListener(typo, this[name] = l, capture); + + // wrapped event listener that preserves i + var node = this; + function l(e) { + var o = d3.event; // Events can be reentrant (e.g., focus). + d3.event = e; + try { + listener.call(this, node.__data__, i); + } finally { + d3.event = o; + } + } + }); + }; + + // TODO slice? + + groups.transition = function() { + return d3_transition(groups); + }; + + groups.call = d3_call; + + return groups; +} + +function d3_selectionEnter(groups) { + + function select(select) { + var subgroups = [], + subgroup, + subnode, + group, + node; + for (var j = 0, m = groups.length; j < m; j++) { + group = groups[j]; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + subgroup.push(subnode = select(group.parentNode)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + } + + // TODO append(node)? + // TODO append(function)? + groups.append = function(name) { + name = d3.ns.qualify(name); + + function append(node) { + return node.appendChild(document.createElement(name)); + } + + function appendNS(node) { + return node.appendChild(document.createElementNS(name.space, name.local)); + } + + return select(name.local ? appendNS : append); + }; + + // TODO insert(node, function)? + // TODO insert(function, string)? + // TODO insert(function, function)? + groups.insert = function(name, before) { + name = d3.ns.qualify(name); + + function insert(node) { + return node.insertBefore( + document.createElement(name), + d3_select(before, node)); + } + + function insertNS(node) { + return node.insertBefore( + document.createElementNS(name.space, name.local), + d3_select(before, node)); + } + + return select(name.local ? insertNS : insert); + }; + + return groups; +} + +function d3_selection_comparator(comparator) { + if (!arguments.length) comparator = d3.ascending; + return function(a, b) { + return comparator(a && a.__data__, b && b.__data__); + }; +} + +function d3_selection_enterNode(data) { + return {__data__: data}; +} +d3.transition = d3_root.transition; + +var d3_transitionId = 0, + d3_transitionInheritId = 0; + +function d3_transition(groups) { + var transition = {}, + transitionId = d3_transitionInheritId || ++d3_transitionId, + tweens = {}, + interpolators = [], + remove = false, + event = d3.dispatch("start", "end"), + stage = [], + delay = [], + duration = [], + durationMax, + ease = d3.ease("cubic-in-out"); + + // + // Be careful with concurrent transitions! + // + // Say transition A causes an exit. Before A finishes, a transition B is + // created, and believes it only needs to do an update, because the elements + // haven't been removed yet (which happens at the very end of the exit + // transition). + // + // Even worse, what if either transition A or B has a staggered delay? Then, + // some elements may be removed, while others remain. Transition B does not + // know to enter the elements because they were still present at the time + // the transition B was created (but not yet started). + // + // To prevent such confusion, we only trigger end events for transitions if + // the transition ending is the only one scheduled for the given element. + // Similarly, we only allow one transition to be active for any given + // element, so that concurrent transitions do not overwrite each other's + // properties. + // + // TODO Support transition namespaces, so that transitions can proceed + // concurrently on the same element if needed. Hopefully, this is rare! + // + + groups.each(function() { + (this.__transition__ || (this.__transition__ = {})).owner = transitionId; + }); + + function step(elapsed) { + var clear = true, + k = -1; + groups.each(function() { + if (stage[++k] === 2) return; // ended + var t = (elapsed - delay[k]) / duration[k], + tx = this.__transition__, + te, // ease(t) + tk, // tween key + ik = interpolators[k]; + + // Check if the (un-eased) time is outside the range [0,1]. + if (t < 1) { + clear = false; + if (t < 0) return; + } else { + t = 1; + } + + // Determine the stage of this transition. + // 0 - Not yet started. + // 1 - In progress. + // 2 - Ended. + if (stage[k]) { + if (!tx || tx.active !== transitionId) { + stage[k] = 2; + return; + } + } else if (!tx || tx.active > transitionId) { + stage[k] = 2; + return; + } else { + stage[k] = 1; + event.start.dispatch.apply(this, arguments); + ik = interpolators[k] = {}; + tx.active = transitionId; + for (tk in tweens) { + if (te = tweens[tk].apply(this, arguments)) { + ik[tk] = te; + } + } + } + + // Apply the interpolators! + te = ease(t); + for (tk in ik) ik[tk].call(this, te); + + // Handle ending transitions. + if (t === 1) { + stage[k] = 2; + if (tx.active === transitionId) { + var owner = tx.owner; + if (owner === transitionId) { + delete this.__transition__; + if (remove) this.parentNode.removeChild(this); + } + d3_transitionInheritId = transitionId; + event.end.dispatch.apply(this, arguments); + d3_transitionInheritId = 0; + tx.owner = owner; + } + } + }); + return clear; + } + + transition.delay = function(value) { + var delayMin = Infinity, + k = -1; + if (typeof value === "function") { + groups.each(function(d, i) { + var x = delay[++k] = +value.apply(this, arguments); + if (x < delayMin) delayMin = x; + }); + } else { + delayMin = +value; + groups.each(function(d, i) { + delay[++k] = delayMin; + }); + } + d3_timer(step, delayMin); + return transition; + }; + + transition.duration = function(value) { + var k = -1; + if (typeof value === "function") { + durationMax = 0; + groups.each(function(d, i) { + var x = duration[++k] = +value.apply(this, arguments); + if (x > durationMax) durationMax = x; + }); + } else { + durationMax = +value; + groups.each(function(d, i) { + duration[++k] = durationMax; + }); + } + return transition; + }; + + transition.ease = function(value) { + ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); + return transition; + }; + + transition.attrTween = function(name, tween) { + + /** @this {Element} */ + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + + /** @this {Element} */ + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + + tweens["attr." + name] = name.local ? attrTweenNS : attrTween; + return transition; + }; + + transition.attr = function(name, value) { + return transition.attrTween(name, d3_transitionTween(value)); + }; + + transition.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = null; + + /** @this {Element} */ + function styleTween(d, i) { + var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + } + + tweens["style." + name] = styleTween; + return transition; + }; + + transition.style = function(name, value, priority) { + if (arguments.length < 3) priority = null; + return transition.styleTween(name, d3_transitionTween(value), priority); + }; + + transition.text = function(value) { + tweens.text = function(d, i) { + this.textContent = typeof value === "function" + ? value.call(this, d, i) + : value; + }; + return transition; + }; + + transition.select = function(query) { + var k, t = d3_transition(groups.select(query)).ease(ease); + k = -1; t.delay(function(d, i) { return delay[++k]; }); + k = -1; t.duration(function(d, i) { return duration[++k]; }); + return t; + }; + + transition.selectAll = function(query) { + var k, t = d3_transition(groups.selectAll(query)).ease(ease); + k = -1; t.delay(function(d, i) { return delay[i ? k : ++k]; }) + k = -1; t.duration(function(d, i) { return duration[i ? k : ++k]; }); + return t; + }; + + transition.remove = function() { + remove = true; + return transition; + }; + + transition.each = function(type, listener) { + event[type].add(listener); + return transition; + }; + + transition.call = d3_call; + + return transition.delay(0).duration(250); +} + +function d3_transitionTween(b) { + return typeof b === "function" + ? function(d, i, a) { var v = b.call(this, d, i) + ""; return a != v && d3.interpolate(a, v); } + : (b = b + "", function(d, i, a) { return a != b && d3.interpolate(a, b); }); +} +var d3_timer_queue = null, + d3_timer_interval, // is an interval (or frame) active? + d3_timer_timeout; // is a timeout active? + +// The timer will continue to fire until callback returns true. +d3.timer = function(callback) { + d3_timer(callback, 0); +}; + +function d3_timer(callback, delay) { + var now = Date.now(), + found = false, + t0, + t1 = d3_timer_queue; + + if (!isFinite(delay)) return; + + // See if the callback's already in the queue. + while (t1) { + if (t1.callback === callback) { + t1.then = now; + t1.delay = delay; + found = true; + break; + } + t0 = t1; + t1 = t1.next; + } + + // Otherwise, add the callback to the queue. + if (!found) d3_timer_queue = { + callback: callback, + then: now, + delay: delay, + next: d3_timer_queue + }; + + // Start animatin'! + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } +} + +function d3_timer_step() { + var elapsed, + now = Date.now(), + t1 = d3_timer_queue; + + while (t1) { + elapsed = now - t1.then; + if (elapsed > t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + + var delay = d3_timer_flush() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } +} + +d3.timer.flush = function() { + var elapsed, + now = Date.now(), + t1 = d3_timer_queue; + + while (t1) { + elapsed = now - t1.then; + if (!t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + + d3_timer_flush(); +}; + +// Flush after callbacks, to avoid concurrent queue modification. +function d3_timer_flush() { + var t0 = null, + t1 = d3_timer_queue, + then = Infinity; + while (t1) { + if (t1.flush) { + t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; + } else { + then = Math.min(then, t1.then + t1.delay); + t1 = (t0 = t1).next; + } + } + return then; +} + +var d3_timer_frame = window.requestAnimationFrame + || window.webkitRequestAnimationFrame + || window.mozRequestAnimationFrame + || window.oRequestAnimationFrame + || window.msRequestAnimationFrame + || function(callback) { setTimeout(callback, 17); }; +d3.scale = {}; + +function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [start, stop] : [stop, start]; +} +function d3_scale_nice(domain, nice) { + var i0 = 0, + i1 = domain.length - 1, + x0 = domain[i0], + x1 = domain[i1], + dx; + + if (x1 < x0) { + dx = i0; i0 = i1; i1 = dx; + dx = x0; x0 = x1; x1 = dx; + } + + nice = nice(x1 - x0); + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + return domain; +} + +function d3_scale_niceDefault() { + return Math; +} +d3.scale.linear = function() { + var domain = [0, 1], + range = [0, 1], + interpolate = d3.interpolate, + clamp = false, + output, + input; + + function rescale() { + var linear = domain.length == 2 ? d3_scale_bilinear : d3_scale_polylinear, + uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3.interpolate); + return scale; + } + + function scale(x) { + return output(x); + } + + // Note: requires range is coercible to number! + scale.invert = function(y) { + return input(y); + }; + + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3.interpolateRound); + }; + + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + + scale.tickFormat = function(m) { + return d3_scale_linearTickFormat(domain, m); + }; + + scale.nice = function() { + d3_scale_nice(domain, d3_scale_linearNice); + return rescale(); + }; + + return rescale(); +}; + +function d3_scale_linearRebind(scale, linear) { + scale.range = d3.rebind(scale, linear.range); + scale.rangeRound = d3.rebind(scale, linear.rangeRound); + scale.interpolate = d3.rebind(scale, linear.interpolate); + scale.clamp = d3.rebind(scale, linear.clamp); + return scale; +}; + +function d3_scale_linearNice(dx) { + dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1); + return { + floor: function(x) { return Math.floor(x / dx) * dx; }, + ceil: function(x) { return Math.ceil(x / dx) * dx; }, + }; +} + +// TODO Dates? Ugh. +function d3_scale_linearTickRange(domain, m) { + var extent = d3_scaleExtent(domain), + span = extent[1] - extent[0], + step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), + err = m / span * step; + + // Filter ticks to get closer to the desired count. + if (err <= .15) step *= 10; + else if (err <= .35) step *= 5; + else if (err <= .75) step *= 2; + + // Round start and stop values to step interval. + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; // inclusive + extent[2] = step; + return extent; +} + +function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); +} + +function d3_scale_linearTickFormat(domain, m) { + return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f"); +} +function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), + i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; +} +function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], + i = [], + j = 0, + n = domain.length; + + while (++j < n) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + + return function(x) { + var j = d3.bisect(domain, x, 1, domain.length - 1) - 1; + return i[j](u[j](x)); + }; +} +d3.scale.log = function() { + var linear = d3.scale.linear(), + log = d3_scale_log, + pow = log.pow; + + function scale(x) { + return linear(log(x)); + } + + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(pow); + log = x[0] < 0 ? d3_scale_logn : d3_scale_log; + pow = log.pow; + linear.domain(x.map(log)); + return scale; + }; + + scale.nice = function() { + linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault)); + return scale; + }; + + scale.ticks = function() { + var extent = d3_scaleExtent(linear.domain()), + ticks = []; + if (extent.every(isFinite)) { + var i = Math.floor(extent[0]), + j = Math.ceil(extent[1]), + u = pow(extent[0]), + v = pow(extent[1]); + if (log === d3_scale_logn) { + ticks.push(pow(i)); + for (; i++ < j;) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k); + } else { + for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } + for (i = 0; ticks[i] < u; i++) {} // strip small values + for (j = ticks.length; ticks[j - 1] > v; j--) {} // strip big values + ticks = ticks.slice(i, j); + } + return ticks; + }; + + scale.tickFormat = function() { + return d3_scale_logTickFormat; + }; + + return d3_scale_linearRebind(scale, linear); +}; + +function d3_scale_log(x) { + return Math.log(x) / Math.LN10; +} + +function d3_scale_logn(x) { + return -Math.log(-x) / Math.LN10; +} + +d3_scale_log.pow = function(x) { + return Math.pow(10, x); +}; + +d3_scale_logn.pow = function(x) { + return -Math.pow(10, -x); +}; + +function d3_scale_logTickFormat(d) { + return d.toPrecision(1); +} +d3.scale.pow = function() { + var linear = d3.scale.linear(), + exponent = 1, + powp = Number, + powb = powp; + + function scale(x) { + return linear(powp(x)); + } + + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(powb); + var pow = (x[0] || x[x.length - 1]) < 0 ? d3_scale_pown : d3_scale_pow; + powp = pow(exponent); + powb = pow(1 / exponent); + linear.domain(x.map(powp)); + return scale; + }; + + scale.ticks = function(m) { + return d3_scale_linearTicks(scale.domain(), m); + }; + + scale.tickFormat = function(m) { + return d3_scale_linearTickFormat(scale.domain(), m); + }; + + scale.nice = function() { + return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); + }; + + scale.exponent = function(x) { + if (!arguments.length) return exponent; + var domain = scale.domain(); + exponent = x; + return scale.domain(domain); + }; + + return d3_scale_linearRebind(scale, linear); +}; + +function d3_scale_pow(e) { + return function(x) { + return Math.pow(x, e); + }; +} + +function d3_scale_pown(e) { + return function(x) { + return -Math.pow(-x, e); + }; +} +d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); +}; +d3.scale.ordinal = function() { + var domain = [], + index = {}, + range = [], + rangeBand = 0; + + function scale(x) { + var i = x in index ? index[x] : (index[x] = domain.push(x) - 1); + return range[i % range.length]; + } + + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x; + index = {}; + var i = -1, j = -1, n = domain.length; while (++i < n) { + x = domain[i]; + if (!(x in index)) index[x] = ++j; + } + return scale; + }; + + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return scale; + }; + + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], + stop = x[1], + step = (stop - start) / (domain.length - 1 + padding); + range = domain.length == 1 + ? [(start + stop) / 2] + : d3.range(start + step * padding / 2, stop + step / 2, step); + rangeBand = 0; + return scale; + }; + + scale.rangeBands = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], + stop = x[1], + step = (stop - start) / (domain.length + padding); + range = d3.range(start + step * padding, stop, step); + rangeBand = step * (1 - padding); + return scale; + }; + + scale.rangeRoundBands = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], + stop = x[1], + diff = stop - start, + step = Math.floor(diff / (domain.length + padding)), + err = diff - (domain.length - padding) * step; + range = d3.range(start + Math.round(err / 2), stop, step); + rangeBand = Math.round(step * (1 - padding)); + return scale; + }; + + scale.rangeBand = function() { + return rangeBand; + }; + + return scale; +}; +/* + * This product includes color specifications and designs developed by Cynthia + * Brewer (http://colorbrewer.org/). See lib/colorbrewer for more information. + */ + +d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); +}; + +d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); +}; + +d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); +}; + +d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); +}; + +var d3_category10 = [ + "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", + "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" +]; + +var d3_category20 = [ + "#1f77b4", "#aec7e8", + "#ff7f0e", "#ffbb78", + "#2ca02c", "#98df8a", + "#d62728", "#ff9896", + "#9467bd", "#c5b0d5", + "#8c564b", "#c49c94", + "#e377c2", "#f7b6d2", + "#7f7f7f", "#c7c7c7", + "#bcbd22", "#dbdb8d", + "#17becf", "#9edae5" +]; + +var d3_category20b = [ + "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", + "#637939", "#8ca252", "#b5cf6b", "#cedb9c", + "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", + "#843c39", "#ad494a", "#d6616b", "#e7969c", + "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" +]; + +var d3_category20c = [ + "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", + "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", + "#31a354", "#74c476", "#a1d99b", "#c7e9c0", + "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", + "#636363", "#969696", "#bdbdbd", "#d9d9d9" +]; +d3.scale.quantile = function() { + var domain = [], + range = [], + thresholds = []; + + function rescale() { + var k = 0, + n = domain.length, + q = range.length, + i; + thresholds.length = Math.max(0, q - 1); + while (++k < q) { + thresholds[k - 1] = (i = n * k / q) % 1 + ? domain[~~i] + : (domain[i = ~~i] + domain[i - 1]) / 2; + } + } + + function scale(x) { + if (isNaN(x = +x)) return NaN; + return range[d3.bisect(thresholds, x)]; + } + + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.filter(function(d) { return !isNaN(d); }).sort(d3.ascending); + rescale(); + return scale; + }; + + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rescale(); + return scale; + }; + + scale.quantiles = function() { + return thresholds; + }; + + return scale; +}; +d3.scale.quantize = function() { + var x0 = 0, + x1 = 1, + kx = 2, + i = 1, + range = [0, 1]; + + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + + scale.domain = function(x) { + if (!arguments.length) return [x0, x1]; + x0 = x[0]; + x1 = x[1]; + kx = range.length / (x1 - x0); + return scale; + }; + + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + }; + + return scale; +}; +d3.svg = {}; +d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, + outerRadius = d3_svg_arcOuterRadius, + startAngle = d3_svg_arcStartAngle, + endAngle = d3_svg_arcEndAngle; + + function arc() { + var r0 = innerRadius.apply(this, arguments), + r1 = outerRadius.apply(this, arguments), + a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, + a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, + da = a1 - a0, + df = da < Math.PI ? "0" : "1", + c0 = Math.cos(a0), + s0 = Math.sin(a0), + c1 = Math.cos(a1), + s1 = Math.sin(a1); + return da >= d3_svg_arcMax + ? (r0 + ? "M0," + r1 + + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) + + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + + "M0," + r0 + + "A" + r0 + "," + r0 + " 0 1,1 0," + (-r0) + + "A" + r0 + "," + r0 + " 0 1,1 0," + r0 + + "Z" + : "M0," + r1 + + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) + + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + + "Z") + : (r0 + ? "M" + r1 * c0 + "," + r1 * s0 + + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + + "L" + r0 * c1 + "," + r0 * s1 + + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + + "Z" + : "M" + r1 * c0 + "," + r1 * s0 + + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + + "L0,0" + + "Z"); + } + + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3.functor(v); + return arc; + }; + + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3.functor(v); + return arc; + }; + + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3.functor(v); + return arc; + }; + + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3.functor(v); + return arc; + }; + + arc.centroid = function() { + var r = (innerRadius.apply(this, arguments) + + outerRadius.apply(this, arguments)) / 2, + a = (startAngle.apply(this, arguments) + + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; + return [Math.cos(a) * r, Math.sin(a) * r]; + }; + + return arc; +}; + +var d3_svg_arcOffset = -Math.PI / 2, + d3_svg_arcMax = 2 * Math.PI - 1e-6; + +function d3_svg_arcInnerRadius(d) { + return d.innerRadius; +} + +function d3_svg_arcOuterRadius(d) { + return d.outerRadius; +} + +function d3_svg_arcStartAngle(d) { + return d.startAngle; +} + +function d3_svg_arcEndAngle(d) { + return d.endAngle; +} +function d3_svg_line(projection) { + var x = d3_svg_lineX, + y = d3_svg_lineY, + interpolate = "linear", + interpolator = d3_svg_lineInterpolators[interpolate], + tension = .7; + + function line(d) { + return d.length < 1 ? null : "M" + interpolator(projection(d3_svg_linePoints(this, d, x, y)), tension); + } + + line.x = function(v) { + if (!arguments.length) return x; + x = v; + return line; + }; + + line.y = function(v) { + if (!arguments.length) return y; + y = v; + return line; + }; + + line.interpolate = function(v) { + if (!arguments.length) return interpolate; + interpolator = d3_svg_lineInterpolators[interpolate = v]; + return line; + }; + + line.tension = function(v) { + if (!arguments.length) return tension; + tension = v; + return line; + }; + + return line; +} + +d3.svg.line = function() { + return d3_svg_line(Object); +}; + +// Converts the specified array of data into an array of points +// (x-y tuples), by evaluating the specified `x` and `y` functions on each +// data point. The `this` context of the evaluated functions is the specified +// "self" object; each function is passed the current datum and index. +function d3_svg_linePoints(self, d, x, y) { + var points = [], + i = -1, + n = d.length, + fx = typeof x === "function", + fy = typeof y === "function", + value; + if (fx && fy) { + while (++i < n) points.push([ + x.call(self, value = d[i], i), + y.call(self, value, i) + ]); + } else if (fx) { + while (++i < n) points.push([x.call(self, d[i], i), y]); + } else if (fy) { + while (++i < n) points.push([x, y.call(self, d[i], i)]); + } else { + while (++i < n) points.push([x, y]); + } + return points; +} + +// The default `x` property, which references d[0]. +function d3_svg_lineX(d) { + return d[0]; +} + +// The default `y` property, which references d[1]. +function d3_svg_lineY(d) { + return d[1]; +} + +// The various interpolators supported by the `line` class. +var d3_svg_lineInterpolators = { + "linear": d3_svg_lineLinear, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + "basis": d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + "bundle": d3_svg_lineBundle, + "cardinal": d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + "monotone": d3_svg_lineMonotone +}; + +// Linear interpolation; generates "L" commands. +function d3_svg_lineLinear(points) { + var path = [], + i = 0, + n = points.length, + p = points[0]; + path.push(p[0], ",", p[1]); + while (++i < n) path.push("L", (p = points[i])[0], ",", p[1]); + return path.join(""); +} + +// Step interpolation; generates "H" and "V" commands. +function d3_svg_lineStepBefore(points) { + var path = [], + i = 0, + n = points.length, + p = points[0]; + path.push(p[0], ",", p[1]); + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); +} + +// Step interpolation; generates "H" and "V" commands. +function d3_svg_lineStepAfter(points) { + var path = [], + i = 0, + n = points.length, + p = points[0]; + path.push(p[0], ",", p[1]); + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); +} + +// Open cardinal spline interpolation; generates "C" commands. +function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 + ? d3_svg_lineLinear(points) + : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), + d3_svg_lineCardinalTangents(points, tension)); +} + +// Closed cardinal spline interpolation; generates "C" commands. +function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 + ? d3_svg_lineLinear(points) + : points[0] + d3_svg_lineHermite((points.push(points[0]), points), + d3_svg_lineCardinalTangents([points[points.length - 2]] + .concat(points, [points[1]]), tension)); +} + +// Cardinal spline interpolation; generates "C" commands. +function d3_svg_lineCardinal(points, tension, closed) { + return points.length < 3 + ? d3_svg_lineLinear(points) + : points[0] + d3_svg_lineHermite(points, + d3_svg_lineCardinalTangents(points, tension)); +} + +// Hermite spline construction; generates "C" commands. +function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 + || (points.length != tangents.length + && points.length != tangents.length + 2)) { + return d3_svg_lineLinear(points); + } + + var quad = points.length != tangents.length, + path = "", + p0 = points[0], + p = points[1], + t0 = tangents[0], + t = t0, + pi = 1; + + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + + "," + p[0] + "," + p[1]; + } + } + + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + + "," + lp[0] + "," + lp[1]; + } + + return path; +} + +// Generates tangents for a cardinal spline. +function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], + a = (1 - tension) / 2, + p0, + p1 = points[0], + p2 = points[1], + i = 1, + n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([a * (p2[0] - p0[0]), a * (p2[1] - p0[1])]); + } + return tangents; +} + +// B-spline interpolation; generates "C" commands. +function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var path = [], + i = 1, + n = points.length, + pi = points[0], + x0 = pi[0], + y0 = pi[1], + px = [x0, x0, x0, (pi = points[1])[0]], + py = [y0, y0, y0, pi[1]]; + path.push(x0, ",", y0); + d3_svg_lineBasisBezier(path, px, py); + while (++i < n) { + pi = points[i]; + px.shift(); px.push(pi[0]); + py.shift(); py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + i = -1; + while (++i < 2) { + px.shift(); px.push(pi[0]); + py.shift(); py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); +} + +// Open B-spline interpolation; generates "C" commands. +function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], + i = -1, + n = points.length, + pi, + px = [0], + py = [0]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; while (++i < n) { + pi = points[i]; + px.shift(); px.push(pi[0]); + py.shift(); py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); +} + +// Closed B-spline interpolation; generates "C" commands. +function d3_svg_lineBasisClosed(points) { + var path, + i = -1, + n = points.length, + m = n + 4, + pi, + px = [], + py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ + d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) + ]; + --i; while (++i < m) { + pi = points[i % n]; + px.shift(); px.push(pi[0]); + py.shift(); py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); +} + +function d3_svg_lineBundle(points, tension) { + var n = points.length - 1, + x0 = points[0][0], + y0 = points[0][1], + dx = points[n][0] - x0, + dy = points[n][1] - y0, + i = -1, + p, + t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + return d3_svg_lineBasis(points); +} + +// Returns the dot product of the given four-element vectors. +function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; +} + +// Matrix to transform basis (b-spline) control points to bezier +// control points. Derived from FvD 11.2.8. +var d3_svg_lineBasisBezier1 = [0, 2/3, 1/3, 0], + d3_svg_lineBasisBezier2 = [0, 1/3, 2/3, 0], + d3_svg_lineBasisBezier3 = [0, 1/6, 2/3, 1/6]; + +// Pushes a "C" Bézier curve onto the specified path array, given the +// two specified four-element arrays which define the control points. +function d3_svg_lineBasisBezier(path, x, y) { + path.push( + "C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), + ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), + ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), + ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), + ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), + ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); +} + +// Computes the slope from points p0 to p1. +function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); +} + +// Compute three-point differences for the given points. +// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference +function d3_svg_lineFiniteDifferences(points) { + var i = 0, + j = points.length - 1, + m = [], + p0 = points[0], + p1 = points[1], + d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1])); + } + m[i] = d; + return m; +} + +// Interpolates the given points using Fritsch-Carlson Monotone cubic Hermite +// interpolation. Returns an array of tangent vectors. For details, see +// http://en.wikipedia.org/wiki/Monotone_cubic_interpolation +function d3_svg_lineMonotoneTangents(points) { + var tangents = [], + d, + a, + b, + s, + m = d3_svg_lineFiniteDifferences(points), + i = -1, + j = points.length - 1; + + // The first two steps are done by computing finite-differences: + // 1. Compute the slopes of the secant lines between successive points. + // 2. Initialize the tangents at every point as the average of the secants. + + // Then, for each segment… + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + + // 3. If two successive yk = y{k + 1} are equal (i.e., d is zero), then set + // mk = m{k + 1} = 0 as the spline connecting these points must be flat to + // preserve monotonicity. Ignore step 4 and 5 for those k. + + if (Math.abs(d) < 1e-6) { + m[i] = m[i + 1] = 0; + } else { + // 4. Let ak = mk / dk and bk = m{k + 1} / dk. + a = m[i] / d; + b = m[i + 1] / d; + + // 5. Prevent overshoot and ensure monotonicity by restricting the + // magnitude of vector to a circle of radius 3. + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + + // Compute the normalized tangent vector from the slopes. Note that if x is + // not monotonic, it's possible that the slope will be infinite, so we protect + // against NaN by setting the coordinate to zero. + i = -1; while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) + / (6 * (1 + m[i] * m[i])); + tangents.push([s || 0, m[i] * s || 0]); + } + + return tangents; +} + +function d3_svg_lineMonotone(points) { + return points.length < 3 + ? d3_svg_lineLinear(points) + : points[0] + + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); +} +d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; +}; + +function d3_svg_lineRadial(points) { + var point, + i = -1, + n = points.length, + r, + a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] + d3_svg_arcOffset; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; +} +function d3_svg_area(projection) { + var x0 = d3_svg_lineX, + x1 = d3_svg_lineX, + y0 = 0, + y1 = d3_svg_lineY, + interpolate = "linear", + interpolator = d3_svg_lineInterpolators[interpolate], + tension = .7; + + function area(d) { + if (d.length < 1) return null; + var points0 = d3_svg_linePoints(this, d, x0, y0), + points1 = d3_svg_linePoints(this, d, x0 === x1 ? d3_svg_areaX(points0) : x1, y0 === y1 ? d3_svg_areaY(points0) : y1); + return "M" + interpolator(projection(points1), tension) + + "L" + interpolator(projection(points0.reverse()), tension) + + "Z"; + } + + area.x = function(x) { + if (!arguments.length) return x1; + x0 = x1 = x; + return area; + }; + + area.x0 = function(x) { + if (!arguments.length) return x0; + x0 = x; + return area; + }; + + area.x1 = function(x) { + if (!arguments.length) return x1; + x1 = x; + return area; + }; + + area.y = function(y) { + if (!arguments.length) return y1; + y0 = y1 = y; + return area; + }; + + area.y0 = function(y) { + if (!arguments.length) return y0; + y0 = y; + return area; + }; + + area.y1 = function(y) { + if (!arguments.length) return y1; + y1 = y; + return area; + }; + + area.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolator = d3_svg_lineInterpolators[interpolate = x]; + return area; + }; + + area.tension = function(x) { + if (!arguments.length) return tension; + tension = x; + return area; + }; + + return area; +} + +d3.svg.area = function() { + return d3_svg_area(Object); +}; + +function d3_svg_areaX(points) { + return function(d, i) { + return points[i][0]; + }; +} + +function d3_svg_areaY(points) { + return function(d, i) { + return points[i][1]; + }; +} +d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; +}; +d3.svg.chord = function() { + var source = d3_svg_chordSource, + target = d3_svg_chordTarget, + radius = d3_svg_chordRadius, + startAngle = d3_svg_arcStartAngle, + endAngle = d3_svg_arcEndAngle; + + // TODO Allow control point to be customized. + + function chord(d, i) { + var s = subgroup(this, source, d, i), + t = subgroup(this, target, d, i); + return "M" + s.p0 + + arc(s.r, s.p1) + (equals(s, t) + ? curve(s.r, s.p1, s.r, s.p0) + : curve(s.r, s.p1, t.r, t.p0) + + arc(t.r, t.p1) + + curve(t.r, t.p1, s.r, s.p0)) + + "Z"; + } + + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), + r = radius.call(self, subgroup, i), + a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, + a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; + return { + r: r, + a0: a0, + a1: a1, + p0: [r * Math.cos(a0), r * Math.sin(a0)], + p1: [r * Math.cos(a1), r * Math.sin(a1)] + }; + } + + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + + function arc(r, p) { + return "A" + r + "," + r + " 0 0,1 " + p; + } + + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3.functor(v); + return chord; + }; + + chord.source = function(v) { + if (!arguments.length) return source; + source = d3.functor(v); + return chord; + }; + + chord.target = function(v) { + if (!arguments.length) return target; + target = d3.functor(v); + return chord; + }; + + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3.functor(v); + return chord; + }; + + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3.functor(v); + return chord; + }; + + return chord; +}; + +function d3_svg_chordSource(d) { + return d.source; +} + +function d3_svg_chordTarget(d) { + return d.target; +} + +function d3_svg_chordRadius(d) { + return d.radius; +} + +function d3_svg_chordStartAngle(d) { + return d.startAngle; +} + +function d3_svg_chordEndAngle(d) { + return d.endAngle; +} +d3.svg.diagonal = function() { + var source = d3_svg_chordSource, + target = d3_svg_chordTarget, + projection = d3_svg_diagonalProjection; + + function diagonal(d, i) { + var p0 = source.call(this, d, i), + p3 = target.call(this, d, i), + m = (p0.y + p3.y) / 2, + p = [p0, {x: p0.x, y: m}, {x: p3.x, y: m}, p3]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3.functor(x); + return diagonal; + }; + + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3.functor(x); + return diagonal; + }; + + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + + return diagonal; +}; + +function d3_svg_diagonalProjection(d) { + return [d.x, d.y]; +} +d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), + projection = d3_svg_diagonalProjection, + projection_ = diagonal.projection; + + diagonal.projection = function(x) { + return arguments.length + ? projection_(d3_svg_diagonalRadialProjection(projection = x)) + : projection; + }; + + return diagonal; +}; + +function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), + r = d[0], + a = d[1] + d3_svg_arcOffset; + return [r * Math.cos(a), r * Math.sin(a)]; + }; +} +d3.svg.mouse = function(container) { + return d3_svg_mousePoint(container, d3.event); +}; + +// https://bugs.webkit.org/show_bug.cgi?id=44083 +var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; + +function d3_svg_mousePoint(container, e) { + var point = (container.ownerSVGElement || container).createSVGPoint(); + if ((d3_mouse_bug44083 < 0) && (window.scrollX || window.scrollY)) { + var svg = d3.select(document.body) + .append("svg:svg") + .style("position", "absolute") + .style("top", 0) + .style("left", 0); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + if (d3_mouse_bug44083) { + point.x = e.pageX; + point.y = e.pageY; + } else { + point.x = e.clientX; + point.y = e.clientY; + } + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [point.x, point.y]; +}; +d3.svg.touches = function(container) { + var touches = d3.event.touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_svg_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; +}; +d3.svg.symbol = function() { + var type = d3_svg_symbolType, + size = d3_svg_symbolSize; + + function symbol(d, i) { + return (d3_svg_symbols[type.call(this, d, i)] + || d3_svg_symbols.circle) + (size.call(this, d, i)); + } + + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3.functor(x); + return symbol; + }; + + // size of symbol in square pixels + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3.functor(x); + return symbol; + }; + + return symbol; +}; + +function d3_svg_symbolSize() { + return 64; +} + +function d3_svg_symbolType() { + return "circle"; +} + +// TODO cross-diagonal? +var d3_svg_symbols = { + "circle": function(size) { + var r = Math.sqrt(size / Math.PI); + return "M0," + r + + "A" + r + "," + r + " 0 1,1 0," + (-r) + + "A" + r + "," + r + " 0 1,1 0," + r + + "Z"; + }, + "cross": function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + + "H" + -r + + "V" + -3 * r + + "H" + r + + "V" + -r + + "H" + 3 * r + + "V" + r + + "H" + r + + "V" + 3 * r + + "H" + -r + + "V" + r + + "H" + -3 * r + + "Z"; + }, + "diamond": function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), + rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + + "L" + rx + ",0" + + " 0," + ry + + " " + -rx + ",0" + + "Z"; + }, + "square": function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + + "L" + r + "," + -r + + " " + r + "," + r + + " " + -r + "," + r + + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), + ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + + "L" + rx +"," + -ry + + " " + -rx + "," + -ry + + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), + ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + + "L" + rx +"," + ry + + " " + -rx + "," + ry + + "Z"; + } +}; + +d3.svg.symbolTypes = d3.keys(d3_svg_symbols); + +var d3_svg_symbolSqrt3 = Math.sqrt(3), + d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); +})(); diff --git a/media/js/collusion/d3.layout.js b/media/js/collusion/d3.layout.js new file mode 100644 index 0000000000..f2d8e70fc8 --- /dev/null +++ b/media/js/collusion/d3.layout.js @@ -0,0 +1,1817 @@ +(function(){d3.layout = {}; +// Implements hierarchical edge bundling using Holten's algorithm. For each +// input link, a path is computed that travels through the tree, up the parent +// hierarchy to the least common ancestor, and then back down to the destination +// node. Each path is simply an array of nodes. +d3.layout.bundle = function() { + return function(links) { + var paths = [], + i = -1, + n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; +}; + +function d3_layout_bundlePath(link) { + var start = link.source, + end = link.target, + lca = d3_layout_bundleLeastCommonAncestor(start, end), + points = [start]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; +} + +function d3_layout_bundleAncestors(node) { + var ancestors = [], + parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; +} + +function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), + bNodes = d3_layout_bundleAncestors(b), + aNode = aNodes.pop(), + bNode = bNodes.pop(), + sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; +} +d3.layout.chord = function() { + var chord = {}, + chords, + groups, + matrix, + n, + padding = 0, + sortGroups, + sortSubgroups, + sortChords; + + function relayout() { + var subgroups = {}, + groupSums = [], + groupIndex = d3.range(n), + subgroupIndex = [], + k, + x, + x0, + i, + j; + + chords = []; + groups = []; + + // Compute the sum. + k = 0, i = -1; while (++i < n) { + x = 0, j = -1; while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + + // Sort groups… + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + + // Sort subgroups… + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + + // Convert the sum to scaling factor for [0, 2pi]. + // TODO Allow start and end angle to be specified. + // TODO Allow padding to be specified as percentage? + k = (2 * Math.PI - padding * n) / k; + + // Compute the start and end angle for each group and subgroup. + x = 0, i = -1; while (++i < n) { + x0 = x, j = -1; while (++j < n) { + var di = groupIndex[i], + dj = subgroupIndex[i][j], + v = matrix[di][dj]; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: x, + endAngle: x += v * k, + value: v + }; + } + groups.push({ + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }); + x += padding; + } + + // Generate chords for each (non-empty) subgroup-subgroup link. + i = -1; while (++i < n) { + j = i - 1; while (++j < n) { + var source = subgroups[i + "-" + j], + target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value + ? {source: target, target: source} + : {source: source, target: target}) + } + } + } + + if (sortChords) resort(); + } + + function resort() { + chords.sort(function(a, b) { + return sortChords(a.target.value, b.target.value); + }); + } + + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + + return chord; +}; +// A rudimentary force layout using Gauss-Seidel. +d3.layout.force = function() { + var force = {}, + event = d3.dispatch("tick"), + size = [1, 1], + alpha, + friction = .9, + distance = 20, + charge = -30, + gravity = .1, + theta = .8, + interval, + nodes = [], + links = [], + distances; + + function repulse(node, kc) { + return function(quad, x1, y1, x2, y2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, + dy = quad.cy - node.y, + dn = 1 / Math.sqrt(dx * dx + dy * dy); + + /* Barnes-Hut criterion. */ + if ((x2 - x1) * dn < theta) { + var k = kc * quad.count * dn * dn; + node.x += dx * k; + node.y += dy * k; + return true; + } + + if (quad.point && isFinite(dn)) { + var k = kc * dn * dn; + node.x += dx * k; + node.y += dy * k; + } + } + }; + } + + function tick() { + var n = nodes.length, + m = links.length, + q = d3.geom.quadtree(nodes), + i, // current index + o, // current object + s, // current source + t, // current target + l, // current distance + x, // x-distance + y; // y-distance + + // gauss-seidel relaxation for links + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = (x * x + y * y)) { + l = alpha * ((l = Math.sqrt(l)) - distance) / l; + x *= l; + y *= l; + t.x -= x; + t.y -= y; + s.x += x; + s.y += y; + } + } + + // apply gravity forces + var kg = alpha * gravity; + x = size[0] / 2; + y = size[1] / 2; + i = -1; while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * kg; + o.y += (y - o.y) * kg; + } + + // compute quadtree center of mass + d3_layout_forceAccumulate(q); + + // apply charge forces + var kc = alpha * charge; + i = -1; while (++i < n) { + q.visit(repulse(nodes[i], kc)); + } + + // position verlet integration + i = -1; while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + + event.tick.dispatch({type: "tick", alpha: alpha}); + + // simulated annealing, basically + return (alpha *= .99) < .005; + } + + force.on = function(type, listener) { + event[type].add(listener); + return force; + }; + + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + + force.distance = function(x) { + if (!arguments.length) return distance; + distance = x; + return force; + }; + + force.friction = function(x) { + if (!arguments.length) return friction; + friction = x; + return force; + }; + + force.charge = function(x) { + if (!arguments.length) return charge; + charge = x; + return force; + }; + + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = x; + return force; + }; + + force.theta = function(x) { + if (!arguments.length) return theta; + theta = x; + return force; + }; + + force.start = function() { + var i, + j, + n = nodes.length, + m = links.length, + w = size[0], + h = size[1], + neighbors, + o; + + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + } + + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + } + + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + + // initialize node position based on first neighbor + function position(dimension, size) { + var neighbors = neighbor(i), + j = -1, + m = neighbors.length, + x; + while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; + return Math.random() * size; + } + + // initialize neighbors lazily + function neighbor() { + if (!neighbors) { + neighbors = []; + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + return neighbors[i]; + } + + return force.resume(); + }; + + force.resume = function() { + alpha = .1; + d3.timer(tick); + return force; + }; + + force.stop = function() { + alpha = 0; + return force; + }; + + // use `node.call(force.drag)` to make nodes draggable + force.drag = function() { + + this + .on("mouseover.force", d3_layout_forceDragOver) + .on("mouseout.force", d3_layout_forceDragOut) + .on("mousedown.force", d3_layout_forceDragDown); + + d3.select(window) + .on("mousemove.force", dragmove) + .on("mouseup.force", dragup, true) + .on("click.force", d3_layout_forceDragClick, true); + + return force; + }; + + function dragmove() { + if (!d3_layout_forceDragNode) return; + var parent = d3_layout_forceDragElement.parentNode; + + // O NOES! The drag element was removed from the DOM. + if (!parent) { + d3_layout_forceDragNode.fixed = false; + d3_layout_forceDragNode = d3_layout_forceDragElement = null; + return; + } + + var m = d3.svg.mouse(parent); + d3_layout_forceDragMoved = true; + d3_layout_forceDragNode.px = m[0]; + d3_layout_forceDragNode.py = m[1]; + force.resume(); // restart annealing + } + + function dragup() { + if (!d3_layout_forceDragNode) return; + + // If the node was moved, prevent the mouseup from propagating. + // Also prevent the subsequent click from propagating (e.g., for anchors). + if (d3_layout_forceDragMoved) { + d3_layout_forceStopClick = true; + d3_layout_forceCancel(); + } + + dragmove(); + d3_layout_forceDragNode.fixed = false; + d3_layout_forceDragNode = d3_layout_forceDragElement = null; + } + + return force; +}; + +var d3_layout_forceDragNode, + d3_layout_forceDragMoved, + d3_layout_forceStopClick, + d3_layout_forceDragElement; + +function d3_layout_forceDragOver(d) { + d.fixed = true; +} + +function d3_layout_forceDragOut(d) { + if (d !== d3_layout_forceDragNode) { + d.fixed = false; + } +} + +function d3_layout_forceDragDown(d, i) { + (d3_layout_forceDragNode = d).fixed = true; + d3_layout_forceDragMoved = false; + d3_layout_forceDragElement = this; + d3_layout_forceCancel(); +} + +function d3_layout_forceDragClick() { + if (d3_layout_forceStopClick) { + d3_layout_forceCancel(); + d3_layout_forceStopClick = false; + } +} + +function d3_layout_forceCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); +} + +function d3_layout_forceAccumulate(quad) { + var cx = 0, + cy = 0; + quad.count = 0; + if (!quad.leaf) { + quad.nodes.forEach(function(c) { + d3_layout_forceAccumulate(c); + quad.count += c.count; + cx += c.count * c.cx; + cy += c.count * c.cy; + }); + } + if (quad.point) { + // jitter internal nodes that are coincident + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + quad.count++; + cx += quad.point.x; + cy += quad.point.y; + } + quad.cx = cx / quad.count; + quad.cy = cy / quad.count; +} +d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), + size = [1, 1]; // width, height + + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children) { + var i = -1, + n = children.length, + c, + d; + dx /= node.value; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + + function depth(node) { + var children = node.children, + d = 0; + if (children) { + var i = -1, + n = children.length; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + + return d3_layout_hierarchyRebind(partition, hierarchy); +}; +d3.layout.pie = function() { + var value = Number, + sort = null, + startAngle = 0, + endAngle = 2 * Math.PI; + + function pie(data, i) { + + // Compute the start angle. + var a = +(typeof startAngle === "function" + ? startAngle.apply(this, arguments) + : startAngle); + + // Compute the angular range (end - start). + var k = (typeof endAngle === "function" + ? endAngle.apply(this, arguments) + : endAngle) - startAngle; + + // Optionally sort the data. + var index = d3.range(data.length); + if (sort != null) index.sort(function(i, j) { + return sort(data[i], data[j]); + }); + + // Compute the numeric values for each data element. + var values = data.map(value); + + // Convert k into a scale factor from value to angle, using the sum. + k /= values.reduce(function(p, d) { return p + d; }, 0); + + // Compute the arcs! + var arcs = index.map(function(i) { + return { + value: d = values[i], + startAngle: a, + endAngle: a += d * k + }; + }); + + // Return the arcs in the original data's order. + return data.map(function(d, i) { + return arcs[index[i]]; + }); + } + + /** + * Specifies the value function *x*, which returns a nonnegative numeric value + * for each datum. The default value function is `Number`. The value function + * is passed two arguments: the current datum and the current index. + */ + pie.value = function(x) { + if (!arguments.length) return value; + value = x; + return pie; + }; + + /** + * Specifies a sort comparison operator *x*. The comparator is passed two data + * elements from the data array, a and b; it returns a negative value if a is + * less than b, a positive value if a is greater than b, and zero if a equals + * b. + */ + pie.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return pie; + }; + + /** + * Specifies the overall start angle of the pie chart. Defaults to 0. The + * start angle can be specified either as a constant or as a function; in the + * case of a function, it is evaluated once per array (as opposed to per + * element). + */ + pie.startAngle = function(x) { + if (!arguments.length) return startAngle; + startAngle = x; + return pie; + }; + + /** + * Specifies the overall end angle of the pie chart. Defaults to 2π. The + * end angle can be specified either as a constant or as a function; in the + * case of a function, it is evaluated once per array (as opposed to per + * element). + */ + pie.endAngle = function(x) { + if (!arguments.length) return endAngle; + endAngle = x; + return pie; + }; + + return pie; +}; +// data is two-dimensional array of x,y; we populate y0 +d3.layout.stack = function() { + var values = Object, + order = d3_layout_stackOrders["default"], + offset = d3_layout_stackOffsets["zero"], + out = d3_layout_stackOut, + x = d3_layout_stackX, + y = d3_layout_stackY; + + function stack(data, index) { + + // Convert series to canonical two-dimensional representation. + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + + // Convert each series to canonical [[x,y]] representation. + var points = series.map(function(d, i) { + return d.map(function(v, i) { + return [x.call(stack, v, i), y.call(stack, v, i)]; + }); + }); + + // Compute the order of series, and permute them. + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + + // Compute the baseline… + var offsets = offset.call(stack, points, index); + + // And propagate it to other series. + var n = series.length, + m = series[0].length, + i, + j, + o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + + return data; + } + + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders[x]; + return stack; + }; + + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets[x]; + return stack; + }; + + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + + return stack; +} + +function d3_layout_stackX(d) { + return d.x; +} + +function d3_layout_stackY(d) { + return d.y; +} + +function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; +} + +var d3_layout_stackOrders = { + + "inside-out": function(data) { + var n = data.length, + i, + j, + max = data.map(d3_layout_stackMaxIndex), + sums = data.map(d3_layout_stackReduceSum), + index = d3.range(n).sort(function(a, b) { return max[a] - max[b]; }), + top = 0, + bottom = 0, + tops = [], + bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + + "reverse": function(data) { + return d3.range(data.length).reverse(); + }, + + "default": function(data) { + return d3.range(data.length); + } + +}; + +var d3_layout_stackOffsets = { + + "silhouette": function(data) { + var n = data.length, + m = data[0].length, + sums = [], + max = 0, + i, + j, + o, + y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + + "wiggle": function(data) { + var n = data.length, + x = data[0], + m = x.length, + max = 0, + i, + j, + k, + s1, + s2, + s3, + dx, + o, + o0, + y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + + "expand": function(data) { + var n = data.length, + m = data[0].length, + k = 1 / n, + i, + j, + o, + y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; + else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + + "zero": function(data) { + var j = -1, + m = data[0].length, + y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + +}; + +function d3_layout_stackMaxIndex(array) { + var i = 1, + j = 0, + v = array[0][1], + k, + n = array.length; + for (; i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; +} + +function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); +} + +function d3_layout_stackSum(p, d) { + return p + d[1]; +} +d3.layout.histogram = function() { + var frequency = true, + valuer = Number, + ranger = d3_layout_histogramRange, + binner = d3_layout_histogramBinSturges; + + function histogram(data, i) { + var bins = [], + values = data.map(valuer, this), + range = ranger.call(this, values, i), + thresholds = binner.call(this, range, values, i), + bin, + i = -1, + n = values.length, + m = thresholds.length - 1, + k = frequency ? 1 / n : 1, + x; + + // Initialize the bins. + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + + // Fill the bins, ignoring values outside the range. + i = -1; while(++i < n) { + x = values[i]; + if ((x >= range[0]) && (x <= range[1])) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + + return bins; + } + + // Specifies how to extract a value from the associated data. The default + // value function is `Number`, which is equivalent to the identity function. + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + + // Specifies the range of the histogram. Values outside the specified range + // will be ignored. The argument `x` may be specified either as a two-element + // array representing the minimum and maximum value of the range, or as a + // function that returns the range given the array of values and the current + // index `i`. The default range is the extent (minimum and maximum) of the + // values. + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3.functor(x); + return histogram; + }; + + // Specifies how to bin values in the histogram. The argument `x` may be + // specified as a number, in which case the range of values will be split + // uniformly into the given number of bins. Or, `x` may be an array of + // threshold values, defining the bins; the specified array must contain the + // rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x` + // may be a function which is evaluated, being passed the range, the array of + // values, and the current index `i`, returning an array of thresholds. The + // default bin function will divide the values into uniform bins using + // Sturges' formula. + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" + ? function(range) { return d3_layout_histogramBinFixed(range, x); } + : d3.functor(x); + return histogram; + }; + + // Specifies whether the histogram's `y` value is a count (frequency) or a + // probability (density). The default value is true. + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + + return histogram; +}; + +function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); +} + +function d3_layout_histogramBinFixed(range, n) { + var x = -1, + b = +range[0], + m = (range[1] - b) / n, + f = []; + while (++x <= n) f[x] = m * x + b; + return f; +} + +function d3_layout_histogramRange(values) { + return [d3.min(values), d3.max(values)]; +} +d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, + children = d3_layout_hierarchyChildren, + value = d3_layout_hierarchyValue; + + // Recursively compute the node depth and value. + // Also converts the data representation into a standard hierarchy structure. + function recurse(data, depth, nodes) { + var datas = children.call(hierarchy, data, depth), + node = {depth: depth, data: data}; + nodes.push(node); + if (datas) { + var i = -1, + n = datas.length, + c = node.children = [], + v = 0, + j = depth + 1; + while (++i < n) { + d = recurse(datas[i], j, nodes); + d.parent = node; + c.push(d); + v += d.value; + } + if (sort) c.sort(sort); + if (value) node.value = v; + } else if (value) { + node.value = value.call(hierarchy, data, depth); + } + return node; + } + + // Recursively re-evaluates the node value. + function revalue(node, depth) { + var children = node.children, + v = 0; + if (children) { + var i = -1, + n = children.length, + j = depth + 1; + while (++i < n) v += revalue(children[i], j); + } else if (value) { + v = value.call(hierarchy, node.data, depth); + } + if (value) node.value = v; + return v; + } + + function hierarchy(d) { + var nodes = []; + recurse(d, 0, nodes); + return nodes; + } + + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + + // Re-evaluates the `value` property for the specified hierarchy. + hierarchy.revalue = function(root) { + revalue(root, 0); + return root; + }; + + return hierarchy; +} + +// A method assignment helper for hierarchy subclasses. +function d3_layout_hierarchyRebind(object, hierarchy) { + object.sort = d3.rebind(object, hierarchy.sort); + object.children = d3.rebind(object, hierarchy.children); + object.links = d3_layout_hierarchyLinks; + object.value = d3.rebind(object, hierarchy.value); + return object; +} + +function d3_layout_hierarchyChildren(d) { + return d.children; +} + +function d3_layout_hierarchyValue(d) { + return d.value; +} + +function d3_layout_hierarchySort(a, b) { + return b.value - a.value; +} + +// Returns an array source+target objects for the specified nodes. +function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return {source: parent, target: child}; + }); + })); +} +d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), + size = [1, 1]; + + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), + root = nodes[0]; + + // Recursively compute the layout. + root.x = 0; + root.y = 0; + d3_layout_packTree(root); + + // Scale the layout to fit the requested size. + var w = size[0], + h = size[1], + k = 1 / Math.max(2 * root.r / w, 2 * root.r / h); + d3_layout_packTransform(root, w / 2, h / 2, k); + + return nodes; + } + + pack.size = function(x) { + if (!arguments.length) return size; + size = x; + return pack; + }; + + return d3_layout_hierarchyRebind(pack, hierarchy); +}; + +function d3_layout_packSort(a, b) { + return a.value - b.value; +} + +function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; +} + +function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; +} + +function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, + dy = b.y - a.y, + dr = a.r + b.r; + return (dr * dr - dx * dx - dy * dy) > .001; // within epsilon +} + +function d3_layout_packCircle(nodes) { + var xMin = Infinity, + xMax = -Infinity, + yMin = Infinity, + yMax = -Infinity, + n = nodes.length, + a, b, c, j, k; + + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + + // Create node links. + nodes.forEach(d3_layout_packLink); + + // Create first node. + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + + // Create second node. + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + + // Create third node and build chain. + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + + // Now iterate through the rest. + for (var i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + + // Search for the closest intersection. + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + if (s2 < s1) { + isect = -1; + j = k; + } + break; + } + } + } + + // Update node chain. + if (isect == 0) { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } else if (isect > 0) { + d3_layout_packSplice(a, j); + b = j; + i--; + } else { // isect < 0 + d3_layout_packSplice(j, b); + a = j; + i--; + } + } + } + } + + // Re-center the circles and return the encompassing radius. + var cx = (xMin + xMax) / 2, + cy = (yMin + yMax) / 2, + cr = 0; + for (var i = 0; i < n; i++) { + var node = nodes[i]; + node.x -= cx; + node.y -= cy; + cr = Math.max(cr, node.r + Math.sqrt(node.x * node.x + node.y * node.y)); + } + + // Remove node links. + nodes.forEach(d3_layout_packUnlink); + + return cr; +} + +function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; +} + +function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; +} + +function d3_layout_packTree(node) { + var children = node.children; + if (children) { + children.forEach(d3_layout_packTree); + node.r = d3_layout_packCircle(children); + } else { + node.r = Math.sqrt(node.value); + } +} + +function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = (x += k * node.x); + node.y = (y += k * node.y); + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } +} + +function d3_layout_packPlace(a, b, c) { + var da = b.r + c.r, + db = a.r + c.r, + dx = b.x - a.x, + dy = b.y - a.y, + dc = Math.sqrt(dx * dx + dy * dy), + cos = (db * db + dc * dc - da * da) / (2 * db * dc), + theta = Math.acos(cos), + x = cos * db, + h = Math.sin(theta) * db; + dx /= dc; + dy /= dc; + c.x = a.x + x * dx + h * dy; + c.y = a.y + x * dy - h * dx; +} +// Implements a hierarchical layout using the cluster (or dendogram) algorithm. +d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), + separation = d3_layout_treeSeparation, + size = [1, 1]; // width, height + + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), + root = nodes[0], + previousNode, + x = 0, + kx, + ky; + + // First walk, computing the initial x & y values. + d3_layout_treeVisitAfter(root, function(node) { + if (node.children) { + node.x = d3_layout_clusterX(node.children); + node.y = d3_layout_clusterY(node.children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + + // Compute the left-most, right-most, and depth-most nodes for extents. + var left = d3_layout_clusterLeft(root), + right = d3_layout_clusterRight(root), + x0 = left.x - separation(left, right) / 2, + x1 = right.x + separation(right, left) / 2; + + // Second walk, normalizing x & y to the desired size. + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - node.y / root.y) * size[1]; + }); + + return nodes; + } + + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + + cluster.size = function(x) { + if (!arguments.length) return size; + size = x; + return cluster; + }; + + return d3_layout_hierarchyRebind(cluster, hierarchy); +}; + +function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); +} + +function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; +} + +function d3_layout_clusterLeft(node) { + var children = node.children; + return children ? d3_layout_clusterLeft(children[0]) : node; +} + +function d3_layout_clusterRight(node) { + var children = node.children; + return children ? d3_layout_clusterRight(children[children.length - 1]) : node; +} +// Node-link tree diagram using the Reingold-Tilford "tidy" algorithm +d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), + separation = d3_layout_treeSeparation, + size = [1, 1]; // width, height + + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), + root = nodes[0]; + + function firstWalk(node, previousSibling) { + var children = node.children, + layout = node._tree; + if (children) { + var n = children.length, + firstChild = children[0], + previousChild, + ancestor = firstChild, + child, + i = -1; + while (++i < n) { + child = children[i]; + firstWalk(child, previousChild); + ancestor = apportion(child, previousChild, ancestor); + previousChild = child; + } + d3_layout_treeShift(node); + var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + layout.mod = layout.prelim - midpoint; + } else { + layout.prelim = midpoint; + } + } else { + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + } + } + } + + function secondWalk(node, x) { + node.x = node._tree.prelim + x; + var children = node.children; + if (children) { + var i = -1, + n = children.length; + x += node._tree.mod; + while (++i < n) { + secondWalk(children[i], x); + } + } + } + + function apportion(node, previousSibling, ancestor) { + if (previousSibling) { + var vip = node, + vop = node, + vim = previousSibling, + vom = node.parent.children[0], + sip = vip._tree.mod, + sop = vop._tree.mod, + sim = vim._tree.mod, + som = vom._tree.mod, + shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop._tree.ancestor = node; + shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); + sip += shift; + sop += shift; + } + sim += vim._tree.mod; + sip += vip._tree.mod; + som += vom._tree.mod; + sop += vop._tree.mod; + } + if (vim && !d3_layout_treeRight(vop)) { + vop._tree.thread = vim; + vop._tree.mod += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom._tree.thread = vip; + vom._tree.mod += sip - som; + ancestor = node; + } + } + return ancestor; + } + + // Initialize temporary layout variables. + d3_layout_treeVisitAfter(root, function(node, previousSibling) { + node._tree = { + ancestor: node, + prelim: 0, + mod: 0, + change: 0, + shift: 0, + number: previousSibling ? previousSibling._tree.number + 1 : 0 + }; + }); + + // Compute the layout using Buchheim et al.'s algorithm. + firstWalk(root); + secondWalk(root, -root._tree.prelim); + + // Compute the left-most, right-most, and depth-most nodes for extents. + var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), + right = d3_layout_treeSearch(root, d3_layout_treeRightmost), + deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), + x0 = left.x - separation(left, right) / 2, + x1 = right.x + separation(right, left) / 2, + y1 = deep.depth || 1; + + // Clear temporary layout variables; transform x and y. + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = node.depth / y1 * size[1]; + delete node._tree; + }); + + return nodes; + } + + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + + tree.size = function(x) { + if (!arguments.length) return size; + size = x; + return tree; + }; + + return d3_layout_hierarchyRebind(tree, hierarchy); +}; + +function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; +} + +// function d3_layout_treeSeparationRadial(a, b) { +// return (a.parent == b.parent ? 1 : 2) / a.depth; +// } + +function d3_layout_treeLeft(node) { + return node.children ? node.children[0] : node._tree.thread; +} + +function d3_layout_treeRight(node) { + return node.children ? node.children[node.children.length - 1] : node._tree.thread; +} + +function d3_layout_treeSearch(node, compare) { + var children = node.children; + if (children) { + var child, + n = children.length, + i = -1; + while (++i < n) { + if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { + node = child; + } + } + } + return node; +} + +function d3_layout_treeRightmost(a, b) { + return a.x - b.x; +} + +function d3_layout_treeLeftmost(a, b) { + return b.x - a.x; +} + +function d3_layout_treeDeepest(a, b) { + return a.depth - b.depth; +} + +function d3_layout_treeVisitAfter(node, callback) { + function visit(node, previousSibling) { + var children = node.children; + if (children) { + var child, + previousChild = null, + i = -1, + n = children.length; + while (++i < n) { + child = children[i]; + visit(child, previousChild); + previousChild = child; + } + } + callback(node, previousSibling); + } + visit(node, null); +} + +function d3_layout_treeShift(node) { + var shift = 0, + change = 0, + children = node.children, + i = children.length, + child; + while (--i >= 0) { + child = children[i]._tree; + child.prelim += shift; + child.mod += shift; + shift += child.shift + (change += child.change); + } +} + +function d3_layout_treeMove(ancestor, node, shift) { + ancestor = ancestor._tree; + node = node._tree; + var change = shift / (node.number - ancestor.number); + ancestor.change += change; + node.change -= change; + node.shift += shift; + node.prelim += shift; + node.mod += shift; +} + +function d3_layout_treeAncestor(vim, node, ancestor) { + return vim._tree.ancestor.parent == node.parent + ? vim._tree.ancestor + : ancestor; +} +// Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk +// Modified to support a target aspect ratio by Jeff Heer +d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), + round = Math.round, + size = [1, 1], // width, height + sticky = false, + stickies, + ratio = 0.5 * (1 + Math.sqrt(5)); // golden ratio + + // Recursively compute the node area based on value & scale. + function scale(node, k) { + var children = node.children; + node.area = node.value * k; + if (children) { + var i = -1, + n = children.length; + while (++i < n) scale(children[i], k); + } + } + + // Recursively arranges the specified node's children into squarified rows. + function squarify(node) { + if (!node.children) return; + var rect = {x: node.x, y: node.y, dx: node.dx, dy: node.dy}, + row = [], + children = node.children.slice(), // copy-on-write + child, + best = Infinity, // the best row score so far + score, // the current row score + u = Math.min(rect.dx, rect.dy), // initial orientation + n; + row.area = 0; + while ((n = children.length) > 0) { + row.push(child = children[n - 1]); + row.area += child.area; + if ((score = worst(row, u)) <= best) { // continue with this orientation + children.pop(); + best = score; + } else { // abort, and try a different orientation + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + node.children.forEach(squarify); + } + + // Recursively resizes the specified node's children into existing rows. + // Preserves the existing layout! + function stickify(node) { + if (!node.children) return; + var rect = {x: node.x, y: node.y, dx: node.dx, dy: node.dy}, + children = node.children.slice(), // copy-on-write + child, + row = []; + row.area = 0; + while (child = children.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !children.length); + row.length = row.area = 0; + } + } + node.children.forEach(stickify); + } + + // Computes the score for the specified row, as the worst aspect ratio. + function worst(row, u) { + var s = row.area, + r, + rmax = 0, + rmin = Infinity, + i = -1, + n = row.length; + while (++i < n) { + r = row[i].area; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return Math.max((u * rmax * ratio) / s, s / (u * rmin * ratio)); + } + + // Positions the specified row of nodes. Modifies `rect`. + function position(row, u, rect, flush) { + var i = -1, + n = row.length, + x = rect.x, + y = rect.y, + v = u ? round(row.area / u) : 0, + o; + if (u == rect.dx) { // horizontal subdivision + if (flush || v > rect.dy) v = rect.dy; // over+underflow + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = round(o.area / v); + } + o.z = true; + o.dx += rect.x + rect.dx - x; // rounding error + rect.y += v; + rect.dy -= v; + } else { // vertical subdivision + if (flush || v > rect.dx) v = rect.dx; // over+underflow + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = round(o.area / v); + } + o.z = false; + o.dy += rect.y + rect.dy - y; // rounding error + rect.x += v; + rect.dx -= v; + } + } + + function treemap(d) { + var nodes = stickies || hierarchy(d), + root = nodes[0]; + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + if (stickies) hierarchy.revalue(root); + scale(root, size[0] * size[1] / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + + return d3_layout_hierarchyRebind(treemap, hierarchy); +}; +})(); diff --git a/media/js/collusion/demo.js b/media/js/collusion/demo.js new file mode 100644 index 0000000000..57d05a25b7 --- /dev/null +++ b/media/js/collusion/demo.js @@ -0,0 +1,87 @@ +var Demo = (function() { + function show(graph) { + function findPageLoadIntervals(json, requestReferrer) { + var requests = []; + + if (typeof(requestRefferer) == "string") + requestReferrer = [requestReferrer]; + + for (var domain in json) + for (var referrer in json[domain].referrers) { + var time = json[domain].referrers[referrer][0]; + if (requestReferrer.indexOf(referrer) != -1) { + requests.push(time); + } + } + + var uniqueRequests = []; + requests.forEach(function(time) { + if (uniqueRequests.indexOf(time) == -1) + uniqueRequests.push(time); + }); + + return uniqueRequests.sort().reverse(); + } + + function getJsonAtTime(json, maxTime) { + var filtered = {}; + + for (var domain in json) { + filtered[domain] = {referrers: {}}; + for (var referrer in json[domain].referrers) { + var time = json[domain].referrers[referrer][0]; + if (time <= maxTime) + filtered[domain].referrers[referrer] = json[domain].referrers[referrer]; + } + } + + return filtered; + } + + jQuery.getJSON("/media/js/collusion/sample-tracking-info.json", function(json) { + $(".demo").find(".step").hide(); + $(".demo").show(); + $(".demo").find(".step.0").fadeIn(); + + var step = 0; + var DOMAINS = [ + "imdb.com", + "nytimes.com", + ["huffingtonpost.com", "atwola.com"], + "gamespot.com", + "reference.com" + ]; + + function showNextStep() { + $(".exposition").slideUp(); + $(".demo").find(".step." + step).fadeOut(function() { + var times = findPageLoadIntervals(json, DOMAINS[step]); + var nextTime = times.pop(); + var virtualTime = 0; + + function triggerNextRequest() { + virtualTime = nextTime; + graph.update(getJsonAtTime(json, virtualTime)); + if (times.length) { + nextTime = times.pop(); + setTimeout(triggerNextRequest, nextTime - virtualTime); + } else + $(".demo").find(".step." + step).fadeIn(); + } + + triggerNextRequest(); + + step++; + }); + } + + $(".demo").find(".next").click(showNextStep); + }); + } + + var Demo = { + show: show + }; + + return Demo; +})(); diff --git a/media/js/collusion/graphrunner.js b/media/js/collusion/graphrunner.js new file mode 100644 index 0000000000..40098093d1 --- /dev/null +++ b/media/js/collusion/graphrunner.js @@ -0,0 +1,455 @@ +var GraphRunner = (function(jQuery, d3) { + /* Keep track of whether we're dragging or not, so we can + * ignore mousover/mouseout events when a drag is in progress:*/ + var isNodeBeingDragged = false; + window.addEventListener("mousedown", function(e) { + if ($(e.target).closest("g.node").length) + isNodeBeingDragged = true; + }, true); + window.addEventListener("mouseup", function(e) { + isNodeBeingDragged = false; + }, true); + + function Runner(options) { + var trackers = options.trackers; + var SVG_WIDTH = options.width; + var SVG_HEIGHT = options.height; + var hideFavicons = options.hideFavicons; + + // Create the SVG element and populate it with some basic definitions + // LONGTERM TODO: Since this is static markup, move it to index.html? + var vis = d3.select("#chart") + .append("svg:svg") + .attr("width", SVG_WIDTH) + .attr("height", SVG_HEIGHT); + + var defs = vis.append("svg:defs"); + defs.append("svg:marker") + .attr("id", "Triangle") + .attr("viewBox", "0 0 10 10") + .attr("refX", 30) + .attr("refY", 5) + .attr("markerUnits", "strokeWidth") + .attr("markerWidth", 4*2) + .attr("markerHeight", 3*2) + .attr("orient", "auto") + .append("svg:path") + .attr("d", "M 0 0 L 10 5 L 0 10 z"); + + var gradient = defs.append("svg:radialGradient") + .attr("id", "glow-gradient") + .attr("cx", "50%") + .attr("cy", "50%") + .attr("r", "50%") + .attr("fx", "50%") + .attr("fy", "50%"); + + gradient.append("svg:stop") + .attr("offset", "0%") + .attr("style", "stop-color:rgb(200, 240, 255);stop-opacity:1"); + + gradient.append("svg:stop") + .attr("offset", "100%") + .attr("style", "stop-color:rgb(0,0,0);stop-opacity:0"); + + vis.append("svg:g").attr("class", "links"); + vis.append("svg:g").attr("class", "nodes"); + + // label goes on the top above the links and nodes + vis.append("svg:path").attr("id", "domain-label"); + vis.append("svg:text").attr("id", "domain-label-text"); + + function setDomainLink(target, d) { + target.removeClass("tracker").removeClass("site"); + if (d.trackerInfo) { + var TRACKER_INFO = "http://www.privacychoice.org/companies/index/"; + var trackerId = d.trackerInfo.network_id; + target.attr("href", TRACKER_INFO + trackerId); + target.addClass("tracker"); + } else { + target.attr("href", "http://" + d.name); + target.addClass("site"); + } + } + + function showDomainInfo(d) { + var className = d.name.replace(/\./g, '-dot-'); + var info = $("#domain-infos").find("." + className); + + $("#domain-infos .info").hide(); + + // TODO Why do we clone the div instead of just clearing the one and adding to it? + // Oh, I see, we create a clone for each domain and then re-use it if it's already + // created. An optimization? + if (!info.length) { + info = $("#templates .info").clone(); + info.addClass(className); + info.find(".domain").text(d.name); + var img = $(''); + if (d.trackerInfo) { + var TRACKER_LOGO = "http://images.privacychoice.org/images/network/"; + var trackerId = d.trackerInfo.network_id; + info.find("h2.domain").empty(); + img.attr("src", TRACKER_LOGO + trackerId + ".jpg").addClass("tracker"); + } else + img.attr("src", 'http://' + d.name + '/favicon.ico') + .addClass("favicon"); + setDomainLink(info.find("a.domain"), d); + info.find("h2.domain").prepend(img); + img.error(function() { img.remove(); }); + $("#domain-infos").append(info); + } + + // List referrers, if any (sites that set cookies read by this site) + var referrers = info.find(".referrers"); + var domains = findReferringDomains(d); + if (domains.length) { + var list = referrers.find("ul"); + list.empty(); + domains.forEach(function(d) { + var item = $('
  • '); + setDomainLink(item.find("a").text(d.name), d); + list.append(item); + }); + referrers.show(); + } else { + referrers.hide(); + } + + // List referees, if any (sites that read cookies set by this site) + var referrees = info.find(".referrees"); + domains = []; + vis.selectAll("line.from-" + d.index).each(function(e) { + domains.push(e.target); + }); + if (domains.length) { + var list = referrees.find("ul"); + list.empty(); + domains.forEach(function(d) { + var item = $('
  • '); + setDomainLink(item.find("a").text(d.name), d); + list.append(item); + }); + referrees.show(); + } else { + referrees.hide(); + } + + info.show(); + } + + function createNodes(nodes, force) { + + /* Represent each site as a node consisting of an svg group + * containing a and an , where the image shows + * the favicon; circle size shows number of links, color shows + * type of site. */ + + function getReferringLinkCount(d) { + return selectReferringLinks(d)[0].length; + } + + function radius(d) { + var added = getReferringLinkCount(d) / 3; + if (added > 7) + added = 7; + return 4 + added; + } + + function selectArcs(d) { + return vis.selectAll("line.to-" + d.index + + ",line.from-" + d.index); + } + + function getClassForSite(d) { + if (d.wasVisited) { + return "visited"; + } + if (d.trackerInfo) { + return "tracker"; + } else { + return "site"; + } + } + + function showPopupLabel(d) { + /* Show popup label to display domain name next to the circle. + * The popup label is defined as a path so that it can be shaped not to overlap its circle + * Cutout circle on left end, rounded right end, length dependent on length of text. + * Get ready for some crazy math and string composition! */ + var r = 12; // radius of circles + var pathStartX = d.x + r; + var pathStartY = d.y - 4; + var labelWidth = d.name.length * 7; + var reverseWidth = 0 - labelWidth - r; + d3.select("#domain-label").classed("hidden", false) + .attr("d", "M " + pathStartX + " " + pathStartY + " l " + labelWidth + " 0 " + + "a 8 8 0 0 1 0 16 l " + reverseWidth + " 0 a 12 12 0 0 0 12 -12") + .attr("class", "round-border " + getClassForSite(d)); + d3.select("#domain-label-text").classed("hidden", false) + .attr("x", d.x + 16) + .attr("y", d.y + 7) + .text(d.name); + /* TODO label width and text offset determined by trial-and-error + * and will not necessarily be correct with different font sizes.*/ + } + + function getConnectedDomains(d) { + var connectedDomains = [d.name]; + findReferringDomains(d).forEach( function(e) { + connectedDomains.push(e.name); + }); + vis.selectAll("line.from-" + d.index).each(function(e) { + connectedDomains.push(e.target.name); + }); + + return connectedDomains; + } + + var node = vis.select("g.nodes").selectAll("g.node") + .data(nodes); + + node.transition() + .duration(1000) + .attr("r", radius); + + // For each node, create svg group to hold circle, image, and title + var gs = node.enter().append("svg:g") + .attr("class", "node") + .attr("transform", function(d) { + // doesn't take x or y attributes but it can be positioned with a transformation + return "translate(" + d.x + "," + d.y + ")"; + }) + .on("mouseover", function(d) { + if (isNodeBeingDragged) + return; + /* Hide all lines except the ones going in or out of this node; + * make those ones bold and show the triangles on the ends */ + vis.selectAll("line").classed("hidden", true); + selectArcs(d).attr("marker-end", "url(#Triangle)") + .classed("hidden", false).classed("bold", true); + showDomainInfo(d); + showPopupLabel(d); + + // Make directly-connected nodes opaque, the rest translucent: + var subGraph = getConnectedDomains(d); + d3.selectAll("g.node").classed("unrelated-domain", function(d) { + return (subGraph.indexOf(d.name) == -1); + }); + }) + .on("mouseout", function(d) { + vis.selectAll("line").classed("hidden", false); + selectArcs(d).attr("marker-end", null).classed("bold", false); + d3.selectAll("g.node").classed("unrelated-domain", false); + d3.select("#domain-label").classed("hidden", true); + d3.select("#domain-label-text").classed("hidden", true); + }) + .call(force.drag); + + + // glow if site is visited + gs.append("svg:circle") + .attr("cx", "0") + .attr("cy", "0") + .attr("r", "30") + .attr("class", "glow") + .attr("fill", "url(#glow-gradient)") + .classed("hidden", function(d) { + return !d.wasVisited; + }); + + gs.append("svg:circle") + .attr("cx", "0") + .attr("cy", "0") + .attr("r", 12) // was radius + .attr("class", function(d) { + return "node round-border " + getClassForSite(d); + }); + + if (!hideFavicons) { + // If hiding favicons ("TED mode"), show initial letter of domain instead of favicon + gs.append("svg:image") + .attr("class", "node") + .attr("width", "16") + .attr("height", "16") + .attr("x", "-8") // offset to make 16x16 favicon appear centered + .attr("y", "-8") + .attr("xlink:href", function(d) {return 'http://' + d.name + '/favicon.ico'; } ); + } + + return node; + } + + function createLinks(links) { + var link = vis.select("g.links").selectAll("line.link") + .data(links) + .enter().append("svg:line") + .attr("class", function(d) { return "link from-" + d.source.index + + " to-" + d.target.index; }) + .style("stroke-width", 1) + .attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + + return link; + } + + function draw(json) { + var force = d3.layout.force() + .charge(-500) + .distance(120) + .friction(0) + .nodes(json.nodes) + .links(json.links) + .size([SVG_WIDTH, SVG_HEIGHT]) + .start(); + + createLinks(json.links); + createNodes(json.nodes, force); + + vis.style("opacity", 1e-6) + .transition() + .duration(1000) + .style("opacity", 1); + + force.on("tick", function() { + vis.selectAll("line.link").attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + + vis.selectAll("g.node").attr("transform", function(d) { + return "translate(" + d.x + "," + d.y + ")"; + }); + }); + + return { + vis: vis, + force: force + }; + } + + function selectReferringLinks(d) { + return vis.selectAll("line.to-" + d.index); + } + + function findReferringDomains(d, list, domain) { + if (!list) { + list = []; + domain = d.name; + } + + selectReferringLinks(d).each(function(d) { + if (list.indexOf(d.source) == -1 && + d.source.name != domain) { + list.push(d.source); + findReferringDomains(d.source, list, domain); + } + }); + + return list; + } + + function CollusionGraph(trackers) { + var nodes = []; + var links = []; + var domainIds = {}; + + function getNodeId(domain) { + if (!(domain in domainIds)) { + domainIds[domain] = nodes.length; + var trackerInfo = null; + for (var i = 0; i < trackers.length; i++) + if (trackers[i].domain == domain) { + trackerInfo = trackers[i]; + break; + } + nodes.push({ + name: domain, + trackerInfo: trackerInfo + }); + } + return domainIds[domain]; + } + + function addLink(options) { + var fromId = getNodeId(options.from); + var toId = getNodeId(options.to); + var link = vis.select("line.to-" + toId + ".from-" + fromId); + if (!link[0][0]) + links.push({source: fromId, target: toId}); + } + + var drawing = draw({nodes: nodes, links: links}); + + return { + data: null, + update: function(json) { + this.data = json; + drawing.force.stop(); + + for (var domain in json) + for (var referrer in json[domain].referrers) + addLink({from: referrer, to: domain}); + for (var n = 0; n < nodes.length; n++) { + if (json[nodes[n].name]) { + nodes[n].wasVisited = json[nodes[n].name].visited; + } else { + nodes[n].wasVisited = false; + } + + /* For nodes that don't already have a position, initialize them near the center. + * This way the graph will start from center. If it already has a position, leave it. + * Note that initializing them all exactly at center causes there to be zero distance, + * which makes the repulsive force explode!! So add some random factor. */ + if (typeof nodes[n].x == "undefined") { + nodes[n].x = nodes[n].px = SVG_WIDTH / 2 + Math.floor( Math.random() * 50 ) ; + nodes[n].y = nodes[n].py = SVG_HEIGHT / 2 + Math.floor( Math.random() * 50 ); + } + } + + drawing.force.nodes(nodes); + drawing.force.links(links); + drawing.force.start(); + createLinks(links); + createNodes(nodes, drawing.force); + } + }; + } + + function makeBufferedGraphUpdate(graph) { + var timeoutID = null; + + return function(json) { + if (timeoutID !== null) + clearTimeout(timeoutID); + timeoutID = setTimeout(function() { + timeoutID = null; + + // This is for debugging purposes only! + self.lastJSON = json; + + graph.update(json); + }, 250); + }; + } + + var graph = CollusionGraph(trackers); + + var self = { + graph: graph, + width: SVG_WIDTH, + height: SVG_HEIGHT, + updateGraph: makeBufferedGraphUpdate(graph) + }; + + return self; + } + + var GraphRunner = { + Runner: Runner + }; + + return GraphRunner; +})(jQuery, d3); diff --git a/media/js/collusion/sample-tracking-info.json b/media/js/collusion/sample-tracking-info.json new file mode 100644 index 0000000000..9d05b3d1b2 --- /dev/null +++ b/media/js/collusion/sample-tracking-info.json @@ -0,0 +1,781 @@ + + + + + + + + + website/sample-tracking-info.json at master from rossbruniges/collusion - GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +

    + / + collusion + + forked from toolness/collusion + +

    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +

    Latest commit to the master branch

    + +
    +

    + New assets and linking to the AMO add-on + +

    +
    + commit 4623f7ca52 + + +
    + + rossbruniges + authored + +
    +
    +
    + + + +
    + + + + + +
    +
    + +
    +
    +
    +
    + Txt + 100644 + 44 lines (23 sloc) + 2.243 kb +
    + +
    +
    + + + + + +
    +
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +
    +
    +
    {

    "imdb.com": {"visited": "true", "referrers": {}},

    "nytimes.com": {"visited": "true", "referrers": {}},

    "huffingtonpost.com": {"visited": "true", "referrers": {}},

    "gamespot.com": {"visited": "true", "referrers": {}},

    "scorecardresearch.com":{"referrers": {"imdb.com":[16876,null],"nytimes.com":[217723,"application/x-javascript",null],"huffingtonpost.com":[261056,null],"gamespot.com":[350881,null]}},

    "doubleclick.net":{"referrers":{"imdb.com":[16966,"application/x-javascript"],"nytimes.com":[212628,"text/html","text/javascript; charset=UTF-8"],"huffingtonpost.com":[261180,"text/html","application/x-javascript"],"gamespot.com":[347704,"text/html"],"reference.com":[406558,"application/x-javascript"]}},

    "atdmt.com":{"referrers":{"imdb.com":[17671,"text/html"]}},

    "checkm8.com":{"referrers":{"nytimes.com":[214265,"text/html","application/javascript"]}},

    "imrworldwide.com":{"referrers":{"nytimes.com":[217811,"text/html","image/gif"],"huffingtonpost.com":[261172,"image/gif"],"gamespot.com":[351053,"image/gif"]}},

    "quantserve.com":{"referrers":{"huffingtonpost.com":[257567,"application/x-javascript",null],"reference.com":[405447,"application/x-javascript"]}},

    "atwola.com":{"referrers":{"huffingtonpost.com":[258588,null,"application/x-javascript","text/html"],"adsonar.com":[260039,"text/html"]}},

    "aol.com":{"referrers":{"huffingtonpost.com":[259239,"text/plain","image/gif","text/html; charset=iso-8859-1"]}},

    "questionmarket.com":{"referrers":{"huffingtonpost.com":[261796,"text/html"]}},

    "advertising.com":{"referrers":{"atwola.com":[263125,"image/gif"]}},

    "com.com":{"referrers":{"gamespot.com":[350513,"text/plain","image/gif"]}},

    "bluekai.com":{"referrers":{"gamespot.com":[351413,"text/html","text/javascript"]}},

    "revsci.net":{"referrers":{"gamespot.com":[351492,"application/javascript;charset=UTF-8"]}},

    "dictionary.com":{"referrers":{"reference.com":[406550,"text/html; charset=UTF-8","application/x-shockwave-flash"]}},

    "adtechus.com":{"referrers":{"reference.com":[406676,null,"application/x-javascript"]}},

    "thesaurus.com":{"referrers":{"reference.com":[407038,"text/html; charset=UTF-8"]}},

    "zedo.com":{"referrers":{"reference.com":[407460,"text/html","application/x-javascript","image/gif"]}}
    }
    +
    +
    + +
    +
    +
    +
    + +
    + + + +
    +
    +
    + + + + + + + + + +
    +

    Markdown Cheat Sheet

    + +
    + +
    +
    +

    Format Text

    +

    Headers

    +
    +# This is an <h1> tag
    +## This is an <h2> tag
    +###### This is an <h6> tag
    +

    Text styles

    +
    +*This text will be italic*
    +_This will also be italic_
    +**This text will be bold**
    +__This will also be bold__
    +
    +*You **can** combine them*
    +
    +
    +
    +

    Lists

    +

    Unordered

    +
    +* Item 1
    +* Item 2
    +  * Item 2a
    +  * Item 2b
    +

    Ordered

    +
    +1. Item 1
    +2. Item 2
    +3. Item 3
    +   * Item 3a
    +   * Item 3b
    +
    +
    +

    Miscellaneous

    +

    Images

    +
    +![GitHub Logo](/images/logo.png)
    +Format: ![Alt Text](url)
    +
    +

    Links

    +
    +http://github.com - automatic!
    +[GitHub](http://github.com)
    +

    Blockquotes

    +
    +As Kanye West said:
    +
    +> We're living the future so
    +> the present is our past.
    +
    +
    +
    +
    + +

    Code Examples in Markdown

    +
    +

    Syntax highlighting with GFM

    +
    +```javascript
    +function fancyAlert(arg) {
    +  if(arg) {
    +    $.facebox({div:'#foo'})
    +  }
    +}
    +```
    +
    +
    +

    Or, indent your code 4 spaces

    +
    +Here is a Python code example
    +without syntax highlighting:
    +
    +    def foo:
    +      if not bar:
    +        return true
    +
    +
    +

    Inline code for comments

    +
    +I think you should use an
    +`<addr>` element here instead.
    +
    +
    + +
    + + + +
    +

    Something went wrong with that request. Please try again. Dismiss

    +
    + + + + + + + + diff --git a/media/js/collusion/trackers.json b/media/js/collusion/trackers.json new file mode 100644 index 0000000000..6295a7e6d5 --- /dev/null +++ b/media/js/collusion/trackers.json @@ -0,0 +1,2581 @@ +[{ + "network_id": "20", + "domain": "bkrtx.com", + "block_cookies": "*" +}, { + "network_id": "20", + "domain": "bluekai.com", + "block_cookies": "*" +}, { + "network_id": "2", + "domain": "247realmedia.com", + "block_cookies": "*" +}, { + "network_id": "2", + "domain": "realmedia.com", + "block_cookies": "*" +}, { + "network_id": "10", + "domain": "imiclk.com", + "block_cookies": "*" +}, { + "network_id": "69", + "domain": "33across.com", + "block_cookies": "*" +}, { + "network_id": "3", + "domain": "acxiom.com", + "block_cookies": "*" +}, { + "network_id": "4", + "domain": "adadvisor.net", + "block_cookies": "*" +}, { + "network_id": "6", + "domain": "afy11.net", + "block_cookies": "*" +}, { + "network_id": "7", + "domain": "nspmotion.com", + "block_cookies": "*" +}, { + "network_id": "52", + "domain": "adtechus.com", + "block_cookies": "*" +}, { + "network_id": "52", + "domain": "adtech.de", + "block_cookies": "*" +}, { + "network_id": "76", + "domain": "advertising.com", + "block_cookies": "*" +}, { + "network_id": "9", + "domain": "abmr.net", + "block_cookies": "*" +}, { + "network_id": "11", + "domain": "pro-market.net", + "block_cookies": "*" +}, { + "network_id": "14", + "domain": "atdmt.com", + "block_cookies": "*" +}, { + "network_id": "16", + "domain": "revsci.net", + "block_cookies": "*" +}, { + "network_id": "17", + "domain": "bizo.com", + "block_cookies": "*" +}, { + "network_id": "17", + "domain": "bizographics.com", + "block_cookies": "*" +}, { + "network_id": "24", + "domain": "casalemedia.com", + "block_cookies": "*" +}, { + "network_id": "79", + "domain": "chitika.net", + "block_cookies": "*" +}, { + "network_id": "26", + "domain": "collective-media.net", + "block_cookies": "*" +}, { + "network_id": "27", + "domain": "coremetrics.com", + "block_cookies": "*" +}, { + "network_id": "27", + "domain": "cmcore.com", + "block_cookies": "*" +}, { + "network_id": "29", + "domain": "exelator.com", + "block_cookies": "*" +}, { + "network_id": "34", + "domain": "fimserve.com", + "block_cookies": "*" +}, { + "network_id": "35", + "domain": "fwmrm.net", + "block_cookies": "*" +}, { + "network_id": "39", + "domain": "interclick.com", + "block_cookies": "*" +}, { + "network_id": "41", + "domain": "crwdcntrl.net", + "block_cookies": "*" +}, { + "network_id": "42", + "domain": "media6degrees.com", + "block_cookies": "*" +}, { + "network_id": "43", + "domain": "mathtag.com", + "block_cookies": "*" +}, { + "network_id": "45", + "domain": "mmismm.com", + "block_cookies": "*" +}, { + "network_id": "88", + "domain": "navegg.com", + "block_cookies": "*" +}, { + "network_id": "47", + "domain": "nextag.com", + "block_cookies": "*" +}, { + "network_id": "48", + "domain": "imrworldwide.com", + "block_cookies": "*" +}, { + "network_id": "49", + "domain": "nuggad.net", + "block_cookies": "*" +}, { + "network_id": "50", + "domain": "2o7.net", + "block_cookies": "*" +}, { + "network_id": "51", + "domain": "openx.net", + "block_cookies": "*" +}, { + "network_id": "75", + "domain": "pointroll.com", + "block_cookies": "*" +}, { + "network_id": "53", + "domain": "precisionclick.com", + "block_cookies": "*" +}, { + "network_id": "84", + "domain": "kanoodle.com", + "block_cookies": "*" +}, { + "network_id": "70", + "domain": "quantserve.com", + "block_cookies": "*" +}, { + "network_id": "54", + "domain": "adsonar.com", + "block_cookies": "*" +}, { + "network_id": "78", + "domain": "yieldmanager.com", + "block_cookies": "*" +}, { + "network_id": "71", + "domain": "rfihub.com", + "block_cookies": "*" +}, { + "network_id": "58", + "domain": "spotxchange.com", + "block_cookies": "*" +}, { + "network_id": "59", + "domain": "tacoda.net", + "block_cookies": "*" +}, { + "network_id": "61", + "domain": "trafficmp.com", + "block_cookies": "*" +}, { + "network_id": "62", + "domain": "tribalfusion.com", + "block_cookies": "*" +}, { + "network_id": "63", + "domain": "adlegend.com", + "block_cookies": "*" +}, { + "network_id": "66", + "domain": "webtrendslive.com", + "block_cookies": "*" +}, { + "network_id": "100", + "domain": "adinterax.com", + "block_cookies": "*" +}, { + "network_id": "1", + "domain": "ru4.com", + "block_cookies": "*" +}, { + "network_id": "14", + "domain": "adbureau.net", + "block_cookies": "*" +}, { + "network_id": "57", + "domain": "specificclick.net", + "block_cookies": "*" +}, { + "network_id": "57", + "domain": "specificmedia.com", + "block_cookies": "*" +}, { + "network_id": "76", + "domain": "atwola.com", + "block_cookies": "*" +}, { + "network_id": "65", + "domain": "apmebf.com", + "block_cookies": "*" +}, { + "network_id": "98", + "domain": "tattomedia.com", + "block_cookies": "*" +}, { + "network_id": "80", + "domain": "kontera.com", + "block_cookies": "*" +}, { + "network_id": "55", + "domain": "questionmarket.com", + "block_cookies": "*" +}, { + "network_id": "85", + "domain": "contextweb.com", + "block_cookies": "*" +}, { + "network_id": "65", + "domain": "mediaplex.com", + "block_cookies": "*" +}, { + "network_id": "110", + "domain": "eyewonder.com", + "block_cookies": "*" +}, { + "network_id": "112", + "domain": "interpolls.com", + "block_cookies": "*" +}, { + "network_id": "57", + "domain": "adviva.co.uk", + "block_cookies": "*" +}, { + "network_id": "57", + "domain": "adviva.net", + "block_cookies": "*" +}, { + "network_id": "113", + "domain": "eyeconomy.com", + "block_cookies": "*" +}, { + "network_id": "114", + "domain": "acceleratorusa.com", + "block_cookies": "*" +}, { + "network_id": "115", + "domain": "adshuffle.com", + "block_cookies": "*" +}, { + "network_id": "116", + "domain": "adperium.com", + "block_cookies": "*" +}, { + "network_id": "117", + "domain": "unanimus.co.uk", + "block_cookies": "*" +}, { + "network_id": "118", + "domain": "clicktale.com", + "block_cookies": "*" +}, { + "network_id": "118", + "domain": "clicktale.net", + "block_cookies": "*" +}, { + "network_id": "119", + "domain": "etology.com", + "block_cookies": "*" +}, { + "network_id": "120", + "domain": "insightexpress.com", + "block_cookies": "*" +}, { + "network_id": "120", + "domain": "insightexpressai.com", + "block_cookies": "*" +}, { + "network_id": "121", + "domain": "doubleverify.com", + "block_cookies": "*" +}, { + "network_id": "122", + "domain": "dotomi.com", + "block_cookies": "*" +}, { + "network_id": "123", + "domain": "gunggo.com", + "block_cookies": "*" +}, { + "network_id": "126", + "domain": "collarity.com", + "block_cookies": "*" +}, { + "network_id": "127", + "domain": "adknowledge.com", + "block_cookies": "*" +}, { + "network_id": "128", + "domain": "loomia.com", + "block_cookies": "*" +}, { + "network_id": "129", + "domain": "clicksor.com", + "block_cookies": "*" +}, { + "network_id": "14", + "domain": "aquantive.com", + "block_cookies": "*" +}, { + "network_id": "127", + "domain": "cubics.com", + "block_cookies": "*" +}, { + "network_id": "131", + "domain": "vizu.com", + "block_cookies": "*" +}, { + "network_id": "133", + "domain": "sproutbuilder.com", + "block_cookies": "*" +}, { + "network_id": "134", + "domain": "quadrantone.com", + "block_cookies": "*" +}, { + "network_id": "135", + "domain": "checkm8.com", + "block_cookies": "*" +}, { + "network_id": "136", + "domain": "vibrantmedia.com", + "block_cookies": "*" +}, { + "network_id": "136", + "domain": "intellitxt.com", + "block_cookies": "*" +}, { + "network_id": "137", + "domain": "gigya.com", + "block_cookies": "*" +}, { + "network_id": "137", + "domain": "gigya-inc.com", + "block_cookies": "*" +}, { + "network_id": "139", + "domain": "crowdscience.com", + "block_cookies": "*" +}, { + "network_id": "141", + "domain": "sharethis.com", + "block_cookies": "*" +}, { + "network_id": "143", + "domain": "ads.e-planning.net", + "block_cookies": "*" +}, { + "network_id": "143", + "domain": "ads.us.e-planning.net", + "block_cookies": "*" +}, { + "network_id": "144", + "domain": "flashtalking.com", + "block_cookies": "*" +}, { + "network_id": "145", + "domain": "weborama.com", + "block_cookies": "*" +}, { + "network_id": "145", + "domain": "weborama.fr", + "block_cookies": "*" +}, { + "network_id": "147", + "domain": "effectivemeasure.com", + "block_cookies": "*" +}, { + "network_id": "148", + "domain": "richrelevance.com", + "block_cookies": "*" +}, { + "network_id": "149", + "domain": "buysafe.com", + "block_cookies": "*" +}, { + "network_id": "150", + "domain": "iperceptions.com", + "block_cookies": "*" +}, { + "network_id": "151", + "domain": "baynote.com", + "block_cookies": "*" +}, { + "network_id": "151", + "domain": "baynote.net", + "block_cookies": "*" +}, { + "network_id": "153", + "domain": "tumri.net", + "block_cookies": "*" +}, { + "network_id": "153", + "domain": "tumri.com", + "block_cookies": "*" +}, { + "network_id": "83", + "domain": "adbrite.com", + "block_cookies": "*" +}, { + "network_id": "154", + "domain": "tradedoubler.com", + "block_cookies": "*" +}, { + "network_id": "155", + "domain": "demdex.com", + "block_cookies": "*" +}, { + "network_id": "99", + "domain": "clearspring.com", + "block_cookies": "*" +}, { + "network_id": "82", + "domain": "burstnet.com", + "block_cookies": "*" +}, { + "network_id": "104", + "domain": "clickability.com", + "block_cookies": "*" +}, { + "network_id": "68", + "domain": "undertone.com", + "block_cookies": "*" +}, { + "network_id": "62", + "domain": "exponential.com", + "block_cookies": "*" +}, { + "network_id": "64", + "domain": "turn.com", + "block_cookies": "*" +}, { + "network_id": "156", + "domain": "pubmatic.com", + "block_cookies": "*" +}, { + "network_id": "157", + "domain": "aggregateknowledge.com", + "block_cookies": "*" +}, { + "network_id": "158", + "domain": "adjuggler.com", + "block_cookies": "*" +}, { + "network_id": "93", + "domain": "videoegg.com", + "block_cookies": "*" +}, { + "network_id": "51", + "domain": "openx.org", + "block_cookies": "*" +}, { + "network_id": "78", + "domain": "yieldmanager.net", + "block_cookies": "*" +}, { + "network_id": "67", + "domain": "overture.com", + "block_cookies": "*" +}, { + "network_id": "107", + "domain": "lucidmedia.com", + "block_cookies": "*" +}, { + "network_id": "159", + "domain": "factortg.com", + "block_cookies": "*" +}, { + "network_id": "160", + "domain": "tremormedia.com", + "block_cookies": "*" +}, { + "network_id": "89", + "domain": "adap.tv", + "block_cookies": "*" +}, { + "network_id": "125", + "domain": "assoc-amazon.com", + "block_cookies": "*" +}, { + "network_id": "84", + "domain": "pulse360.com", + "block_cookies": "*" +}, { + "network_id": "163", + "domain": "admeld.com", + "block_cookies": "*" +}, { + "network_id": "16", + "domain": "targetingmarketplace.com", + "block_cookies": "*" +}, { + "network_id": "165", + "domain": "brand.net", + "block_cookies": "*" +}, { + "network_id": "166", + "domain": "optmd.com", + "block_cookies": "*" +}, { + "network_id": "92", + "domain": "blogads.com", + "block_cookies": "*" +}, { + "network_id": "101", + "domain": "adaptiveads.com", + "block_cookies": "*" +}, { + "network_id": "5", + "domain": "adcentriconline.com", + "block_cookies": "*" +}, { + "network_id": "72", + "domain": "adconion.com", + "block_cookies": "*" +}, { + "network_id": "103", + "domain": "adotube.com", + "block_cookies": "*" +}, { + "network_id": "22", + "domain": "btrll.com", + "block_cookies": "*" +}, { + "network_id": "23", + "domain": "btbuckets.com", + "block_cookies": "*" +}, { + "network_id": "146", + "domain": "clearsightinteractive.com", + "block_cookies": "*" +}, { + "network_id": "105", + "domain": "clipsyndicate.com", + "block_cookies": "*" +}, { + "network_id": "102", + "domain": "cj.com", + "block_cookies": "*" +}, { + "network_id": "95", + "domain": "congoo.com", + "block_cookies": "*" +}, { + "network_id": "94", + "domain": "datranmedia.com", + "block_cookies": "*" +}, { + "network_id": "31", + "domain": "serving-sys.com", + "block_cookies": "*" +}, { + "network_id": "31", + "domain": "eyeblaster.com", + "block_cookies": "*" +}, { + "network_id": "32", + "domain": "facilitatedigital.com", + "block_cookies": "*" +}, { + "network_id": "33", + "domain": "fetchback.com", + "block_cookies": "*" +}, { + "network_id": "38", + "domain": "hitbox.com", + "block_cookies": "*" +}, { + "network_id": "106", + "domain": "iacadvertising.com", + "block_cookies": "*" +}, { + "network_id": "152", + "domain": "mediawhiz.com", + "block_cookies": "*" +}, { + "network_id": "124", + "domain": "mpire.com", + "block_cookies": "*" +}, { + "network_id": "46", + "domain": "nextaction.net", + "block_cookies": "*" +}, { + "network_id": "46", + "domain": "nexac.com", + "block_cookies": "*" +}, { + "network_id": "108", + "domain": "popularmedia.com", + "block_cookies": "*" +}, { + "network_id": "97", + "domain": "shorttailmedia.com", + "block_cookies": "*" +}, { + "network_id": "56", + "domain": "smartadserver.com", + "block_cookies": "*" +}, { + "network_id": "81", + "domain": "spot200.com", + "block_cookies": "*" +}, { + "network_id": "130", + "domain": "tealium.com", + "block_cookies": "*" +}, { + "network_id": "87", + "domain": "yumenetworks.com", + "block_cookies": "*" +}, { + "network_id": "167", + "domain": "traffiq.com", + "block_cookies": "*" +}, { + "network_id": "168", + "domain": "scanscout.com", + "block_cookies": "*" +}, { + "network_id": "169", + "domain": "outbrain.com", + "block_cookies": "*" +}, { + "network_id": "50", + "domain": "omniture.com", + "block_cookies": "*" +}, { + "network_id": "72", + "domain": "amgdgt.com", + "block_cookies": "*" +}, { + "network_id": "170", + "domain": "sitemeter.com", + "block_cookies": "*" +}, { + "network_id": "50", + "domain": "offermatica.com", + "block_cookies": "*" +}, { + "network_id": "171", + "domain": "adperfect.com", + "block_cookies": "*" +}, { + "network_id": "124", + "domain": "widgetbucks.com", + "block_cookies": "*" +}, { + "network_id": "78", + "domain": "rmxads.com", + "block_cookies": "*" +}, { + "network_id": "172", + "domain": "cpxinteractive.com", + "block_cookies": "*" +}, { + "network_id": "174", + "domain": "channelintelligence.com", + "block_cookies": "*" +}, { + "network_id": "175", + "domain": "teracent.com", + "block_cookies": "*" +}, { + "network_id": "78", + "domain": "flingwebads.com", + "block_cookies": "*" +}, { + "network_id": "176", + "domain": "adserverplus.com", + "block_cookies": "*" +}, { + "network_id": "176", + "domain": "oridian.com", + "block_cookies": "*" +}, { + "network_id": "175", + "domain": "teracent.net", + "block_cookies": "*" +}, { + "network_id": "177", + "domain": "widgetserver.com", + "block_cookies": "*" +}, { + "network_id": "177", + "domain": "widgetbox.com", + "block_cookies": "*" +}, { + "network_id": "55", + "domain": "dl-rms.com", + "block_cookies": "*" +}, { + "network_id": "178", + "domain": "mediaforceads.com", + "block_cookies": "*" +}, { + "network_id": "179", + "domain": "addtoany.com", + "block_cookies": "*" +}, { + "network_id": "55", + "domain": "dlqm.net", + "block_cookies": "*" +}, { + "network_id": "180", + "domain": "contextuads.com", + "block_cookies": "*" +}, { + "network_id": "181", + "domain": "xgraph.net", + "block_cookies": "*" +}, { + "network_id": "32", + "domain": "adsfac.us", + "block_cookies": "*" +}, { + "network_id": "183", + "domain": "cetrk.com", + "block_cookies": "*" +}, { + "network_id": "183", + "domain": "crazyegg.com", + "block_cookies": "*" +}, { + "network_id": "184", + "domain": "gumgum.com", + "block_cookies": "*" +}, { + "network_id": "185", + "domain": "lfstmedia.com", + "block_cookies": "*" +}, { + "network_id": "186", + "domain": "adtegrity.com", + "block_cookies": "*" +}, { + "network_id": "186", + "domain": "adtegrity.net", + "block_cookies": "*" +}, { + "network_id": "187", + "domain": "adfrontiers.com", + "block_cookies": "*" +}, { + "network_id": "188", + "domain": "reinvigorate.net", + "block_cookies": "*" +}, { + "network_id": "44", + "domain": "roiservice.com", + "block_cookies": "*" +}, { + "network_id": "189", + "domain": "adnxs.com", + "block_cookies": "*" +}, { + "network_id": "190", + "domain": "bridgetrack.com", + "block_cookies": "*" +}, { + "network_id": "191", + "domain": "adroll.com", + "block_cookies": "*" +}, { + "network_id": "67", + "domain": "adrevolver.com", + "block_cookies": "*" +}, { + "network_id": "67", + "domain": "bluelithium.com", + "block_cookies": "*" +}, { + "network_id": "192", + "domain": "adonnetwork.com", + "block_cookies": "*" +}, { + "network_id": "192", + "domain": "adonnetwork.net", + "block_cookies": "*" +}, { + "network_id": "51", + "domain": "openx.com", + "block_cookies": "*" +}, { + "network_id": "82", + "domain": "burstdirectads.com", + "block_cookies": "*" +}, { + "network_id": "194", + "domain": "addkick.com", + "block_cookies": "*" +}, { + "network_id": "195", + "domain": "peer39.net", + "block_cookies": "*" +}, { + "network_id": "196", + "domain": "accelerator-media.com", + "block_cookies": "*" +}, { + "network_id": "197", + "domain": "batanganetwork.com", + "block_cookies": "*" +}, { + "network_id": "197", + "domain": "batanga.com", + "block_cookies": "*" +}, { + "network_id": "99", + "domain": "connectedads.net", + "block_cookies": "*" +}, { + "network_id": "198", + "domain": "adfusion.com", + "block_cookies": "*" +}, { + "network_id": "199", + "domain": "adparlor.com", + "block_cookies": "*" +}, { + "network_id": "200", + "domain": "connextra.com", + "block_cookies": "*" +}, { + "network_id": "102", + "domain": "qksz.com", + "block_cookies": "*" +}, { + "network_id": "70", + "domain": "quantcast.com", + "block_cookies": "*" +}, { + "network_id": "201", + "domain": "proximic.com", + "block_cookies": "*" +}, { + "network_id": "201", + "domain": "proximic.net", + "block_cookies": "*" +}, { + "network_id": "72", + "domain": "euroclick.com", + "block_cookies": "*" +}, { + "network_id": "65", + "domain": "ftjcfx.com", + "block_cookies": "*" +}, { + "network_id": "202", + "domain": "hitslink.com", + "block_cookies": "*" +}, { + "network_id": "202", + "domain": "hitsprocessor.com", + "block_cookies": "*" +}, { + "network_id": "203", + "domain": "owneriq.net", + "block_cookies": "*" +}, { + "network_id": "203", + "domain": "owneriq.com", + "block_cookies": "*" +}, { + "network_id": "204", + "domain": "criteo.com", + "block_cookies": "*" +}, { + "network_id": "205", + "domain": "permuto.com", + "block_cookies": "*" +}, { + "network_id": "205", + "domain": "pulsemgr.com", + "block_cookies": "*" +}, { + "network_id": "206", + "domain": "dataxu.com", + "block_cookies": "*" +}, { + "network_id": "206", + "domain": "w55c.net", + "block_cookies": "*" +}, { + "network_id": "207", + "domain": "scorecardresearch.com", + "block_cookies": "*" +}, { + "network_id": "208", + "domain": "dapper.net", + "block_cookies": "*" +}, { + "network_id": "65", + "domain": "yceml.net", + "block_cookies": "*" +}, { + "network_id": "211", + "domain": "fastclick.com", + "block_cookies": "*" +}, { + "network_id": "211", + "domain": "fastclick.net", + "block_cookies": "*" +}, { + "network_id": "211", + "domain": "valueclick.net", + "block_cookies": "*" +}, { + "network_id": "211", + "domain": "valueclick.com", + "block_cookies": "*" +}, { + "network_id": "212", + "domain": "struq.com", + "block_cookies": "*" +}, { + "network_id": "65", + "domain": "awltovhc.com", + "block_cookies": "*" +}, { + "network_id": "211", + "domain": "lduhtrp.net", + "block_cookies": "*" +}, { + "network_id": "82", + "domain": "burstbeacon.com", + "block_cookies": "*" +}, { + "network_id": "78", + "domain": "yldmgrimg.net", + "block_cookies": "*" +}, { + "network_id": "213", + "domain": "invitemedia.com", + "block_cookies": "*" +}, { + "network_id": "214", + "domain": "beencounter.com", + "block_cookies": "*" +}, { + "network_id": "181", + "domain": "xgraph.com", + "block_cookies": "*" +}, { + "network_id": "215", + "domain": "gorillanation.com", + "block_cookies": "*" +}, { + "network_id": "216", + "domain": "vindicosuite.com", + "block_cookies": "*" +}, { + "network_id": "217", + "domain": "netmng.com", + "block_cookies": "*" +}, { + "network_id": "217", + "domain": "netmining.com", + "block_cookies": "*" +}, { + "network_id": "152", + "domain": "adnetinteractive.com", + "block_cookies": "*" +}, { + "network_id": "102", + "domain": "tqlkg.com", + "block_cookies": "*" +}, { + "network_id": "218", + "domain": "adspeed.net", + "block_cookies": "*" +}, { + "network_id": "63", + "domain": "trueffect.com", + "block_cookies": "*" +}, { + "network_id": "219", + "domain": "traveladvertising.com", + "block_cookies": "*" +}, { + "network_id": "219", + "domain": "traveladnetwork.com", + "block_cookies": "*" +}, { + "network_id": "220", + "domain": "amadesa.com", + "block_cookies": "*" +}, { + "network_id": "158", + "domain": "adjuggler.net", + "block_cookies": "*" +}, { + "network_id": "175", + "domain": "ytsa.net", + "block_cookies": "*" +}, { + "network_id": "175", + "domain": "smtad.net", + "block_cookies": "*" +}, { + "network_id": "222", + "domain": "netshelter.net", + "block_cookies": "*" +}, { + "network_id": "223", + "domain": "adbuyer.com", + "block_cookies": "*" +}, { + "network_id": "224", + "domain": "wunderloop.net", + "block_cookies": "*" +}, { + "network_id": "225", + "domain": "everesttech.net", + "block_cookies": "*" +}, { + "network_id": "226", + "domain": "adchemy.com", + "block_cookies": "*" +}, { + "network_id": "204", + "domain": "criteo.net", + "block_cookies": "*" +}, { + "network_id": "227", + "domain": "ctasnet.com", + "block_cookies": "*" +}, { + "network_id": "227", + "domain": "crimtan.com", + "block_cookies": "*" +}, { + "network_id": "229", + "domain": "tidaltv.com", + "block_cookies": "*" +}, { + "network_id": "230", + "domain": "raasnet.com", + "block_cookies": "*" +}, { + "network_id": "230", + "domain": "redaril.com", + "block_cookies": "*" +}, { + "network_id": "232", + "domain": "rapleaf.com", + "block_cookies": "*" +}, { + "network_id": "232", + "domain": "rlcdn.com", + "block_cookies": "*" +}, { + "network_id": "234", + "domain": "eyereturn.com", + "block_cookies": "*" +}, { + "network_id": "234", + "domain": "eyereturnmarketing.com", + "block_cookies": "*" +}, { + "network_id": "235", + "domain": "netseer.com", + "block_cookies": "*" +}, { + "network_id": "236", + "domain": "halogennetwork.com", + "block_cookies": "*" +}, { + "network_id": "237", + "domain": "qnsr.com", + "block_cookies": "*" +}, { + "network_id": "237", + "domain": "quinstreet.com", + "block_cookies": "*" +}, { + "network_id": "238", + "domain": "adgear.com", + "block_cookies": "*" +}, { + "network_id": "201", + "domain": "proxilinks.com", + "block_cookies": "*" +}, { + "network_id": "239", + "domain": "mythingsmedia.com", + "block_cookies": "*" +}, { + "network_id": "239", + "domain": "mythings.com", + "block_cookies": "*" +}, { + "network_id": "240", + "domain": "bannerconnect.net", + "block_cookies": "*" +}, { + "network_id": "241", + "domain": "hurra.com", + "block_cookies": "*" +}, { + "network_id": "153", + "domain": "yt1187.net", + "block_cookies": "*" +}, { + "network_id": "242", + "domain": "snoobi.com", + "block_cookies": "*" +}, { + "network_id": "243", + "domain": "opinmind.com", + "block_cookies": "*" +}, { + "network_id": "244", + "domain": "mxptint.net", + "block_cookies": "*" +}, { + "network_id": "244", + "domain": "maxpointinteractive.com", + "block_cookies": "*" +}, { + "network_id": "245", + "domain": "esm1.net", + "block_cookies": "*" +}, { + "network_id": "245", + "domain": "echosearch.com", + "block_cookies": "*" +}, { + "network_id": "246", + "domain": "adocean.pl", + "block_cookies": "*" +}, { + "network_id": "246", + "domain": "adocean-global.com", + "block_cookies": "*" +}, { + "network_id": "247", + "domain": "tellapart.com", + "block_cookies": "*" +}, { + "network_id": "248", + "domain": "mixpo.com", + "block_cookies": "*" +}, { + "network_id": "249", + "domain": "faithadnet.com", + "block_cookies": "*" +}, { + "network_id": "250", + "domain": "retargeter.com", + "block_cookies": "*" +}, { + "network_id": "252", + "domain": "adready.com", + "block_cookies": "*" +}, { + "network_id": "253", + "domain": "xtendmedia.com", + "block_cookies": "*" +}, { + "network_id": "254", + "domain": "cpmatic.com", + "block_cookies": "*" +}, { + "network_id": "255", + "domain": "adpepper.com", + "block_cookies": "*" +}, { + "network_id": "256", + "domain": "pinnacledream.com", + "block_cookies": "*" +}, { + "network_id": "257", + "domain": "themig.com", + "block_cookies": "*" +}, { + "network_id": "258", + "domain": "domdex.com", + "block_cookies": "*" +}, { + "network_id": "258", + "domain": "qjex.net", + "block_cookies": "*" +}, { + "network_id": "257", + "domain": "mookie1.com", + "block_cookies": "*" +}, { + "network_id": "259", + "domain": "lockedonmedia.com", + "block_cookies": "*" +}, { + "network_id": "260", + "domain": "legolas-media.com", + "block_cookies": "*" +}, { + "network_id": "261", + "domain": "spongecell.com", + "block_cookies": "*" +}, { + "network_id": "262", + "domain": "httpool.com", + "block_cookies": "*" +}, { + "network_id": "263", + "domain": "didit.com", + "block_cookies": "*" +}, { + "network_id": "264", + "domain": "radiusmarketing.com", + "block_cookies": "*" +}, { + "network_id": "254", + "domain": "xa.net", + "block_cookies": "*" +}, { + "network_id": "265", + "domain": "inadco.com", + "block_cookies": "*" +}, { + "network_id": "265", + "domain": "anadcoads.com", + "block_cookies": "*" +}, { + "network_id": "266", + "domain": "lijit.com", + "block_cookies": "*" +}, { + "network_id": "267", + "domain": "bunchball.com", + "block_cookies": "*" +}, { + "network_id": "268", + "domain": "visiblemeasures.com", + "block_cookies": "*" +}, { + "network_id": "269", + "domain": "resonatenetworks.com", + "block_cookies": "*" +}, { + "network_id": "270", + "domain": "wsod.com", + "block_cookies": "*" +}, { + "network_id": "124", + "domain": "adxpose.com", + "block_cookies": "*" +}, { + "network_id": "127", + "domain": "bidsystem.com", + "block_cookies": "*" +}, { + "network_id": "271", + "domain": "triggit.com", + "block_cookies": "*" +}, { + "network_id": "243", + "domain": "yieldoptimizer.com", + "block_cookies": "*" +}, { + "network_id": "289", + "domain": "campaigngrid.com", + "block_cookies": "*" +}, { + "network_id": "290", + "domain": "oxamedia.com", + "block_cookies": "*" +}, { + "network_id": "290", + "domain": "adsbwm.com", + "block_cookies": "*" +}, { + "network_id": "3", + "domain": "mm7.net", + "block_cookies": "*" +}, { + "network_id": "291", + "domain": "mybuys.com", + "block_cookies": "*" +}, { + "network_id": "292", + "domain": "veruta.com", + "block_cookies": "*" +}, { + "network_id": "288", + "domain": "ib-ibi.com", + "block_cookies": "*" +}, { + "network_id": "288", + "domain": "i-behavior.com", + "block_cookies": "*" +}, { + "network_id": "205", + "domain": "buysight.com", + "block_cookies": "*" +}, { + "network_id": "93", + "domain": "saymedia.com", + "block_cookies": "*" +}, { + "network_id": "293", + "domain": "mediaforge.com", + "block_cookies": "*" +}, { + "network_id": "294", + "domain": "teadma.com", + "block_cookies": "0" +}, { + "network_id": "67", + "domain": "yahoo.com", + "block_cookies": "B" +}, { + "network_id": "140", + "domain": "addthis.com", + "block_cookies": "*" +}, { + "network_id": "298", + "domain": "wtp101.com", + "block_cookies": "*" +}, { + "network_id": "298", + "domain": "adnetik.com", + "block_cookies": "*" +}, { + "network_id": "299", + "domain": "gwallet.com", + "block_cookies": "*" +}, { + "network_id": "300", + "domain": "adsummos.com", + "block_cookies": "*" +}, { + "network_id": "300", + "domain": "adsummos.net", + "block_cookies": "*" +}, { + "network_id": "301", + "domain": "svc.pch.com", + "block_cookies": "*" +}, { + "network_id": "280", + "domain": "brilig.com", + "block_cookies": "*" +}, { + "network_id": "111", + "domain": "adblade.com", + "block_cookies": "*" +}, { + "network_id": "278", + "domain": "adroitinteractive.com", + "block_cookies": "*" +}, { + "network_id": "251", + "domain": "atrinsic.com", + "block_cookies": "*" +}, { + "network_id": "277", + "domain": "bvmedia.ca", + "block_cookies": "*" +}, { + "network_id": "277", + "domain": "networldmedia.com", + "block_cookies": "*" +}, { + "network_id": "172", + "domain": "cpxadroit.com", + "block_cookies": "*" +}, { + "network_id": "37", + "domain": "doubleclick.net", + "block_cookies": "*" +}, { + "network_id": "284", + "domain": "groceryshopping.net", + "block_cookies": "*" +}, { + "network_id": "272", + "domain": "impressiondesk.com", + "block_cookies": "*" +}, { + "network_id": "282", + "domain": "inflectionpointmedia.com", + "block_cookies": "*" +}, { + "network_id": "281", + "domain": "infra-ad.com", + "block_cookies": "*" +}, { + "network_id": "268", + "domain": "cdn.visiblemeasures.com", + "block_cookies": "*" +}, { + "network_id": "213", + "domain": "cdn2.invitemedia.com", + "block_cookies": "*" +}, { + "network_id": "99", + "domain": "bin.clearspring.com", + "block_cookies": "*" +}, { + "network_id": "34", + "domain": "cache.opt.fimserve.com", + "block_cookies": "*" +}, { + "network_id": "57", + "domain": "cache.specificmedia.com", + "block_cookies": "*" +}, { + "network_id": "72", + "domain": "cdn-video.adconion.com", + "block_cookies": "*" +}, { + "network_id": "9", + "domain": "content.yieldmanager.edgesuite.net", + "block_cookies": "*" +}, { + "network_id": "120", + "domain": "core.insightexpressai.com", + "block_cookies": "*" +}, { + "network_id": "93", + "domain": "core.videoegg.com", + "block_cookies": "*" +}, { + "network_id": "147", + "domain": "effectivemeasure.net", + "block_cookies": "*" +}, { + "network_id": "70", + "domain": "flash.quantserve.com", + "block_cookies": "*" +}, { + "network_id": "80", + "domain": "kona.kontera.com", + "block_cookies": "*" +}, { + "network_id": "168", + "domain": "media.scanscout.com", + "block_cookies": "*" +}, { + "network_id": "160", + "domain": "objects.tremormedia.com", + "block_cookies": "*" +}, { + "network_id": "168", + "domain": "static.scanscout.com", + "block_cookies": "*" +}, { + "network_id": "132", + "domain": "tap-cdn.rubiconproject.com", + "block_cookies": "*" +}, { + "network_id": "99", + "domain": "widgets.clearspring.com", + "block_cookies": "*" +}, { + "network_id": "240", + "domain": "www.bannerconnect.net", + "block_cookies": "*" +}, { + "network_id": "94", + "domain": "www.datranmedia.com", + "block_cookies": "*" +}, { + "network_id": "268", + "domain": "www.visiblemeasures.com", + "block_cookies": "*" +}, { + "network_id": "99", + "domain": "cs69.clearspring.com", + "block_cookies": "*" +}, { + "network_id": "302", + "domain": "convertro.com", + "block_cookies": "*" +}, { + "network_id": "32", + "domain": "adsfac.net", + "block_cookies": "*" +}, { + "network_id": "6", + "domain": "adify.com", + "block_cookies": "*" +}, { + "network_id": "274", + "domain": "quisma.com", + "block_cookies": "*" +}, { + "network_id": "286", + "domain": "newtention.de", + "block_cookies": "*" +}, { + "network_id": "272", + "domain": "infectiousmedia.com", + "block_cookies": "*" +}, { + "network_id": "35", + "domain": "freewheel.tv", + "block_cookies": "*" +}, { + "network_id": "277", + "domain": "networldmedia.net", + "block_cookies": "*" +}, { + "network_id": "275", + "domain": "peerset.com", + "block_cookies": "*" +}, { + "network_id": "275", + "domain": "keewurd.com", + "block_cookies": "*" +}, { + "network_id": "32", + "domain": "adsfac.sg", + "block_cookies": "*" +}, { + "network_id": "32", + "domain": "adsfac.eu", + "block_cookies": "*" +}, { + "network_id": "2", + "domain": "decideinteractive.com", + "block_cookies": "*" +}, { + "network_id": "225", + "domain": "efrontier.com", + "block_cookies": "*" +}, { + "network_id": "211", + "domain": "valueclickmedia.com", + "block_cookies": "*" +}, { + "network_id": "41", + "domain": "lotame.com", + "block_cookies": "*" +}, { + "network_id": "233", + "domain": "monster.com", + "block_cookies": "*" +}, { + "network_id": "276", + "domain": "buzzlogic.com", + "block_cookies": "*" +}, { + "network_id": "94", + "domain": "displaymarketplace.com", + "block_cookies": "*" +}, { + "network_id": "286", + "domain": "newtention.net", + "block_cookies": "*" +}, { + "network_id": "283", + "domain": "predictad.com", + "block_cookies": "*" +}, { + "network_id": "17", + "domain": "www.bizo.com", + "block_cookies": "*" +}, { + "network_id": "214", + "domain": "www.beencounter.com", + "block_cookies": "*" +}, { + "network_id": "205", + "domain": "preferences.permuto.com", + "block_cookies": "*" +}, { + "network_id": "76", + "domain": "advertising.aol.com", + "block_cookies": "*" +}, { + "network_id": "303", + "domain": "simpli.fi", + "block_cookies": "*" +}, { + "network_id": "85", + "domain": "agencytradingdesk.net", + "block_cookies": "*" +}, { + "network_id": "304", + "domain": "p-td.com", + "block_cookies": "*" +}, { + "network_id": "305", + "domain": "mochila.com", + "block_cookies": "*" +}, { + "network_id": "306", + "domain": "engagebdr.com", + "block_cookies": "*" +}, { + "network_id": "61", + "domain": "cdn1.trafficmp.com", + "block_cookies": "*" +}, { + "network_id": "307", + "domain": "meebo.com", + "block_cookies": "bcookie" +}, { + "network_id": "306", + "domain": "bnmla.com", + "block_cookies": "*" +}, { + "network_id": "285", + "domain": "chango.com", + "block_cookies": "*" +}, { + "network_id": "285", + "domain": "chango.ca", + "block_cookies": "*" +}, { + "network_id": "155", + "domain": "demdex.net", + "block_cookies": "*" +}, { + "network_id": "308", + "domain": "admagnet.net", + "block_cookies": "*" +}, { + "network_id": "309", + "domain": "cpmstar.com", + "block_cookies": "*" +}, { + "network_id": "310", + "domain": "adecn.com", + "block_cookies": "*" +}, { + "network_id": "156", + "domain": "ads.pubmatic.com", + "block_cookies": "*" +}, { + "network_id": "37", + "domain": "ad.doubleclick.net", + "block_cookies": "*" +}, { + "network_id": "37", + "domain": "googleads.g.doubleclick.net", + "block_cookies": "*" +}, { + "network_id": "6", + "domain": "im.afy11.net", + "block_cookies": "*" +}, { + "network_id": "174", + "domain": "youknowbest.com", + "block_cookies": "*" +}, { + "network_id": "311", + "domain": "kantarmedia.com", + "block_cookies": "*" +}, { + "network_id": "312", + "domain": "mediacom.com", + "block_cookies": "*" +}, { + "network_id": "2", + "domain": "decdna.net", + "block_cookies": "*" +}, { + "network_id": "2", + "domain": "pm14.com", + "block_cookies": "*" +}, { + "network_id": "312", + "domain": "gmads.com", + "block_cookies": "*" +}, { + "network_id": "132", + "domain": "rubiconproject.com", + "block_cookies": "*" +}, { + "network_id": "313", + "domain": "traffiliate.com", + "block_cookies": "0" +}, { + "network_id": "314", + "domain": "tonefuse.com", + "block_cookies": "*" +}, { + "network_id": "315", + "domain": "tagman.com", + "block_cookies": "*" +}, { + "network_id": "315", + "domain": "levexis.com", + "block_cookies": "*" +}, { + "network_id": "316", + "domain": "clickdistrict.com", + "block_cookies": "*" +}, { + "network_id": "316", + "domain": "creative-serving.com", + "block_cookies": "*" +}, { + "network_id": "317", + "domain": "thetradedesk.com", + "block_cookies": "*" +}, { + "network_id": "317", + "domain": "adsrvr.org", + "block_cookies": "*" +}, { + "network_id": "318", + "domain": "surphace.com", + "block_cookies": "*" +}, { + "network_id": "14", + "domain": "netconversions.com", + "block_cookies": "*" +}, { + "network_id": "254", + "domain": "cpmadvisors.com", + "block_cookies": "*" +}, { + "network_id": "319", + "domain": "oneriot.com", + "block_cookies": "*" +}, { + "network_id": "320", + "domain": "bluecava.com", + "block_cookies": "*" +}, { + "network_id": "37", + "domain": "googleadservices.com", + "block_cookies": "*" +}, { + "network_id": "128", + "domain": "loomia.com", + "block_cookies": "_loomiaUTrack" +}, { + "network_id": "160", + "domain": "tmnetads.com", + "block_cookies": "*" +}, { + "network_id": "321", + "domain": "glam.com", + "block_cookies": "*" +}, { + "network_id": "321", + "domain": "glammedia.com", + "block_cookies": "*" +}, { + "network_id": "254", + "domain": "optim.al", + "block_cookies": "*" +}, { + "network_id": "322", + "domain": "komli.com", + "block_cookies": "*" +}, { + "network_id": "323", + "domain": "liverail.com", + "block_cookies": "*" +}, { + "network_id": "324", + "domain": "cyberplex.com", + "block_cookies": "*" +}, { + "network_id": "325", + "domain": "cognitivematch.com", + "block_cookies": "*" +}, { + "network_id": "20", + "domain": "tracksimple.com", + "block_cookies": "*" +}, { + "network_id": "326", + "domain": "ignitad.com", + "block_cookies": "*" +}, { + "network_id": "206", + "domain": "dataxu.net", + "block_cookies": "*" +}, { + "network_id": "327", + "domain": "nxtck.com", + "block_cookies": "*" +}, { + "network_id": "327", + "domain": "nextperformance.com", + "block_cookies": "*" +}, { + "network_id": "328", + "domain": "eadvtracker.com", + "block_cookies": "*" +}, { + "network_id": "62", + "domain": "fulltango.com", + "block_cookies": "*" +}, { + "network_id": "329", + "domain": "crosspixelmedia.com", + "block_cookies": "*" +}, { + "network_id": "330", + "domain": "milabra.com", + "block_cookies": "*" +}, { + "network_id": "331", + "domain": "adzcentral.com", + "block_cookies": "*" +}, { + "network_id": "254", + "domain": "orbengine.com", + "block_cookies": "*" +}, { + "network_id": "169", + "domain": "surphace.com", + "block_cookies": "*" +}, { + "network_id": "333", + "domain": "p-td.com", + "block_cookies": "*" +}, { + "network_id": "333", + "domain": "accuenmedia.com", + "block_cookies": "*" +}, { + "network_id": "26", + "domain": "collective.com", + "block_cookies": "*" +}, { + "network_id": "334", + "domain": "mexad.com", + "block_cookies": "*" +}, { + "network_id": "335", + "domain": "applifier.com", + "block_cookies": "*" +}, { + "network_id": "336", + "domain": "certona.com", + "block_cookies": "*" +}, { + "network_id": "337", + "domain": "perfiliate.com", + "block_cookies": "*" +}, { + "network_id": "337", + "domain": "buy.at", + "block_cookies": "*" +}, { + "network_id": "338", + "domain": "underdogmedia.com", + "block_cookies": "*" +}, { + "network_id": "339", + "domain": "oneiota.co.uk", + "block_cookies": "*" +}, { + "network_id": "340", + "domain": "resolutionmedia.com", + "block_cookies": "*" +}, { + "network_id": "341", + "domain": "tynt.com", + "block_cookies": "*" +}, { + "network_id": "342", + "domain": "ohanaqb.com", + "block_cookies": "*" +}, { + "network_id": "342", + "domain": "ohana-media.com", + "block_cookies": "*" +}, { + "network_id": "342", + "domain": "adohana.com", + "block_cookies": "*" +}, { + "network_id": "343", + "domain": "forbesmedia.com", + "block_cookies": "*" +}, { + "network_id": "132", + "domain": "myads.com", + "block_cookies": "*" +}, { + "network_id": "225", + "domain": "everestads.net", + "block_cookies": "*" +}, { + "network_id": "225", + "domain": "everestjs.net", + "block_cookies": "*" +}, { + "network_id": "344", + "domain": "acquisio.com", + "block_cookies": "*" +}, { + "network_id": "345", + "domain": "veremedia.com", + "block_cookies": "*" +}, { + "network_id": "346", + "domain": "mercent.com", + "block_cookies": "*" +}, { + "network_id": "347", + "domain": "intentmedia.com", + "block_cookies": "*" +}, { + "network_id": "347", + "domain": "intentmedia.net", + "block_cookies": "*" +}, { + "network_id": "348", + "domain": "keyade.com", + "block_cookies": "*" +}, { + "network_id": "146", + "domain": "csi-tracking.com", + "block_cookies": "*" +}, { + "network_id": "349", + "domain": "rmmonline.com", + "block_cookies": "*" +}, { + "network_id": "350", + "domain": "ringleaderdigital.com", + "block_cookies": "*" +}, { + "network_id": "351", + "domain": "rovion.com", + "block_cookies": "*" +}, { + "network_id": "352", + "domain": "orangesoda.com", + "block_cookies": "*" +}, { + "network_id": "352", + "domain": "otracking.com", + "block_cookies": "*" +}, { + "network_id": "353", + "domain": "enecto.com", + "block_cookies": "*" +}, { + "network_id": "354", + "domain": "yabuka.com", + "block_cookies": "*" +}, { + "network_id": "355", + "domain": "mywebgrocer.com", + "block_cookies": "*" +}, { + "network_id": "356", + "domain": "mediatrust.com", + "block_cookies": "*" +}, { + "network_id": "357", + "domain": "contextin.com", + "block_cookies": "*" +}, { + "network_id": "357", + "domain": "admailtiser.com", + "block_cookies": "*" +}, { + "network_id": "358", + "domain": "admarketplace.com", + "block_cookies": "*" +}, { + "network_id": "236", + "domain": "halogenmediagroup.com", + "block_cookies": "*" +}, { + "network_id": "325", + "domain": "cmadseu.com", + "block_cookies": "*" +}, { + "network_id": "325", + "domain": "cmadsasia.com", + "block_cookies": "*" +}, { + "network_id": "325", + "domain": "cmads.com.tw", + "block_cookies": "*" +}, { + "network_id": "48", + "domain": "nielsen-online.com", + "block_cookies": "*" +}, { + "network_id": "336", + "domain": "res-x.com", + "block_cookies": "*" +}, { + "network_id": "359", + "domain": "adjug.com", + "block_cookies": "*" +}, { + "network_id": "360", + "domain": "switchconcepts.co.uk", + "block_cookies": "*" +}, { + "network_id": "360", + "domain": "switchadhub.com", + "block_cookies": "*" +}, { + "network_id": "361", + "domain": "bizmey.com", + "block_cookies": "*" +}, { + "network_id": "362", + "domain": "wibiya.com", + "block_cookies": "*" +}, { + "network_id": "363", + "domain": "audienceiq.com", + "block_cookies": "*" +}, { + "network_id": "363", + "domain": "experian.com", + "block_cookies": "*" +}, { + "network_id": "364", + "domain": "adlantic.nl", + "block_cookies": "*" +}, { + "network_id": "365", + "domain": "eyeviewdigital.com", + "block_cookies": "*" +}, { + "network_id": "366", + "domain": "51network.com", + "block_cookies": "*" +}, { + "network_id": "366", + "domain": "uniqlick.com", + "block_cookies": "*" +}, { + "network_id": "366", + "domain": "wanmo.com", + "block_cookies": "*" +}, { + "network_id": "367", + "domain": "eloqua.com", + "block_cookies": "*" +}, { + "network_id": "369", + "domain": "infogroup.com", + "block_cookies": "*" +}, { + "network_id": "370", + "domain": "scandinavianadnetworks.com", + "block_cookies": "*" +}, { + "network_id": "371", + "domain": "medicxmedia.com", + "block_cookies": "*" +}, { + "network_id": "372", + "domain": "hooklogic.com", + "block_cookies": "*" +}, { + "network_id": "373", + "domain": "kenshoo.com", + "block_cookies": "*" +}, { + "network_id": "56", + "domain": "meetic-partners.com", + "block_cookies": "*" +}, { + "network_id": "56", + "domain": "horyzon-media.com", + "block_cookies": "*" +}, { + "network_id": "374", + "domain": "fairfax.com.au", + "block_cookies": "*" +}, { + "network_id": "375", + "domain": "sensis.com.au", + "block_cookies": "*" +}, { + "network_id": "375", + "domain": "telstra.com", + "block_cookies": "*" +}, { + "network_id": "375", + "domain": "sensisdigitalmedia.com.au", + "block_cookies": "*" +}, { + "network_id": "375", + "domain": "sensisdata.com.au", + "block_cookies": "*" +}, { + "network_id": "376", + "domain": "maxusglobal.com", + "block_cookies": "*" +}, { + "network_id": "377", + "domain": "fetchforce.com", + "block_cookies": "*" +}, { + "network_id": "378", + "domain": "barilliance.com", + "block_cookies": "*" +}, { + "network_id": "379", + "domain": "jaroop.com", + "block_cookies": "*" +}, { + "network_id": "380", + "domain": "autonomy.com", + "block_cookies": "*" +}, { + "network_id": "381", + "domain": "collidermedia.com", + "block_cookies": "*" +}, { + "network_id": "382", + "domain": "epsilon.com", + "block_cookies": "*" +}, { + "network_id": "383", + "domain": "leadformix.com", + "block_cookies": "*" +}, { + "network_id": "384", + "domain": "rimmkaufman.com", + "block_cookies": "*" +}, { + "network_id": "384", + "domain": "rkdms.com", + "block_cookies": "*" +}, { + "network_id": "385", + "domain": "vizisense.com", + "block_cookies": "*" +}, { + "network_id": "385", + "domain": "vizisense.net", + "block_cookies": "*" +}, { + "network_id": "386", + "domain": "sagemetrics.com", + "block_cookies": "*" +}, { + "network_id": "386", + "domain": "sageanalyst.net", + "block_cookies": "*" +}, { + "network_id": "387", + "domain": "krxd.net", + "block_cookies": "*" +}, { + "network_id": "387", + "domain": "kruxdigital.com", + "block_cookies": "*" +}, { + "network_id": "24", + "domain": "medianet.com", + "block_cookies": "*" +}, { + "network_id": "274", + "domain": "iaded.com", + "block_cookies": "*" +}, { + "network_id": "274", + "domain": "xaded.com", + "block_cookies": "*" +}, { + "network_id": "274", + "domain": "xmladed.com", + "block_cookies": "*" +}, { + "network_id": "51", + "domain": "servedbyopenx.com", + "block_cookies": "*" +}, { + "network_id": "372", + "domain": "hlserve.com", + "block_cookies": "*" +}, { + "network_id": "388", + "domain": "mediaarmor", + "block_cookies": "*" +}, { + "network_id": "389", + "domain": "viglink.com", + "block_cookies": "*" +}, { + "network_id": "390", + "domain": "inuvo.com", + "block_cookies": "*" +}, { + "network_id": "391", + "domain": "tubemogul.com", + "block_cookies": "*" +}, { + "network_id": "392", + "domain": "steelhouse.com", + "block_cookies": "*" +}, { + "network_id": "392", + "domain": "steelhousemedia.com", + "block_cookies": "*" +}, { + "network_id": "393", + "domain": "oggifinogi.com", + "block_cookies": "*" +}, { + "network_id": "394", + "domain": "sophus3.co.uk", + "block_cookies": "*" +}, { + "network_id": "394", + "domain": "sophus3.com", + "block_cookies": "*" +}, { + "network_id": "395", + "domain": "daphnecm.com", + "block_cookies": "*" +}, { + "network_id": "395", + "domain": "gfk.com", + "block_cookies": "*" +}, { + "network_id": "395", + "domain": "gfkdaphne.com", + "block_cookies": "*" +}, { + "network_id": "245", + "domain": "thinkrealtime.com", + "block_cookies": "*" +}, { + "network_id": "396", + "domain": "sensic.net", + "block_cookies": "*" +}, { + "network_id": "396", + "domain": "nurago.de", + "block_cookies": "*" +}, { + "network_id": "397", + "domain": "poprule.com", + "block_cookies": "*" +}, { + "network_id": "397", + "domain": "gocampaignlive.com", + "block_cookies": "*" +}, { + "network_id": "398", + "domain": "gemius.pl", + "block_cookies": "*" +}, { + "network_id": "399", + "domain": "mindshare.nl", + "block_cookies": "*" +}, { + "network_id": "399", + "domain": "mindshareworld.com", + "block_cookies": "*" +}, { + "network_id": "400", + "domain": "eulerian.com", + "block_cookies": "*" +}, { + "network_id": "400", + "domain": "eulerian.net", + "block_cookies": "*" +}, { + "network_id": "401", + "domain": "qoof.com", + "block_cookies": "*" +}, { + "network_id": "402", + "domain": "mecglobal.com", + "block_cookies": "*" +}, { + "network_id": "403", + "domain": "zedo.com", + "block_cookies": "*" +}, { + "network_id": "404", + "domain": "apture.com", + "block_cookies": "*" +}, { + "network_id": "405", + "domain": "adform.com", + "block_cookies": "*" +}, { + "network_id": "405", + "domain": "adform.net", + "block_cookies": "*" +}, { + "network_id": "68", + "domain": "undertonenetworks.com", + "block_cookies": "*" +}, { + "network_id": "406", + "domain": "pswec.com", + "block_cookies": "*" +}, { + "network_id": "406", + "domain": "proclivitysystems.com", + "block_cookies": "*" +}, { + "network_id": "407", + "domain": "martinimedianetwork.com", + "block_cookies": "*" +}, { + "network_id": "408", + "domain": "adriver.ru", + "block_cookies": "*" +}, { + "network_id": "409", + "domain": "madisonlogic.com", + "block_cookies": "*" +}, { + "network_id": "410", + "domain": "adaptly.com", + "block_cookies": "*" +}, { + "network_id": "411", + "domain": "c3tag.com", + "block_cookies": "*" +}, { + "network_id": "411", + "domain": "c3metrics.com", + "block_cookies": "*" +}, { + "network_id": "412", + "domain": "cadreon.com", + "block_cookies": "*" +}, { + "network_id": "413", + "domain": "sociomantic.com", + "block_cookies": "0" +}, { + "network_id": "414", + "domain": "atinternet.com", + "block_cookies": "*" +}, { + "network_id": "414", + "domain": "xiti.com", + "block_cookies": "*" +}, { + "network_id": "415", + "domain": "affinesystems.com", + "block_cookies": "*" +}, { + "network_id": "416", + "domain": "gmads.net", + "block_cookies": "*" +}, { + "network_id": "416", + "domain": "groupm.com", + "block_cookies": "*" +}, { + "network_id": "417", + "domain": "nascar.com", + "block_cookies": "*" +}, { + "network_id": "418", + "domain": "gravity.com", + "block_cookies": "*" +}, { + "network_id": "419", + "domain": "smowtion.com", + "block_cookies": "*" +}, { + "network_id": "420", + "domain": "v12groupinc.com", + "block_cookies": "*" +}, { + "network_id": "421", + "domain": "webtrekk.net", + "block_cookies": "*" +}, { + "network_id": "421", + "domain": "webtrekk.com", + "block_cookies": "*" +}, { + "network_id": "422", + "domain": "net-results.com", + "block_cookies": "*" +}, { + "network_id": "423", + "domain": "etracker.de", + "block_cookies": "*" +}, { + "network_id": "423", + "domain": "etracker.com", + "block_cookies": "*" +}, { + "network_id": "424", + "domain": "woopra.com", + "block_cookies": "*" +}, { + "network_id": "425", + "domain": "blacklabelads.com", + "block_cookies": "*" +}, { + "network_id": "426", + "domain": "fireclick.com", + "block_cookies": "*" +}, { + "network_id": "427", + "domain": "personyze.com", + "block_cookies": "*" +}, { + "network_id": "428", + "domain": "adultadworld.com", + "block_cookies": "*" +}, { + "network_id": "429", + "domain": "vdopia.com", + "block_cookies": "*" +}, { + "network_id": "429", + "domain": "ivdopia.com", + "block_cookies": "*" +}, { + "network_id": "430", + "domain": "gourmetads.com", + "block_cookies": "0" +}, { + "network_id": "431", + "domain": "dsnrmg.com", + "block_cookies": "*" +}, { + "network_id": "431", + "domain": "dsnrgroup.com", + "block_cookies": "*" +}, { + "network_id": "432", + "domain": "millennialmedia.com", + "block_cookies": "*" +}, { + "network_id": "432", + "domain": "mydas.mobi", + "block_cookies": "*" +}, { + "network_id": "433", + "domain": "maxymiser.com", + "block_cookies": "*" +}, { + "network_id": "434", + "domain": "marketo.com", + "block_cookies": "*" +}, { + "network_id": "435", + "domain": "onestat.com", + "block_cookies": "*" +}, { + "network_id": "436", + "domain": "kissmyads.com", + "block_cookies": "*" +}, { + "network_id": "8", + "domain": "heias.com", + "block_cookies": "*" +}, { + "network_id": "437", + "domain": "zanox.com", + "block_cookies": "*" +}, { + "network_id": "438", + "domain": "haileo.com", + "block_cookies": "*" +}, { + "network_id": "439", + "domain": "tickerish.com", + "block_cookies": "*" +}, { + "network_id": "329", + "domain": "crosspixel.net", + "block_cookies": "*" +}, { + "network_id": "71", + "domain": "rfihub.net", + "block_cookies": "*" +}, { + "network_id": "440", + "domain": "adpredictive.com", + "block_cookies": "*" +}, { + "network_id": "441", + "domain": "xad.com", + "block_cookies": "*" +}, { + "network_id": "442", + "domain": "advertise.com", + "block_cookies": "*" +}, { + "network_id": "443", + "domain": "meteorsolutions.com", + "block_cookies": "*" +}, { + "network_id": "39", + "domain": "secure-adserver.com", + "block_cookies": "*" +}, { + "network_id": "444", + "domain": "adition.com", + "block_cookies": "*" +}, { + "network_id": "445", + "domain": "ooyala.com", + "block_cookies": "*" +}, { + "network_id": "446", + "domain": "dedicatednetworks.com", + "block_cookies": "*" +}, { + "network_id": "268", + "domain": "viewablemedia.net", + "block_cookies": "*" +}, { + "network_id": "447", + "domain": "adnectar.com", + "block_cookies": "*" +}, { + "network_id": "448", + "domain": "smileymedia.com", + "block_cookies": "*" +}, { + "network_id": "449", + "domain": "emediate.biz", + "block_cookies": "*" +}, { + "network_id": "449", + "domain": "emediate.dk", + "block_cookies": "*" +}, { + "network_id": "449", + "domain": "emediate.eu", + "block_cookies": "*" +}, { + "network_id": "450", + "domain": "optimumresponse.com", + "block_cookies": "*" +}, { + "network_id": "451", + "domain": "crispmedia.com", + "block_cookies": "*" +}] \ No newline at end of file diff --git a/settings/base.py b/settings/base.py index 18680f9b0b..a097838daf 100644 --- a/settings/base.py +++ b/settings/base.py @@ -47,6 +47,9 @@ MINIFY_BUNDLES = { 'b2g': ( 'css/b2g.less', ), + 'collusion': ( + 'css/collusion.less', + ), 'common': ( 'css/sandstone/sandstone.less', ), @@ -85,6 +88,13 @@ MINIFY_BUNDLES = { ), }, 'js': { + 'collusion': ( + 'js/collusion/d3.layout.js', + 'js/collusion/d3.geom.js', + 'js/collusion/collusion-addon.js', + 'js/collusion/demo.js', + 'js/collusion/graphrunner.js', + ), 'common': ( 'js/libs/jquery-1.7.1.min.js', ), @@ -130,6 +140,7 @@ INSTALLED_APPS = list(INSTALLED_APPS) + [ # Local apps 'l10n_example', # DELETEME 'b2g', + 'collusion', 'marketplace', 'mozorg', 'persona', diff --git a/urls.py b/urls.py index af2385b276..587a56095e 100644 --- a/urls.py +++ b/urls.py @@ -9,6 +9,7 @@ from django.conf.urls.defaults import * urlpatterns = patterns('', # Main pages (r'^b2g/', include('b2g.urls')), + (r'^collusion/', include('collusion.urls')), (r'^apps/', include('marketplace.urls')), (r'^persona/', include('persona.urls')), (r'', include('mozorg.urls')),