зеркало из https://github.com/mozilla/bedrock.git
Initial setup of Collusion page
Bug 730012 - [Collusion] Build Collusion Landing Page on mozilla.org for Gary for TED
This commit is contained in:
Родитель
59d9b1c78d
Коммит
e128b3fc56
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -0,0 +1,401 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block page_title %}Collusion{% endblock %}
|
||||
{% block body_id %}collusion{% endblock %}
|
||||
|
||||
{% block site_header_nav %}
|
||||
<nav id="nav-main" role="navigation">
|
||||
<ul>
|
||||
<li><b>Home</b></li>
|
||||
<li><a href="/collusion/demo/">Demo</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ css('collusion') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<hgroup class="container">
|
||||
<h1 class="large">Introducing Collusion</h1>
|
||||
<h2>Discover who’s tracking you online</h2>
|
||||
</hgroup>
|
||||
|
||||
<section id="primary" class="container">
|
||||
|
||||
<img src="/media/img/collusion/nodes.png" alt="">
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<a class="button-blue" href="demo/">
|
||||
View Our Demo
|
||||
<small>see how you’re being tracked</small>
|
||||
</a>
|
||||
|
||||
<a class="button" href="https://addons.mozilla.org/en-US/firefox/addon/collusion/">
|
||||
Download
|
||||
<small>the Collusion add-on for Firefox</small>
|
||||
</a>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="secondary" class="container">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="span6">
|
||||
|
||||
<h3>Take control of your data</h3>
|
||||
<p>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.</p>
|
||||
|
||||
<h3>Telling the global tracking story</h3>
|
||||
<p>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.</p>
|
||||
|
||||
<h3>Building user awareness</h3>
|
||||
<p>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).</p>
|
||||
|
||||
<h3>Collusion is about choice</h3>
|
||||
<p>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.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="span3 offset1">
|
||||
|
||||
<form action="https://donate.mozilla.org/page/s/sign-up-for-mozilla" method="post" class="box" id="updates">
|
||||
<h3>Want to hear more?</h3>
|
||||
<p class="intro">Get Mozilla updates:</p>
|
||||
|
||||
<div class="field" id="email-field">
|
||||
<span id="email-wrapper"><input name="email" type="email" id="email" value="" placeholder="Your Email Address" required></span>
|
||||
</div>
|
||||
|
||||
<div class="field" id="country-field">
|
||||
<select id="country" name="country" required>
|
||||
<option value=""></option>
|
||||
<option value="AF">Afghanistan</option>
|
||||
<option value="AL">Albania</option>
|
||||
<option value="DZ">Algeria</option>
|
||||
|
||||
<option value="AS">American Samoa</option>
|
||||
<option value="AD">Andorra</option>
|
||||
<option value="AO">Angola</option>
|
||||
<option value="AI">Anguilla</option>
|
||||
<option value="AG">Antigua and Barbuda</option>
|
||||
<option value="AR">Argentina</option>
|
||||
|
||||
<option value="AM">Armenia</option>
|
||||
<option value="AW">Aruba</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="BS">Bahamas</option>
|
||||
|
||||
<option value="BH">Bahrain</option>
|
||||
<option value="BD">Bangladesh</option>
|
||||
<option value="BB">Barbados</option>
|
||||
<option value="BY">Belarus</option>
|
||||
<option value="BE">Belgium</option>
|
||||
<option value="BZ">Belize</option>
|
||||
|
||||
<option value="BJ">Benin</option>
|
||||
<option value="BM">Bermuda</option>
|
||||
<option value="BT">Bhutan</option>
|
||||
<option value="BO">Bolivia</option>
|
||||
<option value="BA">Bosnia and Herzegovina</option>
|
||||
<option value="BW">Botswana</option>
|
||||
|
||||
<option value="BR">Brazil</option>
|
||||
<option value="VG">British Virgin Islands</option>
|
||||
<option value="IO">British Indian Ocean Territory</option>
|
||||
<option value="BN">Brunei</option>
|
||||
<option value="BG">Bulgaria</option>
|
||||
<option value="BF">Burkina Faso</option>
|
||||
|
||||
<option value="BI">Burundi</option>
|
||||
<option value="KH">Cambodia</option>
|
||||
<option value="CM">Cameroon</option>
|
||||
<option value="CA">Canada</option>
|
||||
|
||||
<option value="CV">Cape Verde</option>
|
||||
<option value="KY">Cayman Islands</option>
|
||||
|
||||
<option value="CF">Central African Republic</option>
|
||||
<option value="TD">Chad</option>
|
||||
<option value="CL">Chile</option>
|
||||
<option value="CN">China</option>
|
||||
|
||||
<option value="CX">Christmas Island</option>
|
||||
<option value="CO">Colombia</option>
|
||||
|
||||
<option value="KM">Comoros Islands</option>
|
||||
<option value="CD">Congo, Democratic Republic of the</option>
|
||||
<option value="CG">Congo, Republic of the</option>
|
||||
<option value="CK">Cook Islands</option>
|
||||
|
||||
<option value="CR">Costa Rica</option>
|
||||
<option value="CI">Cote D'ivoire</option>
|
||||
|
||||
<option value="HR">Croatia</option>
|
||||
<option value="CU">Cuba</option>
|
||||
<option value="CY">Cyprus</option>
|
||||
<option value="CZ">Czech Republic</option>
|
||||
|
||||
<option value="DK">Denmark</option>
|
||||
<option value="DJ">Djibouti</option>
|
||||
|
||||
<option value="DM">Dominica</option>
|
||||
<option value="DO">Dominican Republic</option>
|
||||
<option value="TP">East Timor</option>
|
||||
<option value="EC">Ecuador</option>
|
||||
|
||||
<option value="EG">Egypt</option>
|
||||
<option value="SV">El Salvador</option>
|
||||
|
||||
<option value="GQ">Equatorial Guinea</option>
|
||||
<option value="ER">Eritrea</option>
|
||||
<option value="EE">Estonia</option>
|
||||
<option value="ET">Ethiopia</option>
|
||||
|
||||
<option value="FK">Falkland Islands (Malvinas)</option>
|
||||
<option value="FO">Faroe Islands</option>
|
||||
|
||||
<option value="FJ">Fiji</option>
|
||||
<option value="FI">Finland</option>
|
||||
<option value="FR">France</option>
|
||||
<option value="GF">French Guiana</option>
|
||||
|
||||
<option value="PF">French Polynesia</option>
|
||||
<option value="TF">French Southern Territories</option>
|
||||
|
||||
<option value="GA">Gabon</option>
|
||||
<option value="GM">Gambia</option>
|
||||
<option value="GE">Georgia</option>
|
||||
<option value="DE">Germany</option>
|
||||
|
||||
<option value="GH">Ghana</option>
|
||||
<option value="GI">Gibraltar</option>
|
||||
|
||||
<option value="GR">Greece</option>
|
||||
<option value="GL">Greenland</option>
|
||||
<option value="GD">Grenada</option>
|
||||
<option value="GP">Guadeloupe</option>
|
||||
|
||||
<option value="GU">Guam</option>
|
||||
<option value="GT">Guatemala</option>
|
||||
|
||||
<option value="GN">Guinea</option>
|
||||
<option value="GW">Guinea-Bissau</option>
|
||||
<option value="GY">Guyana</option>
|
||||
<option value="HT">Haiti</option>
|
||||
|
||||
<option value="VA">Holy See (Vatican City State)</option>
|
||||
<option value="HN">Honduras</option>
|
||||
|
||||
<option value="HK">Hong Kong</option>
|
||||
<option value="HU">Hungary</option>
|
||||
<option value="IS">Iceland</option>
|
||||
<option value="IN">India</option>
|
||||
|
||||
<option value="ID">Indonesia</option>
|
||||
<option value="IR">Iran</option>
|
||||
|
||||
<option value="IQ">Iraq</option>
|
||||
<option value="IE">Ireland</option>
|
||||
<option value="IL">Israel</option>
|
||||
<option value="IT">Italy</option>
|
||||
|
||||
<option value="JM">Jamaica</option>
|
||||
<option value="JP">Japan</option>
|
||||
|
||||
<option value="JO">Jordan</option>
|
||||
<option value="KZ">Kazakhstan</option>
|
||||
<option value="KE">Kenya</option>
|
||||
<option value="KI">Kiribati</option>
|
||||
|
||||
<option value="KR">South Korea</option>
|
||||
<option value="XK">Kosovo</option>
|
||||
|
||||
<option value="KW">Kuwait</option>
|
||||
<option value="KG">Kyrgyzstan</option>
|
||||
<option value="LA">Laos</option>
|
||||
<option value="LV">Latvia</option>
|
||||
|
||||
<option value="LB">Lebanon</option>
|
||||
<option value="LS">Lesotho</option>
|
||||
|
||||
<option value="LR">Liberia</option>
|
||||
<option value="LI">Liechtenstein</option>
|
||||
<option value="LT">Lithuania</option>
|
||||
<option value="LU">Luxembourg</option>
|
||||
|
||||
<option value="MO">Macau</option>
|
||||
<option value="MK">Macedonia</option>
|
||||
|
||||
<option value="MG">Madagascar</option>
|
||||
<option value="MW">Malawi</option>
|
||||
<option value="MY">Malaysia</option>
|
||||
<option value="MV">Maldives</option>
|
||||
|
||||
<option value="ML">Mali</option>
|
||||
<option value="MT">Malta</option>
|
||||
|
||||
<option value="MH">Marshall Islands</option>
|
||||
<option value="MQ">Martinique</option>
|
||||
<option value="MR">Mauritania</option>
|
||||
<option value="MU">Mauritius</option>
|
||||
|
||||
<option value="YT">Mayotte</option>
|
||||
<option value="MX">Mexico</option>
|
||||
|
||||
<option value="FM">Micronesia</option>
|
||||
<option value="MD">Moldova, Republic of</option>
|
||||
<option value="MC">Monaco</option>
|
||||
<option value="MN">Mongolia</option>
|
||||
|
||||
<option value="ME">Montenegro</option>
|
||||
<option value="MS">Montserrat</option>
|
||||
|
||||
<option value="MA">Morocco</option>
|
||||
<option value="MZ">Mozambique</option>
|
||||
<option value="MM">Myanmar</option>
|
||||
<option value="NA">Namibia</option>
|
||||
|
||||
<option value="NR">Nauru</option>
|
||||
<option value="NP">Nepal</option>
|
||||
|
||||
<option value="NL">Netherlands</option>
|
||||
<option value="AN">Netherlands Antilles</option>
|
||||
<option value="NC">New Caledonia</option>
|
||||
<option value="NZ">New Zealand</option>
|
||||
|
||||
<option value="NI">Nicaragua</option>
|
||||
<option value="NE">Niger</option>
|
||||
|
||||
<option value="NG">Nigeria</option>
|
||||
<option value="NU">Niue</option>
|
||||
<option value="NF">Norfolk Island</option>
|
||||
<option value="MP">Northern Mariana Islands</option>
|
||||
|
||||
<option value="NO">Norway</option>
|
||||
<option value="OM">Oman</option>
|
||||
|
||||
<option value="PK">Pakistan</option>
|
||||
<option value="PW">Palau</option>
|
||||
<option value="PA">Panama</option>
|
||||
<option value="PG">Papua New Guinea</option>
|
||||
|
||||
<option value="PY">Paraguay</option>
|
||||
<option value="PE">Peru</option>
|
||||
|
||||
<option value="PH">Philippines</option>
|
||||
<option value="PN">Pitcairn Island</option>
|
||||
<option value="PL">Poland</option>
|
||||
<option value="PT">Portugal</option>
|
||||
|
||||
<option value="PR">Puerto Rico</option>
|
||||
<option value="QA">Qatar</option>
|
||||
|
||||
<option value="RE">Reunion</option>
|
||||
<option value="RO">Romania</option>
|
||||
<option value="RU">Russian Federation</option>
|
||||
<option value="RW">Rwanda</option>
|
||||
|
||||
<option value="KN">Saint Kitts and Nevis</option>
|
||||
<option value="LC">Saint Lucia</option>
|
||||
|
||||
<option value="VC">Saint Vincent and the Grenadines</option>
|
||||
<option value="WS">Samoa</option>
|
||||
<option value="SM">San Marino</option>
|
||||
<option value="ST">Sao Tome and Principe</option>
|
||||
|
||||
<option value="SA">Saudi Arabia</option>
|
||||
<option value="SN">Senegal</option>
|
||||
|
||||
<option value="RS">Serbia</option>
|
||||
<option value="SC">Seychelles</option>
|
||||
<option value="SL">Sierra Leone</option>
|
||||
<option value="SG">Singapore</option>
|
||||
|
||||
<option value="SK">Slovakia</option>
|
||||
<option value="SI">Slovenia</option>
|
||||
|
||||
<option value="SB">Solomon Islands</option>
|
||||
<option value="SO">Somalia</option>
|
||||
<option value="ZA">South Africa</option>
|
||||
<option value="ES">Spain</option>
|
||||
|
||||
<option value="LK">Sri Lanka</option>
|
||||
<option value="SH">St. Helena</option>
|
||||
|
||||
<option value="PM">St. Pierre and Miquelon</option>
|
||||
<option value="SD">Sudan</option>
|
||||
<option value="SR">Suriname</option>
|
||||
<option value="SZ">Swaziland</option>
|
||||
|
||||
<option value="SE">Sweden</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
|
||||
<option value="SY">Syria</option>
|
||||
<option value="TW">Taiwan</option>
|
||||
<option value="TJ">Tajikistan</option>
|
||||
<option value="TZ">Tanzania</option>
|
||||
|
||||
<option value="TH">Thailand</option>
|
||||
<option value="TG">Togo</option>
|
||||
|
||||
<option value="TK">Tokelau</option>
|
||||
<option value="TO">Tonga</option>
|
||||
<option value="TT">Trinidad and Tobago</option>
|
||||
<option value="TN">Tunisia</option>
|
||||
|
||||
<option value="TR">Turkey</option>
|
||||
<option value="TM">Turkmenistan</option>
|
||||
|
||||
<option value="TC">Turks and Caicos Islands</option>
|
||||
<option value="TV">Tuvalu</option>
|
||||
<option value="UG">Uganda</option>
|
||||
<option value="UA">Ukraine</option>
|
||||
<option value="AE">United Arab Emirates</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="US" selected="selected">United States</option>
|
||||
<option value="UY">Uruguay</option>
|
||||
<option value="UZ">Uzbekistan</option>
|
||||
<option value="VU">Vanuatu</option>
|
||||
<option value="VE">Venezuela</option>
|
||||
<option value="VN">Viet Nam</option>
|
||||
<option value="VI">Virgin Islands (U.S.)</option>
|
||||
<option value="WF">Wallis and Futuna Islands</option>
|
||||
<option value="EH">Western Sahara</option>
|
||||
<option value="YE">Yemen</option>
|
||||
<option value="ZM">Zambia</option>
|
||||
<option value="ZW">Zimbabwe</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field" id="privacy-field">
|
||||
<label for="privacy-check" id="privacy-check-label" class="">
|
||||
<span class="input"><input type="checkbox" name="custom-314" id="privacy-check" value="1" required></span>
|
||||
|
||||
<span class="title">I agree to the <a href="http://www.mozilla.org/about/policies/privacy-policy.html">Privacy Policy</a></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field" id="submit-field">
|
||||
<button type="submit" class="submit button-blue">Sign me up</button>
|
||||
</div>
|
||||
<p><small>We will only send you Mozilla-related information.</small></p>
|
||||
<input type="hidden" name="submit" value="submit">
|
||||
</form>
|
||||
|
||||
<h3>Supported by</h3>
|
||||
<img src="/media/img/collusion/ford-foundation.png" alt="Ford Foundation" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,197 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block page_title %}Collusion Demo{% endblock %}
|
||||
{% block body_id %}collusion-demo{% endblock %}
|
||||
|
||||
{% block site_header_nav %}
|
||||
<nav id="nav-main" role="navigation">
|
||||
<ul>
|
||||
<li><a href="/collusion/">Home</a></li>
|
||||
<li><b>Demo</b></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ css('collusion') }}
|
||||
<script src="/media/js/collusion/d3.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ js('collusion') }}
|
||||
<script>
|
||||
// taken from http://stackoverflow.com/questions/2090551/parse-query-string-in-javascript
|
||||
function getQueryVariable(variable) {
|
||||
var query = window.location.search.substring(1);
|
||||
var vars = query.split("&");
|
||||
for (var i = 0; i < vars.length; i++) {
|
||||
var pair = vars[i].split("=");
|
||||
if (pair[0] == variable) {
|
||||
return unescape(pair[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateGraphViaPostMessage(graph, target) {
|
||||
target.postMessage("ready", "*");
|
||||
$(window).one("message", function(event) {
|
||||
graph.update(JSON.parse(event.originalEvent.data));
|
||||
});
|
||||
}
|
||||
|
||||
$(window).ready(function() {
|
||||
var addon = CollusionAddon;
|
||||
var graphUrl = getQueryVariable("graph_url");
|
||||
|
||||
$("#domain-infos").hide();
|
||||
|
||||
// get list of known trackers from trackers.json file hosted on website:
|
||||
jQuery.getJSON("/media/js/collusion/trackers.json", function(trackers) {
|
||||
var runner = GraphRunner.Runner({
|
||||
width: addon.isInstalled() || graphUrl ? $(window).width() : 640,
|
||||
height: addon.isInstalled() || graphUrl ? $(window).height() : 480,
|
||||
trackers: trackers,
|
||||
hideFavicons: false
|
||||
});
|
||||
var graph = runner.graph;
|
||||
|
||||
// Enable drag-and-drop to import collusion graph data from a json file:
|
||||
$(document.body).bind("dragover", function(event) {
|
||||
event.preventDefault();
|
||||
}).bind("drop", function(event) {
|
||||
event.preventDefault();
|
||||
var files = event.originalEvent.dataTransfer.files;
|
||||
if (files.length == 1) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
if (addon.importGraph) {
|
||||
addon.importGraph(reader.result);
|
||||
window.location.reload();
|
||||
} else
|
||||
graph.update(JSON.parse(reader.result));
|
||||
};
|
||||
reader.readAsText(files[0], "UTF-8");
|
||||
}
|
||||
});
|
||||
|
||||
$("#page").width(runner.width);
|
||||
|
||||
if (graphUrl) {
|
||||
if (graphUrl == "opener") {
|
||||
updateGraphViaPostMessage(graph, window.opener);
|
||||
} else if (graphUrl == "parent") {
|
||||
|
||||
updateGraphViaPostMessage(graph, window.parent);
|
||||
} else
|
||||
jQuery.getJSON(graphUrl, function(data) {
|
||||
graph.update(data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Demo.show(graph);
|
||||
|
||||
// Display the footer now the page is rendered
|
||||
$('section.external').fadeIn();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="container">Collusion Demo</h1>
|
||||
<section class="container">
|
||||
|
||||
<div id="page">
|
||||
<div id="sidebar" class="box">
|
||||
<div class="byline"></a></div>
|
||||
|
||||
<div id="sidebar-content">
|
||||
<div class="exposition">
|
||||
<blockquote>If you're not paying for something, you're not the customer; you're the product being sold.</blockquote><span class="attribution">- Andrew Lewis</span>
|
||||
<p>Collusion provides an interactive, real-time visualization of the entities that track your behavior across the web.</p>
|
||||
</div> <!-- end of exposition -->
|
||||
|
||||
<div class="live-data" style="display: none;">
|
||||
<p>Keep browsing the web. As you do so, a graph on this page will change. Each dot represents a website.</p>
|
||||
<p>Sites in <span class="tracker">red</span> are confirmed trackers by <a href="http://privacychoice.org">privacychoice.org</a>. Sites in <span class="site">gray</span> are not, but may still track you.</p>
|
||||
<p>Sites with a halo are sites that you have visited. Sites without a halo are sites you have not visited.</p>
|
||||
|
||||
<p>Hover your mouse over the dots to learn more about them.</p>
|
||||
<div class="settings-menu">
|
||||
<ul>
|
||||
<li id="reset-graph">Reset Graph</li>
|
||||
<li id="export-graph">Export Graph</li>
|
||||
<li id="hide-ui">Hide UI</li>
|
||||
</ul>
|
||||
|
||||
</div> <!-- end of settingsmenu -->
|
||||
<p class="privacy-policy"><strong>Privacy Policy</strong> When you're using the add-on, we collect information from sites you visit solely to show you how they're connected. This information is stored locally on your computer and you can delete it by resetting the graph. We don't give away this information to anyone except you.</p>
|
||||
</div> <!-- end of live-data -->
|
||||
<div class="demo" style="display: none;">
|
||||
<div class="step 0">
|
||||
<p>This page will show you a demo of what you might see if you were surfing the web with Collusion.</p>
|
||||
|
||||
<p><span class="next">Click here</span> to see what happens when you start your browser and visit the Internet Movie Database at imdb.com.</p>
|
||||
</div>
|
||||
<div class="step 1">
|
||||
<p>Each dot in this graph represents a website. The gray dot in the middle is imdb.com; the red dots near it are advertising sites that have created <a href="http://en.wikipedia.org/wiki/HTTP_cookie">cookies</a> in your browser and are now tracking your behavior on the IMDB.</p>
|
||||
<p>The advertising sites are colored red because <a href="http://privacychoice.org">privacychoice.org</a> has determined that they're behavioral tracking sites. Non-red sites may still track you.</p>
|
||||
|
||||
<p>Hover your mouse over any of the sites to learn more about them.</p>
|
||||
<p>When you're done, <span class="next">click here</span> to continue your web adventure. Our next stop is the New York Times website.</p>
|
||||
</div>
|
||||
<div class="step 2">
|
||||
<p>It looks like the New York Times is affiliated with some of the same advertising companies as the IMDB.</p>
|
||||
<p>Because the same cookies were transmitted to the same advertisers when you visited both sites, those advertisers effectively track you <em>across</em> them. That's valuable data for their market research.</p>
|
||||
|
||||
<p>When you're ready, <span class="next">click here</span> to visit our next stop, the Huffington Post.</p>
|
||||
</div>
|
||||
<div class="step 3">
|
||||
<p>Some companies are already using their knowledge about you to determine what you see on the sites they're affiliated with—not just the ads you see, but the actual content you read. Eli Pariser examines what this could mean for society at large in his book <b>The Filter Bubble</b>. Watch Eli's TED talk on the Filter Bubble <a href="http://www.ted.com/talks/eli_pariser_beware_online_filter_bubbles.html">here</a>.</p>
|
||||
|
||||
<p>By the way, if the graph is starting to look a bit confusing, try dragging the dots around with your mouse to get a better view.</p>
|
||||
<p>Then, <span class="next">click here</span> to go to our next stop, gamespot.com.</p>
|
||||
</div>
|
||||
<div class="step 4">
|
||||
<p>If you haven't realized it yet, companies are tracking you across most of the sites you visit daily on the web. It's quite likely that these companies know more about you than your government. Some of them might even know more about you than your best friends.</p>
|
||||
<p><span class="next">Click here</span> to visit our final stop, reference.com.</p>
|
||||
|
||||
</div>
|
||||
<div class="step 5">
|
||||
<p>Thanks for trying out this demo. If you'd like to see this graph change in real time based on the sites you visit in your own browser, feel free to try the <a href="https://secure.toolness.com/xpi/collusion.html" class="xpi-download">Collusion Firefox add-on</a>. Even visiting the sites mentioned in the demo will probably give you different visualizations, because some services you may be logged into—like Facebook—can also track you across sites.</p>
|
||||
<p>If you want to block companies from tracking you on the internet, you can install <a href="http://www.privacychoice.org/trackerblock/firefox">TrackerBlock</a> for Firefox and Internet Explorer.</p>
|
||||
|
||||
<p>Collusion was created as a prototype by Atul Varma. For more information about the making of this demo, check out the <a href="http://www.toolness.com/wp/2011/07/collusion/">blog post</a> and <a href="https://github.com/toolness/collusion">code repository</a>.</p>
|
||||
</div>
|
||||
</div> <!-- End of .demo -->
|
||||
|
||||
<a href="https://addons.mozilla.org/en-US/firefox/addon/collusion/" class="button download">Download the Collusion add-on for Firefox</a>
|
||||
|
||||
<div id="domain-infos">Mouse over any circle to see how that site is involved in tracking.</div> <!-- Templates are copied into here -->
|
||||
</div> <!-- End of #sidebar-content -->
|
||||
</div> <!-- End of #sidebar -->
|
||||
<div id="chart"></div><!-- The d3 layout graph will go inside this div -->
|
||||
</div>
|
||||
<div id="templates">
|
||||
<div class="info" style="display: none;">
|
||||
<h2 class="domain"></h2>
|
||||
|
||||
<div class="referrees">
|
||||
<p>When you visit <a class="domain"></a>, it informs the following websites about your visit.</p>
|
||||
<ul></ul>
|
||||
</div>
|
||||
<div class="referrers">
|
||||
<p>The site <a class="domain"></a> tracks your behavior across the following websites.</p>
|
||||
|
||||
<ul></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
|
@ -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)
|
|
@ -0,0 +1,7 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from views import collusion, demo
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^demo/$', demo),
|
||||
(r'^$', collusion),
|
||||
)
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
||||
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 15 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 25 KiB |
|
@ -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;
|
||||
})();
|
|
@ -0,0 +1,825 @@
|
|||
(function(){d3.geom = {};
|
||||
/**
|
||||
* Computes a contour for a given input grid function using the <a
|
||||
* href="http://en.wikipedia.org/wiki/Marching_squares">marching
|
||||
* squares</a> 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<len; ++i) {
|
||||
if (vertices[i][1] < vertices[h][1]) {
|
||||
h = i;
|
||||
} else if (vertices[i][1] == vertices[h][1]) {
|
||||
h = (vertices[i][0] < vertices[h][0] ? i : h);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate polar angles from ref point and sort
|
||||
for (i=0; i<len; ++i) {
|
||||
if (i === h) continue;
|
||||
y1 = vertices[i][1] - vertices[h][1];
|
||||
x1 = vertices[i][0] - vertices[h][0];
|
||||
points.push({angle: Math.atan2(y1, x1), index: i});
|
||||
}
|
||||
points.sort(function(a, b) { return a.angle - b.angle; });
|
||||
|
||||
// toss out duplicate angles
|
||||
a = points[0].angle;
|
||||
v = points[0].index;
|
||||
u = 0;
|
||||
for (i=1; i<plen; ++i) {
|
||||
j = points[i].index;
|
||||
if (a == points[i].angle) {
|
||||
// keep angle for point most distant from the reference
|
||||
x1 = vertices[v][0] - vertices[h][0];
|
||||
y1 = vertices[v][1] - vertices[h][1];
|
||||
x2 = vertices[j][0] - vertices[h][0];
|
||||
y2 = vertices[j][1] - vertices[h][1];
|
||||
if ((x1*x1 + y1*y1) >= (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<plen; ++j) {
|
||||
if (points[j].index === -1) continue; // skip tossed out points
|
||||
while (!d3_geom_hullCCW(stack[sp-2], stack[sp-1], points[j].index, vertices)) {
|
||||
--sp;
|
||||
}
|
||||
stack[sp++] = points[j].index;
|
||||
}
|
||||
|
||||
// construct the hull
|
||||
var poly = [];
|
||||
for (i=0; i<sp; ++i) {
|
||||
poly.push(vertices[stack[i]]);
|
||||
}
|
||||
return poly;
|
||||
}
|
||||
|
||||
// are three points in counter-clockwise order?
|
||||
function d3_geom_hullCCW(i1, i2, i3, v) {
|
||||
var t, a, b, c, d, e, f;
|
||||
t = v[i1]; a = t[0]; b = t[1];
|
||||
t = v[i2]; c = t[0]; d = t[1];
|
||||
t = v[i3]; e = t[0]; f = t[1];
|
||||
return ((f-b)*(c-a) - (d-b)*(e-a)) > 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<l; i++) {
|
||||
var next = list[i];
|
||||
if (he.ystar > 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<l && (ls[i] != he); ++i) {}
|
||||
ls.splice(i, 1);
|
||||
},
|
||||
|
||||
empty: function() { return EventQueue.list.length === 0; },
|
||||
|
||||
nextEvent: function(he) {
|
||||
for (var i=0, ls=EventQueue.list, l=ls.length; i<l; ++i) {
|
||||
if (ls[i] == he) return ls[i+1];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
min: function() {
|
||||
var elem = EventQueue.list[0];
|
||||
return {
|
||||
x: elem.vertex.x,
|
||||
y: elem.ystar
|
||||
};
|
||||
},
|
||||
|
||||
extractMin: function() {
|
||||
return EventQueue.list.shift();
|
||||
}
|
||||
};
|
||||
|
||||
EdgeList.init();
|
||||
Sites.bottomSite = Sites.list.shift();
|
||||
|
||||
var newSite = Sites.list.shift(), newIntStar;
|
||||
var lbnd, rbnd, llbnd, rrbnd, bisector;
|
||||
var bot, top, temp, p, v;
|
||||
var e, pm;
|
||||
|
||||
while (true) {
|
||||
if (!EventQueue.empty()) {
|
||||
newIntStar = EventQueue.min();
|
||||
}
|
||||
if (newSite && (EventQueue.empty()
|
||||
|| newSite.y < newIntStar.y
|
||||
|| (newSite.y == newIntStar.y
|
||||
&& newSite.x < newIntStar.x))) { //new site is smallest
|
||||
lbnd = EdgeList.leftBound(newSite);
|
||||
rbnd = EdgeList.right(lbnd);
|
||||
bot = EdgeList.rightRegion(lbnd);
|
||||
e = Geom.bisect(bot, newSite);
|
||||
bisector = EdgeList.createHalfEdge(e, "l");
|
||||
EdgeList.insert(lbnd, bisector);
|
||||
p = Geom.intersect(lbnd, bisector);
|
||||
if (p) {
|
||||
EventQueue.del(lbnd);
|
||||
EventQueue.insert(lbnd, p, Geom.distance(p, newSite));
|
||||
}
|
||||
lbnd = bisector;
|
||||
bisector = EdgeList.createHalfEdge(e, "r");
|
||||
EdgeList.insert(lbnd, bisector);
|
||||
p = Geom.intersect(bisector, rbnd);
|
||||
if (p) {
|
||||
EventQueue.insert(bisector, p, Geom.distance(p, newSite));
|
||||
}
|
||||
newSite = Sites.list.shift();
|
||||
} else if (!EventQueue.empty()) { //intersection is smallest
|
||||
lbnd = EventQueue.extractMin();
|
||||
llbnd = EdgeList.left(lbnd);
|
||||
rbnd = EdgeList.right(lbnd);
|
||||
rrbnd = EdgeList.right(rbnd);
|
||||
bot = EdgeList.leftRegion(lbnd);
|
||||
top = EdgeList.rightRegion(rbnd);
|
||||
v = lbnd.vertex;
|
||||
Geom.endPoint(lbnd.edge, lbnd.side, v);
|
||||
Geom.endPoint(rbnd.edge, rbnd.side, v);
|
||||
EdgeList.del(lbnd);
|
||||
EventQueue.del(rbnd);
|
||||
EdgeList.del(rbnd);
|
||||
pm = "l";
|
||||
if (bot.y > 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]
|
||||
};
|
||||
}
|
||||
})();
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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;
|
||||
})();
|
|
@ -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 = $('<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 = $('<li><a></a></li>');
|
||||
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 = $('<li><a></a></li>');
|
||||
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 <g>
|
||||
* containing a <circle> and an <image>, 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 <g> to hold circle, image, and title
|
||||
var gs = node.enter().append("svg:g")
|
||||
.attr("class", "node")
|
||||
.attr("transform", function(d) {
|
||||
// <g> 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);
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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',
|
||||
|
|
1
urls.py
1
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')),
|
||||
|
|
Загрузка…
Ссылка в новой задаче