├── .gitignore ├── LICENSE ├── Readme.md ├── Traduction.md ├── analyses ├── favicons.mjs ├── requests.mjs └── websites.mjs ├── browser.config.js ├── browser.html ├── core ├── browser.js ├── cookieviz.js ├── database.js ├── dropdown.js ├── error.js ├── language.js ├── plugins.js └── visit.js ├── css ├── all.css ├── bootstrap.min.css ├── bootstrap.min.css.map ├── browser.css ├── cookieviz.css ├── hist.css ├── voronoi.css └── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.svg │ ├── fa-brands-400.ttf │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.svg │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.eot │ ├── fa-solid-900.svg │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ └── fa-solid-900.woff2 ├── data └── adv.json ├── en.cookieviz.html ├── es.cookieviz.html ├── fr.cookieviz.html ├── icons ├── browser.icns ├── browser.ico ├── browser_128x128.png ├── browser_16x16.png ├── browser_256x256.png ├── browser_512x512.png ├── browser_64x64.png └── empty_favicon.png ├── installer.nsi ├── js ├── angular-cookies.min.js ├── angular-translate-loader-static-files.min.js ├── angular-translate-storage-cookie.min.js ├── angular-translate-storage-local.min.js ├── angular-translate.min.js ├── angular.min.js ├── bootstrap.min.js ├── bootstrap.min.js.map ├── d3-legend.min.js ├── d3-scale-chromatic.v1.min.js ├── d3-voronoi-map.js ├── d3-voronoi-treemap.js ├── d3-weighted-voronoi.js ├── d3.v4.min.js └── jquery-3.5.1.min.js ├── languages ├── en.json ├── es.json ├── fr.json └── pt.json ├── package.json ├── parameters ├── clean_data.js └── visit_path.js ├── pt.cookieviz.html └── visualization ├── export.js ├── graph.js ├── hist.js ├── search.js └── voronoi.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | CookieViz (Français) 2 | === 3 | 4 | **CookieViz est un outil de visualisation qui permet de mesurer l'impact des cookies lors de votre propre navigation.** 5 | 6 | Cookieviz analyse les interactions entre un navigateur et des sites et serveurs distants. Vous pourrez savoir à quels autres acteurs le site que vous visitez envoie des informations. 7 | 8 | 9 | ## Utilisation 10 | 11 | Assurez-vous d'avoir la plateforme logicielle [node.js](https://nodejs.org/fr/download/) et [git](https://git-scm.com/downloads) installés sur votre système. 12 | 13 | 1. Depuis un terminal, récupérer le code de CookieViz : 14 | 15 | * ``git clone https://github.com/LINCnil/CookieViz`` 16 | 17 | 2. Configurer et lancer CookieViz : 18 | 19 | * ``npm install`` 20 | 21 | * ``npm run start`` 22 | 23 | 24 | Vous pouvez également générer des binaires de CookieViz pour les plateformes Windows, Mac et Linux en utilisant la commande ``npm run dist``. 25 | 26 | ## Contribuer 27 | 28 | **CookieViz est disponible sous license GPLv3 et peut être enrichi par chacun des utilisateurs.** Les plus expérimentés peuvent améliorer cette version initiale de notre outil ou corriger d’éventuels bugs. N'oubliez pas de soumettre vos contributions via des pull-requests. 29 | 30 | **Vous avez une idée que vous souhaitez partager avec nous pour améliorer ce projet ?** Vous avez envie de vous appuyer sur cette base pour construire un projet de pédagogie de la traçabilité numérique ? Contactez l’équipe du laboratoire CNIL par mail - ip(at)cnil.fr - ou via le compte Twitter [@LINCnil](https://twitter.com/LINCnil). 31 | 32 | Pour de plus amples informations, voir le fichier ``LICENSE`` inclus. 33 | 34 | # CookieViz (English) 35 | 36 | **CookieViz is a vizualization tool allowing to view cookies impact during your browsing.** 37 | 38 | CookieViz analyzes the interactions between your web browser and the remote servers and websites. You will be able to know to which other player the site you're visiting sends information. 39 | 40 | ## Usage 41 | 42 | Make sure you have the software platform [node.js] (https://nodejs.org/en/download/) and [git] (https://git-scm.com/downloads) installed on your system. 43 | 44 | 1. From a terminal, retrieve the CookieViz code: 45 | 46 | * ``git clone https://github.com/LINCnil/CookieViz`` 47 | 48 | 2. Configure and launch CookieViz: 49 | 50 | * ``npm install`` 51 | 52 | * ``npm run start`` 53 | 54 | 55 | You can also generate CookieViz binaries for Windows, Mac and Linux platforms using the ``npm run dist'' command. 56 | 57 | 58 | ## Contribute 59 | **CookieViz is available under the terms of the GPLv3 license and can be enriched by any of its users.** The most experimented can improve this initial version of our tool or correct potential bugs. Don't forget to submit your contributions *via* pull-requests. 60 | 61 | **You have an idea you wish to share with us to improve this project ?** You want to rely on this base to build a teaching project on numerical traceability ? Contact the team of the CNIL lab by mail - ip(at)cnil.fr - or *via* the Twitter account [@CNIL](https://twitter.com/CNIL). 62 | 63 | For more information, see the `LICENSE` file included. 64 | -------------------------------------------------------------------------------- /Traduction.md: -------------------------------------------------------------------------------- 1 | Traduire CookieViz (Français) 2 | === 3 | 4 | La traduction de CookieViz se déroule en quelques étapes : 5 | * Copier/coller un fichier .json depuis le répertoire languages (fr.json/en.json) 6 | * Traduiser les contenus en respectant la structure et les noms des variables 7 | * Enregistrer le fichier sous la forme XX.json, XX correspondant deux caractères d'une langue suivant le standard [ISO 639-1](https://fr.wikipedia.org/wiki/Liste_des_codes_ISO_639-1) 8 | * Metter à jour la section "languages" du fichier browser.config.js à la racine de ce projet pour référencer cette nouvelle traduction. 9 | 10 | Envoyez nous vos contributions au travers via des "pull requests" ! 11 | 12 | 13 | Translate CookieViz (Français) 14 | === 15 | 16 | The translation of CookieViz takes place in a few steps: 17 | * Copy / paste a .json file from the languages ​​directory (fr.json / en.json) 18 | * Translate the contents while respecting the structure and the names of the variables 19 | * Save the file as XX.json, XX corresponding to two characters of a language according to the [ISO 639-1] standard (https://fr.wikipedia.org/wiki/Liste_des_codes_ISO_639-1) 20 | * Update the "languages" section of the browser.config.js file at the root of this project with the new translation. 21 | 22 | Send us your contributions through "pull requests"! -------------------------------------------------------------------------------- /analyses/favicons.mjs: -------------------------------------------------------------------------------- 1 | const psl = require('psl'); 2 | const getFavicons = require('get-website-favicon'); 3 | 4 | const favicons_table_column = { 5 | "favicons": [ 6 | "website" 7 | ] 8 | } 9 | 10 | const favicons_stored = []; 11 | 12 | function extractHostname(url, keep_protocol) { 13 | let hostname; 14 | 15 | if (!url) return ""; 16 | 17 | //find & remove protocol (http, ftp, etc.) and get hostname 18 | if (url.indexOf("//") > -1) { 19 | hostname = url.split('/')[2]; 20 | } 21 | else { 22 | hostname = url.split('/')[0]; 23 | } 24 | 25 | //find & remove port number 26 | hostname = hostname.split(':')[0]; 27 | //find & remove "?" 28 | hostname = hostname.split('?')[0]; 29 | 30 | return hostname; 31 | } 32 | 33 | 34 | function processFaviconRequest(requestdetails) { 35 | if (!db) return; 36 | 37 | //Parsing all cookies 38 | let initiator = psl.get(extractHostname(requestdetails.initiator)); 39 | let request_url = psl.get(extractHostname(requestdetails.url)); 40 | 41 | function checkFavicon(request) { 42 | favicons_stored.push(request); 43 | let txn = db.transaction(["favicons"], 'readonly'); 44 | let objectStore = txn.objectStore('favicons'); 45 | var index = objectStore.index('website'); 46 | 47 | index.get(request).onsuccess = function (event) { 48 | var cursor = event.target.result; 49 | if (cursor) { 50 | return; 51 | } else { 52 | getFavicons(request).then(data => { 53 | if (data.icons.length > 0) { 54 | WriteToDb("favicons", { website: request, favicon: data.icons[0].src }); 55 | } else { 56 | WriteToDb("favicons", { website: request, favicon: null }); 57 | } 58 | }); 59 | } 60 | }; 61 | } 62 | 63 | if (initiator && !favicons_stored.includes(initiator)) checkFavicon(initiator); 64 | if (request_url && !favicons_stored.includes(request_url)) checkFavicon(request_url); 65 | } 66 | 67 | function initFaviconCrawler() { 68 | // Read cookie 69 | chrome.webRequest.onBeforeSendHeaders.addListener(processFaviconRequest, { 70 | urls: ["*://*/*"] 71 | }, [] 72 | ); 73 | } 74 | 75 | function deleteFaviconCrawler() { 76 | chrome.webRequest.onBeforeSendHeaders.removeListener(processFaviconRequest); 77 | } 78 | 79 | async function get_all_favicons() { 80 | return new Promise((resolve, reject) => { 81 | const favicons = {}; 82 | let txn = db.transaction(["favicons"], 'readonly'); 83 | const objectStore = txn.objectStore("favicons"); 84 | objectStore.openCursor().onsuccess = function (event) { 85 | var cursor = event.target.result; 86 | if (cursor) { 87 | favicons[cursor.value.website]= cursor.value.favicon; 88 | cursor.continue(); 89 | } else { 90 | resolve(favicons); 91 | } 92 | } 93 | }); 94 | } 95 | 96 | 97 | // Template for plugins 98 | const plugins_favicon = { 99 | name: "favicons", 100 | description: "This plugins stores favicons of every website that send/receive requests", 101 | author: "linc", 102 | tables: favicons_table_column, 103 | init: initFaviconCrawler, 104 | delete: deleteFaviconCrawler, 105 | data: { 106 | "get_all_favicons": get_all_favicons 107 | } 108 | } 109 | 110 | 111 | 112 | // Entry point of plugins 113 | export function plugins() { 114 | return plugins_favicon; 115 | } 116 | 117 | 118 | -------------------------------------------------------------------------------- /analyses/requests.mjs: -------------------------------------------------------------------------------- 1 | const psl = require('psl'); 2 | 3 | var current_page = null; 4 | var new_page = null; 5 | 6 | // Requests tables forms 7 | const requests_table_column = { 8 | //Standard request fields 9 | request_table: ['page', 'url', 'referer', 'cookie'] 10 | } 11 | 12 | // Crawler analyses 13 | function getBaseDomain(full_url) { 14 | if (full_url == null) return null; 15 | return psl.parse(full_url).domain; 16 | } 17 | 18 | function extractCookies(cookies) { 19 | if (!cookies) return {}; 20 | return cookies.split(/; */).reduce((obj, str) => { 21 | if (str === "") return obj; 22 | const eq = str.indexOf('='); 23 | const key = eq > 0 ? str.slice(0, eq) : str; 24 | let val = eq > 0 ? str.slice(eq + 1) : null; 25 | if (val != null) try { 26 | val = decodeURIComponent(val); 27 | } catch (ex) { 28 | //console.log(ex); 29 | } 30 | obj[key] = val; 31 | return obj; 32 | }, {}); 33 | } 34 | 35 | // Parsing cookie header 36 | function parseCookies(cookie_header_strs) { 37 | const cookies = {}; 38 | 39 | if (Array.isArray(cookie_header_strs)) { 40 | for (const cookie_header_str of cookie_header_strs) { 41 | Object.assign(cookies, extractCookies(cookie_header_str)); 42 | } 43 | return cookies; 44 | } 45 | return extractCookies(cookie_header_strs); 46 | } 47 | 48 | 49 | 50 | function parseHeader(table, headerdetails) { 51 | const results = {}; 52 | 53 | for (var i = 0; i < headerdetails.length; i++) { 54 | const header = headerdetails[i]; 55 | const name = header.name.toLowerCase(); 56 | if (!table.includes(name)) { 57 | continue; 58 | } 59 | if (name in results) { 60 | if (!Array.isArray(results[name])) { 61 | // Transform this place into an array 62 | results[name] = [results[name]]; 63 | } 64 | 65 | results[name].push(header.value); 66 | } else { 67 | results[name] = header.value; 68 | } 69 | } 70 | return results; 71 | } 72 | 73 | function processRequest(requestdetails) { 74 | const headers = parseHeader(['cookie', 'referer'], requestdetails.requestHeaders); 75 | 76 | if (!current_page && requestdetails.initiator) { 77 | if (new URL(requestdetails.initiator).hostname == new_page.hostname) { 78 | current_page = new_page; 79 | new_page = null; 80 | } 81 | } 82 | 83 | if (!current_page) return; 84 | 85 | const referer = 'referer' in headers ? new URL(headers['referer']).hostname : null; 86 | 87 | WriteToDb("request_table", { page: current_page.hostname, url: new URL(requestdetails.url).hostname, referer: referer, cookie: headers['cookie'], timestamp: requestdetails.timeStamp }); 88 | } 89 | 90 | function initRequestsCrawler() { 91 | // Read cookie 92 | nwjsBrowser.request.onBeforeSendHeaders.addListener(processRequest, { 93 | urls: ["*://*/*"] 94 | }, ['requestHeaders', 'extraHeaders'] 95 | ); 96 | 97 | nwjsBrowser.addEventListener("new_page", function () { 98 | current_page = null; 99 | new_page = new URL(nwjsBrowser.src); 100 | }); 101 | } 102 | 103 | function deleteRequestsCrawler() { 104 | nwjsBrowser.request.onBeforeSendHeaders.removeListener(processRequest); 105 | } 106 | 107 | 108 | function getNodes(table, index) { 109 | if (!db) return; 110 | 111 | return new Promise((resolve, reject) => { 112 | 113 | let txn = db.transaction([table], 'readonly'); 114 | 115 | function get_requested() { 116 | return new Promise((resolve, reject) => { 117 | const objectStore = txn.objectStore(table); 118 | 119 | if (index){ 120 | objectStore = objectStore.index(index); 121 | } 122 | 123 | let visited = new Set(); 124 | let requested = new Set(); 125 | 126 | objectStore.openCursor().onsuccess = function (event) { 127 | const cursor = event.target.result; 128 | if (cursor) { 129 | const page = getBaseDomain(cursor.value.page); 130 | const referer = getBaseDomain(cursor.value.referer); 131 | const url = getBaseDomain(cursor.value.url); 132 | 133 | visited.add(page); 134 | requested.add(referer); 135 | requested.add(url); 136 | cursor.continue(); 137 | } else { 138 | resolve([Array.from(visited), Array.from(requested)]); 139 | } 140 | } 141 | }); 142 | } 143 | 144 | get_requested().then((values) => { 145 | const visited = values[0]; 146 | resolve([...new Set(values.flat())] 147 | .filter(x => x) 148 | .map(x => ({ 'id': x, 'visited': visited.includes(x) ? 1 : 0 }))); 149 | }); 150 | }); 151 | } 152 | 153 | function getAllNodes(table, index) { 154 | if (!db) return; 155 | 156 | let txn = db.transaction([table], 'readonly'); 157 | let objectStore = txn.objectStore("request_table"); 158 | 159 | const promises = index.map (x => 160 | new Promise((resolve, reject) => { 161 | let url = new Set(); 162 | const index = objectStore.index(x); 163 | index.openCursor().onsuccess = function(event) { 164 | const cursor = event.target.result; 165 | if(cursor) { 166 | url.add(getBaseDomain(cursor.key)); 167 | cursor.continue(); 168 | }else{ 169 | resolve([...url]); 170 | } 171 | 172 | } 173 | }) 174 | ); 175 | 176 | return new Promise((resolve, reject) => { 177 | Promise.all(promises).then((values)=>{ 178 | const visited = values[0]; 179 | resolve([...new Set(values.flat())] 180 | .filter(x => x) 181 | .map(x => ({ 'id': x, 'visited': visited.includes(x) ? 1 : 0 }))); 182 | }); 183 | }); 184 | } 185 | 186 | function getLinks(table, index) { 187 | if (!db) return; 188 | 189 | let txn = db.transaction([table], 'readonly'); 190 | 191 | return new Promise((resolve, reject) => { 192 | let links = []; 193 | let objectStore = txn.objectStore(table); 194 | 195 | if (index){ 196 | objectStore = objectStore.index(index); 197 | } 198 | 199 | objectStore.openCursor().onsuccess = function (event) { 200 | const cursor = event.target.result; 201 | if (cursor) { 202 | const source = getBaseDomain(cursor.value.referer); 203 | const target = getBaseDomain(cursor.value.url); 204 | const page = getBaseDomain(cursor.value.page); 205 | if (source && target && source != target) { 206 | const link = links.find(x => x.source == source && x.target == target); 207 | const cookies = parseCookies(cursor.value.cookie); 208 | if (link){ 209 | Object.assign(link.cookie,cookies); 210 | if(!link.page.includes(page)){ 211 | link.page.push(page); 212 | } 213 | 214 | }else{ 215 | links.push({ source: source, target: target, cookie: cookies, page : [page]}); 216 | } 217 | 218 | } 219 | cursor.continue(); 220 | } else { 221 | resolve(links); 222 | } 223 | } 224 | }); 225 | } 226 | 227 | 228 | // Entry point of the analyzes 229 | const requests = { 230 | name: "requests", 231 | description: "This plugins analyzes requests and stores cookies", 232 | author: "linc", 233 | init: initRequestsCrawler, 234 | delete: deleteRequestsCrawler, 235 | tables: requests_table_column, 236 | data: { 237 | "link_requests": getLinks.bind(null, "request_table"), 238 | "nodes_requests": getAllNodes.bind(null, "request_table", ["page", "referer", "url"]), 239 | "nodes_requests_with_cookies": getNodes.bind(null, "request_table", "cookie"), 240 | "link_requests_with_cookies": getLinks.bind(null, "request_table", "cookie"), 241 | } 242 | } 243 | 244 | export function plugins() { 245 | return requests; 246 | } -------------------------------------------------------------------------------- /analyses/websites.mjs: -------------------------------------------------------------------------------- 1 | const { parseAdsTxt } = require('ads.txt'); 2 | const psl = require('psl'); 3 | 4 | const websites_table_column = { 5 | websites_visited:[ 6 | "url" 7 | ], 8 | websites_adstxt:[ 9 | ] 10 | } 11 | 12 | var website = null; 13 | var index = null; 14 | 15 | // Data handling 16 | function getVisitedList(){ 17 | if (!db) return; 18 | 19 | let txn = db.transaction(["websites_visited"], 'readonly'); 20 | 21 | return new Promise((resolve, reject) => { 22 | const objectStore = txn.objectStore("websites_visited"); 23 | 24 | let urls = []; 25 | 26 | objectStore.openCursor().onsuccess = function (event) { 27 | const cursor = event.target.result; 28 | if (cursor) { 29 | urls.push(cursor.value.request_url); 30 | cursor.continue(); 31 | } else { 32 | resolve(urls); 33 | } 34 | } 35 | }); 36 | } 37 | 38 | function getAds() { 39 | if (!db) return; 40 | 41 | let txn = db.transaction(["websites_adstxt"], 'readonly'); 42 | 43 | return new Promise((resolve, reject) => { 44 | const objectStore = txn.objectStore("websites_adstxt"); 45 | 46 | let ads = []; 47 | 48 | objectStore.openCursor().onsuccess = function (event) { 49 | const cursor = event.target.result; 50 | if (cursor) { 51 | ads.push(cursor.value.domain); 52 | cursor.continue(); 53 | } else { 54 | resolve(ads); 55 | } 56 | } 57 | }); 58 | } 59 | 60 | 61 | // Crawler analyses 62 | function storeAdsTxt(url){ 63 | fetch(url).then(function(response) { 64 | if(response.ok) { 65 | response.text().then(function(ads_txt) { 66 | let { variables, fields } = parseAdsTxt(ads_txt); 67 | fields.forEach(field => { 68 | WriteToDb("websites_adstxt", {domain: field.domain, publisherAccountID: field.publisherAccountID, accountType: field.accountType, certificateAuthorityID: field.certificateAuthorityID}); 69 | }); 70 | }); 71 | } 72 | }) 73 | .catch(function(error) { 74 | //In case of error 75 | console.log("errror:" +error.message); 76 | }); 77 | } 78 | 79 | function website_loaded(){ 80 | let full_url= new URL(nwjsBrowser.src); 81 | let host = psl.parse(full_url.hostname).domain; 82 | 83 | WriteToDb("websites_visited", {request_url:host, full_url:full_url.href}); 84 | 85 | //Check if ads.txt/app-ads.txt is present 86 | let host_name = full_url.protocol+'//'+full_url.hostname; 87 | storeAdsTxt(host_name+"/ads.txt"); 88 | storeAdsTxt(host_name+"/app-ads.txt"); 89 | } 90 | 91 | function initWebsitesAnalyses(){ 92 | nwjsBrowser.addEventListener("contentload", website_loaded); 93 | } 94 | 95 | function eraseWebsitesAnalyses(){ 96 | nwjsBrowser.removeEventListener("contentload", website_loaded); 97 | } 98 | 99 | // Entry point of the analyzes 100 | const websites = { 101 | name : "websites", 102 | description : "This plugins stores visited website and there associated ads.txt", 103 | author:"linc", 104 | tables:websites_table_column, 105 | init:initWebsitesAnalyses, 106 | delete:eraseWebsitesAnalyses, 107 | data:{ 108 | "visited_list": getVisitedList, 109 | "adstxt_list": getAds 110 | } 111 | } 112 | 113 | export function plugins() { 114 | return websites; 115 | } 116 | 117 | 118 | -------------------------------------------------------------------------------- /browser.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | "homepage": "https://www.cnil.fr", 3 | "name": "CookieViz", 4 | "showDevTools": false, 5 | "analyses": [ 6 | "websites", 7 | "requests", 8 | "favicons" 9 | ], 10 | "languages": { 11 | 'en_*': 'en', 12 | 'es_*': 'es', 13 | 'fr_*': 'fr', 14 | 'pt_*': 'pt' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | 22 | 23 | 24 | 25 | 64 | 65 | 103 | 104 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /core/browser.js: -------------------------------------------------------------------------------- 1 | var gui = require('nw.gui'); 2 | var win = gui.Window.get(); 3 | 4 | const new_page_event = new Event('new_page'); 5 | 6 | //Viz container 7 | const viz_windows = []; 8 | 9 | window.addEventListener( 10 | 'DOMContentLoaded', 11 | initBrowser 12 | ); 13 | 14 | function initBrowser(){ 15 | if(config.homepage.indexOf('//')<0){ 16 | config.homepage='http://'+config.homepage; 17 | } 18 | 19 | if(config.showDevTools){ 20 | setTimeout( 21 | function(){ 22 | win.showDevTools(); 23 | }, 24 | 2000 25 | ); 26 | } 27 | 28 | window.nwjsHeader=document.querySelector('#navigation-bar'); 29 | window.nwjsBrowser=document.querySelector('#browser'); 30 | window.nwjsVisitList=document.querySelector('#visit-list'); 31 | window.nwjsVisitBar=document.querySelector('#visit-bar'); 32 | 33 | nwjsHeader.addEventListener( 34 | 'click', 35 | navigate 36 | ); 37 | 38 | nwjsHeader.addEventListener( 39 | 'keyup', 40 | go 41 | ); 42 | 43 | setTimeout( 44 | function(){ 45 | win.title=config.name; 46 | nwjsHeader.querySelector('#address').value=config.homepage; 47 | nwjsBrowser.src=config.homepage; 48 | nwjsBrowser.dispatchEvent(new_page_event); 49 | setTimeout( 50 | function(){ 51 | nwjsBrowser.style.opacity=1; 52 | },600 53 | ); 54 | nwjsHeader.style.opacity=1; 55 | nwjsBrowser.addEventListener("contentload", function () { 56 | nwjsHeader.querySelector('#address').value=nwjsBrowser.src; 57 | }); 58 | },300 59 | ); 60 | } 61 | 62 | function go(e){ 63 | if(e.keyCode!==13){ 64 | return; 65 | } 66 | 67 | if(e.target.value.indexOf('//')<0){ 68 | e.target.value='http://'+e.target.value; 69 | } 70 | 71 | nwjsBrowser.src=e.target.value; 72 | nwjsBrowser.dispatchEvent(new_page_event); 73 | } 74 | 75 | async function closeBrowser(){ 76 | try { 77 | viz_windows.forEach(viz_win => viz_win.close()); 78 | viz_windows.length = 0; 79 | win.hide(); // Pretend to be closed already 80 | console.log("browser -> En cours de fermeture..."); 81 | await unloadAnalysis(); 82 | win.close(true); // then close it forcefully 83 | } catch (error){ 84 | console.log("browser -> "+error); 85 | // Just in case something happened... 86 | win.close(true); 87 | } 88 | } 89 | 90 | win.on('close', function () { 91 | closeBrowser(); 92 | }); 93 | 94 | 95 | //Sortable chart windows 96 | function openViz () { 97 | nw.Window.open("./"+currentLang+".cookieviz.html",{ 98 | position: 'mouse', 99 | width: 1024, 100 | height: 768 101 | }, function(new_win) { 102 | new_win.requestAttention(true); 103 | new_win.setResizable(true); 104 | viz_windows.push(new_win); 105 | }); 106 | } 107 | 108 | 109 | function navigate(e){ 110 | // Set action depending on click event 111 | switch(e.target.id){ 112 | case 'back' : 113 | nwjsBrowser.back( () => nwjsBrowser.dispatchEvent(new_page_event)); 114 | 115 | break; 116 | case 'forward' : 117 | nwjsBrowser.forward(() => nwjsBrowser.dispatchEvent(new_page_event)); 118 | break; 119 | case 'refresh' : 120 | nwjsBrowser.src=nwjsBrowser.src; 121 | break; 122 | case 'address' : 123 | e.target.select(); 124 | break; 125 | case 'viz': 126 | openViz(); 127 | break; 128 | case 'language': 129 | case 'settings': 130 | openDropdown(e.target.id); 131 | return 132 | } 133 | 134 | // Close dropdown if opened 135 | closeDropdown(nwjsBrowser.contentWindow); 136 | } 137 | 138 | 139 | -------------------------------------------------------------------------------- /core/cookieviz.js: -------------------------------------------------------------------------------- 1 | var filter_node = null; 2 | var nb_visited = 0; 3 | var nb_visited_with_cookie = 0; 4 | var nb_third = 0; 5 | var most_third = [0, 0]; 6 | var rest_third = [0, 0]; 7 | 8 | 9 | const DELAY = 2000; 10 | 11 | window.addEventListener( 12 | 'DOMContentLoaded', 13 | initCookieViz 14 | ); 15 | 16 | function initCookieViz() { 17 | 18 | window.nwjsHeader = document.querySelector('#navigation-bar'); 19 | window.nwjsCookieViz = document.querySelector('#cookieviz'); 20 | window.nwjsLegend = document.querySelector('#legend-bar'); 21 | window.nwjsHist = document.querySelector('#hist'); 22 | window.nwjsTreemap = document.querySelector('#treemap'); 23 | 24 | setTimeout( 25 | function () { 26 | setTimeout( 27 | function () { 28 | nwjsCookieViz.style.opacity = 1; 29 | }, 600 30 | ); 31 | nwjsHeader.style.opacity = 1; 32 | config.analyses.forEach(function (plug) { 33 | import("../../analyses/" + plug + ".mjs").then(loaded_plug => { 34 | loaded_plugins[loaded_plug.plugins().name] = loaded_plug.plugins().data; 35 | if (Object.keys(loaded_plugins).length == config.analyses.length) LoadViz(); //we are ready 36 | }); 37 | }); 38 | }, 300 39 | ); 40 | } 41 | 42 | 43 | function getDistrib(links) { 44 | const reverse_map = {}; 45 | 46 | links.forEach(x => { 47 | if(!(x.page in reverse_map)){ 48 | reverse_map[x.page] = []; 49 | } 50 | 51 | if (!reverse_map[x.page].includes(x.target)){ 52 | reverse_map[x.page].push(x.target); 53 | } 54 | }) 55 | 56 | const distrib = Object.keys(reverse_map) 57 | .map((x) => ({ site: x, value: Object.keys(reverse_map[x]).length })) 58 | .sort((a, b) => b.value - a.value); 59 | 60 | const threshold_max = Math.round(distrib.length * 0.8); 61 | const threshold_min = Math.round(distrib.length * 0.2); 62 | 63 | var range_min = 0; 64 | var range_max = 0; 65 | 66 | if (distrib[threshold_min]) 67 | range_min = distrib[threshold_min].value; 68 | 69 | if (distrib[threshold_max]) 70 | range_max = distrib[threshold_max].value; 71 | 72 | most_third = [Math.max(...distrib.map(x => x.value)), range_min]; 73 | rest_third = [0, range_min]; 74 | 75 | return distrib; 76 | } 77 | 78 | function getMostPresent(nodes, links, adstxt_list) { 79 | const reverse = {}; 80 | const trigger = 1; 81 | const global_count = nodes.filter(x => x.visited == 1).length; 82 | const cookies = {}; 83 | 84 | // Reverse source and target 85 | links.forEach(x => { 86 | const target = x.target.id? x.target.id : x.target; 87 | const pages = x.page; 88 | 89 | if(!(target in reverse)){ 90 | reverse[target] = new Set(); 91 | } 92 | 93 | if(!(target in cookies)){ 94 | cookies[target] = new Set(); 95 | } 96 | 97 | pages.forEach(page => reverse[target].add(page)); 98 | Object.keys(x.cookie).forEach(cookie => cookies[target].add(cookie)); 99 | }) 100 | 101 | // Categorize results 102 | const to_study = Object.keys(reverse) 103 | .filter(x => 100 * reverse[x].size / global_count >= trigger); 104 | 105 | 106 | // Computing region 107 | const ads_list = to_study 108 | .filter(x => adstxt_list.includes(x)) 109 | .map(x => ({ 110 | name: x, 111 | code: x.substring(0, 2), 112 | cookie: [...cookies[x]], 113 | ads: 1, 114 | weight: (100 * reverse[x].size / global_count).toFixed(2) 115 | })) // percent of third domain presence 116 | .sort((first, second) => second.weight - first.weight); 117 | 118 | const nonads_list = to_study 119 | .filter(x => !adstxt_list.includes(x)) 120 | .map(x => ({ 121 | name: x, 122 | code: x.substring(0, 2), 123 | cookie: [...cookies[x]], 124 | ads: 0, 125 | weight: (100 * reverse[x].size / global_count).toFixed(2) 126 | })) // percent of third domain presence 127 | .sort((first, second) => second.weight - first.weight); 128 | 129 | const voronoir = { children: [] }; 130 | 131 | voronoir.children.push({ name: "La politique de confidentialité indique une finalité publicitaire", color: "#fb8761", children: ads_list }); 132 | voronoir.children.push({ name: "La politique de confidentialité n'indique pas de finalité publicitaire", color: "#b5367a", children: nonads_list }); 133 | return voronoir; 134 | } 135 | 136 | const nodes = []; 137 | const links = []; 138 | 139 | 140 | async function UpdateViz() { 141 | 142 | let refreshed_nodes = await loaded_plugins.requests.nodes_requests(); 143 | let refreshed_links = await loaded_plugins.requests.link_requests_with_cookies(); 144 | const adstxt_list = await loaded_plugins.websites.adstxt_list(); 145 | 146 | 147 | if (!refreshed_nodes || !refreshed_links || !adstxt_list){ 148 | setTimeout(UpdateViz, 100); 149 | return; 150 | } 151 | 152 | try { 153 | if (filter_node != null) { 154 | const subgraph_links = refreshed_links.filter(x => x.page.includes(filter_node)); 155 | const subgraph_nodeset = new Set(); 156 | subgraph_nodeset.add(filter_node); 157 | subgraph_links.forEach(x => subgraph_nodeset.add(x.source)); 158 | subgraph_links.forEach(x => subgraph_nodeset.add(x.target)); 159 | 160 | const subgraph_nodes = Array.from(subgraph_nodeset) 161 | .map(x => refreshed_nodes.find(elt => elt.id == x)); 162 | refreshed_nodes = subgraph_nodes; 163 | refreshed_links = subgraph_links; 164 | } 165 | 166 | const nodes_to_add = refreshed_nodes.filter(x => !nodes.some(y => y.id == x.id)); 167 | const links_to_add = refreshed_links.filter(x => !links.some(y => y.source.id == x.source && y.target.id == x.target)); 168 | const nodes_to_remove = nodes.filter(x => !refreshed_nodes.some(y => y.id == x.id)); 169 | const links_to_remove = links.filter(x => !refreshed_links.some(y => y.source == x.source.id && y.target == x.target.id)); 170 | 171 | // Update graph 172 | nodes_to_add.forEach(x => nodes.push(x)); 173 | links_to_add.forEach(x => links.push(x)); 174 | nodes_to_remove.forEach(x => nodes.splice(nodes.findIndex(y => y.id == x.id), 1)); 175 | links_to_remove.forEach(x => links.splice(nodes.findIndex(y => y => y.source.id == x.source && y.target.id == x.target), 1)); 176 | 177 | if (nodes_to_add.length > 0 || 178 | links_to_add.length > 0 || 179 | nodes_to_remove > 0 || 180 | links_to_remove.length) { 181 | const favicons = await loaded_plugins.favicons.get_all_favicons(); 182 | updateValues(favicons, refreshed_nodes, refreshed_links); 183 | 184 | update_graph(nodes, links); 185 | update_hist(getDistrib(refreshed_links), nwjsHist.getBoundingClientRect()); 186 | update_voronoi(getMostPresent(refreshed_nodes, refreshed_links, adstxt_list)); 187 | } 188 | setTimeout(UpdateViz, DELAY); 189 | }catch(e){ 190 | console.log("update error : " + e + "!") // The database is in an intermediate state wipe everything is start over 191 | simulation.stop(); 192 | nodes.splice(0,nodes.length); 193 | links.splice(0,links.length); 194 | update_graph(nodes, links); 195 | setTimeout(UpdateViz, 100); 196 | } 197 | } 198 | 199 | function updateValues(favicons, nodes, links) { 200 | nodes.forEach(x => (x.id in favicons) ? x.icon = favicons[x.id] : null); 201 | 202 | if (filter_node == null) { 203 | nb_visited = nodes.filter(x => x.visited == 1).length; 204 | const thirds = Array.from(new Set(links.map(x => x.target))); 205 | const firsts = Array.from(new Set(links.map(x => x.page).flat())); 206 | nb_visited_with_cookie = Math.round(100 * firsts.length / nb_visited); 207 | nb_third = thirds.length; 208 | autocomplete(document.getElementById("searchVisited"), firsts); 209 | } 210 | 211 | } 212 | 213 | async function LoadViz() { 214 | initDb(); 215 | load_graph([], [], nwjsCookieViz.getBoundingClientRect(), -200, 1); 216 | load_voronoi([], nwjsHist.getBoundingClientRect()); 217 | load_hist([], nwjsHist.getBoundingClientRect()); 218 | document.querySelector('#cookieviz').style.opacity = 1; 219 | UpdateViz(); 220 | } 221 | 222 | var app = angular.module('cookieVizApp', []); 223 | 224 | app.controller('cookieVizCtrl', function ($scope, $interval) { 225 | 226 | $interval(function () { 227 | $scope.nb_visited = nb_visited; 228 | $scope.nb_third = nb_third; 229 | $scope.nb_visited_with_cookie = nb_visited_with_cookie; 230 | $scope.most_third = most_third; 231 | $scope.rest_third = rest_third; 232 | }, 500); 233 | }); -------------------------------------------------------------------------------- /core/database.js: -------------------------------------------------------------------------------- 1 | var databaseName = 'CookieViz2'; 2 | var versionNumber = '1'; 3 | 4 | var DBOpenRequest = null; 5 | var db = null; 6 | function initDb() { 7 | DBOpenRequest = indexedDB.open( 8 | databaseName, 9 | versionNumber 10 | ); 11 | 12 | //check if the Database failed to open 13 | DBOpenRequest.onerror = (event) => { 14 | console.error('Error loading database.'); 15 | }; 16 | 17 | //check if the database openned successfully 18 | DBOpenRequest.onsuccess = (event) => { 19 | console.log('Your database is created'); 20 | db = DBOpenRequest.result; 21 | }; 22 | 23 | // create the tables object store and indexes 24 | DBOpenRequest.onupgradeneeded = (event) => { 25 | let local_db = event.target.result; 26 | loaded_plugins.forEach(plugin => { 27 | Object.keys(plugin.tables).forEach(table => { 28 | let idb_table = local_db.createObjectStore(table, { 29 | autoIncrement: true 30 | }); 31 | Object.keys(plugin.tables[table]).forEach(key => { 32 | const table_name = plugin.tables[table][key]; 33 | idb_table.createIndex(table_name, table_name, { unique: false }) 34 | } 35 | ) 36 | }) 37 | }) 38 | } 39 | } 40 | 41 | 42 | 43 | // Handler that avoid WebSQL disk error 44 | function WriteToDb(table, values, index) { 45 | if (!db) return; 46 | const txn = db.transaction(table, 'readwrite'); 47 | const objectStore = txn.objectStore(table); 48 | return objectStore.put(values,index); 49 | } 50 | 51 | function cleanDB(tables) { 52 | if (!db) return; 53 | for (const table in tables){ 54 | const txn = db.transaction([table], 'readwrite'); 55 | var objectStore = txn.objectStore(table); 56 | objectStore.clear(); 57 | objectStore.onsuccess = function(event) { 58 | console.log(table +"cleared !"); 59 | }; 60 | } 61 | } -------------------------------------------------------------------------------- /core/dropdown.js: -------------------------------------------------------------------------------- 1 | /* When the user clicks on the button, 2 | toggle between hiding and showing the dropdown content */ 3 | function openDropdown(id) { 4 | // Show content 5 | const dropdowns = document.getElementsByClassName("dropdown-content"); 6 | for (const dropdown of dropdowns){ 7 | if (dropdown.id == "dropdown-"+id) dropdown.classList.toggle("show"); 8 | else if (dropdown.classList.contains('show')) dropdown.classList.remove('show'); 9 | }; 10 | 11 | // Active button 12 | const buttons = document.getElementsByClassName("dropbtn"); 13 | for (const button of buttons){ 14 | if (button.id == id) button.classList.toggle("active"); 15 | else if (button.classList.contains('active')) button.classList.remove('active'); 16 | }; 17 | } 18 | 19 | function closeDropdown(elt) { 20 | var dropdowns = document.getElementsByClassName("dropdown-content"); 21 | var i; 22 | 23 | // Close content 24 | for (const openDropdown of dropdowns){ 25 | if (openDropdown.classList.contains('show')) { 26 | openDropdown.classList.remove('show'); 27 | } 28 | } 29 | 30 | // Close buttons 31 | const buttons = document.getElementsByClassName("dropbtn"); 32 | for (const button of buttons){ 33 | if (button.classList.contains('active')) button.classList.remove('active'); 34 | }; 35 | } 36 | 37 | function createDropdownElt(menu_id, content, event_menu, id, title, ngclick){ 38 | menu_elt = document.getElementById(menu_id); 39 | 40 | // Create separator if required 41 | if (menu_elt.childElementCount > 1){ 42 | var div = document.createElement("div"); 43 | div.class = "separator"; 44 | menu_elt.appendChild(div); 45 | } 46 | 47 | // Create new menu element 48 | var a = document.createElement("a"); 49 | a.href = "#"; 50 | if(id) a.id = id; 51 | if(content) a.textContent = content; 52 | if(title) a.title = title; 53 | if(ngclick) a.setAttribute("ng-click", ngclick); 54 | if(event_menu) a.addEventListener ("click", event_menu); 55 | 56 | menu_elt.appendChild(a); 57 | } 58 | -------------------------------------------------------------------------------- /core/error.js: -------------------------------------------------------------------------------- 1 | function logError(error){ 2 | var scripts = document.getElementsByTagName("script"), 3 | src = scripts[scripts.length-1].src; 4 | 5 | console.log(src + " --> " + error); 6 | } -------------------------------------------------------------------------------- /core/language.js: -------------------------------------------------------------------------------- 1 | const ISO6391 = require('iso-639-1'); 2 | 3 | var app = angular.module('translate', ['ngCookies', 'pascalprecht.translate', ]); 4 | var currentLang; 5 | 6 | app.config(['$translateProvider', function ($translateProvider) { 7 | const langs = Object.keys(config.languages).map(function(key){ 8 | return config.languages[key]; 9 | }) 10 | 11 | $translateProvider.useStaticFilesLoader({ 12 | prefix: 'languages/', 13 | suffix: '.json' 14 | }) 15 | 16 | .registerAvailableLanguageKeys(langs, config.languages) 17 | 18 | .determinePreferredLanguage() 19 | 20 | .fallbackLanguage('en') 21 | 22 | .useLocalStorage(); 23 | 24 | langs.forEach(function (lang) { 25 | createDropdownElt('language-menus', ISO6391.getNativeName(lang), null, "", lang, "changeLanguage('"+lang+"')"); 26 | }); 27 | }]); 28 | 29 | app.controller('Ctrl', ['$translate', '$scope', function ($translate, $scope) { 30 | $scope.changeLanguage = function (langKey) { 31 | $translate.use(langKey); 32 | currentLang = $translate.proposedLanguage() || $translate.use(); 33 | }; 34 | currentLang = $translate.proposedLanguage() || $translate.use(); 35 | }]); 36 | 37 | 38 | -------------------------------------------------------------------------------- /core/plugins.js: -------------------------------------------------------------------------------- 1 | //Plugins container 2 | const loaded_plugins = []; 3 | 4 | function initAnalysis(plugins_module){ 5 | loaded_plugins.push(plugins_module); 6 | 7 | if(plugins_module.init){ 8 | plugins_module.init(); 9 | } 10 | } 11 | 12 | async function unloadAnalysis() { 13 | for (const plugin of loaded_plugins) { 14 | if (plugin.delete) { 15 | const isAsync = plugin.delete.constructor.name === "AsyncFunction"; 16 | if (isAsync) { 17 | await plugin.delete(); 18 | } else { 19 | plugin.delete(); 20 | } 21 | } 22 | }; 23 | } 24 | 25 | function clearPlugins() { 26 | for (const loaded_plugin of loaded_plugins) { 27 | if (loaded_plugin.tables) { 28 | cleanDB(loaded_plugin.tables); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /core/visit.js: -------------------------------------------------------------------------------- 1 | var visit_timeout = null; 2 | const apiHost = 'ats.api.alexa.com'; 3 | 4 | const alexa_db = openDatabase( 5 | 'ATS', 6 | '1.0', 7 | 'Storage of Alexa Top Sites', 8 | 2 * 1024 * 1024 9 | ); 10 | 11 | 12 | alexa_db.transaction(async function (tx) { 13 | let query_str = 'CREATE TABLE IF NOT EXISTS ats_list (DataUrl TEXT, Country INTEGER, Global INTEGER, ReachPerMillion INTEGER, PageViewsPerMillion INTEGER, PageViewsPerUser DOUBLE)'; 14 | tx.executeSql(query_str); 15 | }, errorHandler); 16 | 17 | async function checkUrls(sites) { 18 | const prefixs = ["https://www.", "https://", "http://www.", "http://"]; 19 | const ok_urls = []; 20 | for (const site of sites) { 21 | for (const prefix of prefixs) { 22 | const url = prefix + site.DataUrl; 23 | try { 24 | const response = await fetch(url); 25 | ok_urls.push(url); 26 | break; 27 | } catch (error) { 28 | continue; 29 | } 30 | } 31 | } 32 | 33 | openVisit(ok_urls); 34 | } 35 | 36 | async function parseATSResponse(sites) { 37 | alexa_db.transaction(async function (tx) { 38 | if (typeof sites[Symbol.iterator] !== 'function') sites = [sites]; // In case there is a single result 39 | 40 | 41 | for (const site of sites) { 42 | const DataUrl = site.DataUrl; 43 | const Country = site.Country.Rank; 44 | const Global = site.Global.Rank; 45 | const ReachPerMillion = site.Country.Reach.PerMillion; 46 | const PageViewsPerMillion = site.Country.PageViews.PerMillion; 47 | const PageViewsPerUser = site.Country.PageViews.PerUser; 48 | const query_str = 'INSERT INTO ats_list (DataUrl, Country, Global, ReachPerMillion, PageViewsPerMillion, PageViewsPerUser) VALUES (?,?,?,?,?,?)'; 49 | 50 | tx.executeSql(query_str, [DataUrl, Country, Global, ReachPerMillion, PageViewsPerMillion, PageViewsPerUser]); 51 | } 52 | }, errorHandler); 53 | 54 | await checkUrls(sites); 55 | } 56 | 57 | async function callATS(apikey, country, count) { 58 | const list_interval = 10; 59 | const error_div = document.getElementById("alexa_response"); 60 | var i = 1; 61 | error_div.innerHTML = "0%"; 62 | while (i < count + 1) { 63 | const start = i; 64 | const count_frame = i + list_interval < count + 1 ? list_interval : count - i + 1; 65 | 66 | var uri = '/api?Action=TopSites&Count=' + count_frame + '&CountryCode=' + country + '&ResponseGroup=Country&Output=json&Start=' + start; 67 | 68 | var opts = { 69 | json: true, 70 | headers: { 'x-api-key': apikey }, 71 | resolveWithFullResponse: true 72 | } 73 | 74 | const response = await fetch('https://' + apiHost + uri, opts); 75 | const json = await response.json(); 76 | if (json.Ats.Results.ResponseStatus.StatusCode != "200") { 77 | error_div.innerHTML = 'failed:' + e; 78 | throw data.Ats.Results.Result.Alexa.Request.Errors.Error.ErrorCode 79 | } 80 | if (json.Ats.Results.Result.Alexa.TopSites.Country.Sites) 81 | await parseATSResponse(json.Ats.Results.Result.Alexa.TopSites.Country.Sites.Site); 82 | i += list_interval; 83 | error_div.innerHTML = Math.round(100 * i / count).toString() + "%"; 84 | } 85 | error_div.innerHTML = "OK"; 86 | } 87 | 88 | function load_visit_txt(ffile) { 89 | // List of website to visit 90 | let visit_list = []; 91 | 92 | const lineReader = require('readline').createInterface({ 93 | input: require('fs').createReadStream(ffile) 94 | }); 95 | 96 | lineReader.on('line', function (line, last) { 97 | visit_list.push(line); 98 | }); 99 | 100 | lineReader.on('close', function () { 101 | openVisit(visit_list); 102 | }) 103 | 104 | } 105 | 106 | function closeVisit() { 107 | window.nwjsVisitBar.style.display = "none"; 108 | const L = window.nwjsVisitList.length; 109 | for (var i = L; i >= 0; i--) { 110 | window.nwjsVisitList.remove(i); 111 | } 112 | window.nwjsVisitBar.removeEventListener( 113 | 'click', 114 | navigate_visit 115 | ); 116 | 117 | window.nwjsVisitList.removeEventListener( 118 | 'change', 119 | visit_url 120 | ); 121 | } 122 | 123 | //Sortable chart windows 124 | function openVisit(visit_list) { 125 | visit_list.forEach((visit) => { 126 | let opt = document.createElement('option'); 127 | opt.appendChild(document.createTextNode(visit)); 128 | window.nwjsVisitList.appendChild(opt); 129 | }) 130 | 131 | window.nwjsVisitBar.style.display = "block"; 132 | 133 | window.nwjsVisitBar.addEventListener( 134 | 'click', 135 | navigate_visit 136 | ); 137 | 138 | window.nwjsVisitList.addEventListener( 139 | 'change', 140 | visit_url 141 | ); 142 | 143 | //Hidden filedialog 144 | const file_dialog_visit = document.createElement("INPUT"); 145 | file_dialog_visit.style = "display:none;"; 146 | file_dialog_visit.id = "fileDialog_path"; 147 | file_dialog_visit.type = "file"; 148 | file_dialog_visit.accept = ".txt,text/plain" 149 | document.querySelector('nav').appendChild(file_dialog_visit); 150 | window.nwjsDialogOpenVisit = file_dialog_visit; 151 | 152 | //Setup load txt trigger 153 | window.alexaModal = document.getElementById('visit_modal'); 154 | file_dialog_visit.addEventListener("change", function (evt) { 155 | load_visit_txt(this.value); 156 | this.value = ""; 157 | }, false); 158 | 159 | window.alexaForm = document.getElementById("alexa_form"); 160 | window.alexaForm.addEventListener("submit", function (event) { 161 | event.preventDefault(); 162 | callATS(event.target.api_key.value, event.target.country_select.value, parseInt(event.target.alexa_list_size.value)); 163 | }); 164 | }; 165 | 166 | function next_visit() { 167 | window.nwjsBrowser.removeEventListener("load", next_visit); 168 | clearTimeout(visit_timeout); 169 | setTimeout(function () { 170 | if (document.querySelector('#visit-play-pause').classList.contains("fa-pause") && window.nwjsVisitList.selectedIndex < window.nwjsVisitList.length - 1) { 171 | window.nwjsVisitList.selectedIndex++; 172 | visit_url(); 173 | } 174 | }, 1000); 175 | } 176 | 177 | function visit_url() { 178 | const url = window.nwjsVisitList.selectedOptions[0].text; 179 | window.nwjsBrowser.contentWindow.window.location.href = url; 180 | console.log("Visit -> Visiting url : " + url); 181 | nwjsHeader.querySelector('#address').value = url; 182 | window.nwjsBrowser.addEventListener("load", next_visit); // In case page is loaded 183 | visit_timeout = setTimeout(next_visit, 60000); //In case page takes time to be fully loaded 184 | } 185 | 186 | function navigate_visit(e) { 187 | // Set action depending on click event 188 | switch (e.target.id) { 189 | case 'visit-close': 190 | closeVisit(); 191 | break; 192 | case 'visit-reduce': 193 | window.nwjsVisitBar.classList.toggle("reduce"); 194 | break; 195 | case 'visit-play-pause': 196 | if (e.target.classList.contains("fa-play")) { 197 | const idx = window.nwjsVisitList.selectedIndex; 198 | window.nwjsVisitList.selectedIndex = idx == -1 ? 0 : idx + 1; 199 | visit_url(); 200 | } 201 | e.target.classList.toggle("fa-play"); 202 | e.target.classList.toggle("fa-pause"); 203 | 204 | break; 205 | case 'visit-init': 206 | window.nwjsVisitList.selectedIndex = 0; 207 | visit_url(); 208 | break; 209 | case 'visit-next': 210 | if (window.nwjsVisitList.selectedIndex < window.nwjsVisitList.length - 1) { 211 | window.nwjsVisitList.selectedIndex++; 212 | visit_url(); 213 | } 214 | break; 215 | case 'visit-add': 216 | let opt = document.createElement('option'); 217 | const url = nwjsHeader.querySelector('#address').value; 218 | opt.appendChild(document.createTextNode(url)); 219 | window.nwjsVisitList.appendChild(opt); 220 | break; 221 | case 'visit-remove': 222 | window.nwjsVisitList.remove(window.nwjsVisitList.selectedOptions[0]); 223 | break; 224 | case 'visit-import': 225 | window.nwjsDialogOpenVisit.click(); 226 | break; 227 | case 'visit-export': 228 | const urls = Array.from(window.nwjsVisitList.options); 229 | var blob = new Blob([urls.map(x => x.text).join("\n")], { type: 'text/plain' }); 230 | var a = document.createElement("a"); 231 | document.body.appendChild(a); 232 | a.style = "display: none"; 233 | const export_url = window.URL.createObjectURL(blob); 234 | a.href = export_url; 235 | a.download = "exported-list-from-" + new Date().toLocaleDateString() + ".txt"; 236 | a.click(); 237 | window.URL.revokeObjectURL(export_url); 238 | break; 239 | case 'visit-download': 240 | // Show download modal 241 | const modal = document.getElementById("visit_modal"); 242 | modal.style.display = "block"; 243 | case 'visit-prev': 244 | if (window.nwjsVisitList.selectedIndex > 0) { 245 | window.nwjsVisitList.selectedIndex--; 246 | visit_url(); 247 | } 248 | break; 249 | } 250 | } 251 | 252 | -------------------------------------------------------------------------------- /css/browser.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | 7 | html, 8 | body{ 9 | width:100%; 10 | height:100%; 11 | margin:0; 12 | padding:0; 13 | 14 | overflow:hidden; 15 | font-family: ubuntu, helvetica, arial; 16 | } 17 | 18 | .splash{ 19 | height:50px; 20 | width:100%; 21 | font-size:40px; 22 | line-height: 50px; 23 | text-align: center; 24 | position:absolute; 25 | top:calc(50% - 25px); 26 | } 27 | 28 | #navigation-bar { 29 | height:40px; 30 | width:100%; 31 | margin: auto; 32 | background: #282E42; 33 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 34 | z-index: 1; 35 | position:absolute; 36 | z-index: 10; 37 | opacity:0; 38 | transition: opacity 500ms; 39 | } 40 | 41 | nav #nav-wrapper { 42 | width: 788px; 43 | margin: auto; 44 | } 45 | 46 | nav .nav-btn{ 47 | font-size: 14px; 48 | color: #ffffff; 49 | height:39px; 50 | width:24px; 51 | cursor:pointer; 52 | outline:none; 53 | padding: 0px; 54 | margin : 0 17px 0 0; 55 | background-color: #282E42; 56 | box-shadow: none; 57 | border: none; 58 | /* border: 1px solid white; */ 59 | } 60 | 61 | nav .nav-btn:hover { 62 | background-color: #FA4564; 63 | } 64 | nav .dropbtn:hover { 65 | background-color: #FA4564; 66 | } 67 | nav .active { 68 | background-color: #FA4564; 69 | } 70 | 71 | nav .address{ 72 | width:450px; 73 | font-size:12px; 74 | line-height:30px; 75 | border-radius: 5px; 76 | border: 1px solid #e4e4e4; 77 | margin:5px 5px 5px 10px; 78 | padding: 0 3px; 79 | box-shadow: none; 80 | } 81 | 82 | nav .nav-elements{ 83 | display: inline; 84 | } 85 | 86 | .dropdown { 87 | position: relative; 88 | display: inline-block; 89 | } 90 | 91 | /* Dropdown Content (Hidden by Default) */ 92 | .dropdown-content { 93 | display: none; 94 | position: absolute; 95 | background-color: #fff; 96 | border: 1px solid #e3e1e8; 97 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 98 | z-index: 1; 99 | } 100 | 101 | .content-container{ 102 | padding: 10px; 103 | } 104 | 105 | /* .dropdown-content a{ 106 | font-size: 12px; 107 | display: block; 108 | width: max-content; 109 | margin-bottom: 10px; 110 | margin-top: 7px; 111 | } 112 | 113 | .dropdown-content a:hover{ 114 | background-color: #FA4564; 115 | } */ 116 | 117 | .dropdown-content .separator { 118 | width: 100%; 119 | display: block; 120 | height: 1px; 121 | background-color: #e3e1e8; 122 | } 123 | 124 | nav h6 { 125 | margin-bottom: 5px; 126 | font-size: 14px; 127 | font-weight: bold; 128 | width: max-content; 129 | } 130 | 131 | nav form{ 132 | margin: 10px 0; 133 | margin-block-end: 0; 134 | } 135 | 136 | nav form label{ 137 | font-size: 12px; 138 | font-weight: 600; 139 | } 140 | 141 | nav form input{ 142 | height: 24px; 143 | width: 100%; 144 | border: 1px solid #e3e1e8; 145 | border-radius: 3px; 146 | margin-top: 3px; 147 | margin-bottom: 10px; 148 | padding: 5px; 149 | } 150 | 151 | ::placeholder{ 152 | color: #c2c0c7; 153 | } 154 | 155 | nav form input:focus{ 156 | border: 1px solid #FA4564; 157 | outline-offset: 0px; 158 | outline: none; 159 | } 160 | 161 | .grid-container{ 162 | display: grid; 163 | grid-template-columns: auto; 164 | grid-column-gap: 0px; 165 | } 166 | 167 | #bookmark\.cancel{ 168 | grid-column: 1 / 3; 169 | } 170 | #bookmark\.confirm{ 171 | grid-column: 3 / 5; 172 | } 173 | 174 | .btn-action{ 175 | text-align: center; 176 | padding: 5px 0; 177 | font-size: 12px; 178 | } 179 | 180 | .action-cancel{ 181 | color: #c2c0c7; 182 | } 183 | 184 | .action-cancel:hover{ 185 | background-color: #f0f0f0; 186 | color: #c2c0c7; 187 | } 188 | 189 | .action-confirm{ 190 | background-color: #FA4564; 191 | color: #fff; 192 | cursor: pointer; 193 | } 194 | .action-confirm:hover{ 195 | background-color: rgb(216, 54, 81); 196 | } 197 | 198 | .link-list a{ 199 | color: #282E42; 200 | padding: 10px; 201 | font-size: 14px; 202 | display: block; 203 | min-width: 80px; 204 | width: max-content; 205 | max-width: 90px; 206 | } 207 | 208 | .link-list a:hover{ 209 | color: #ffffff; 210 | background-color: #FA4564; 211 | } 212 | 213 | #browser{ 214 | border:none; 215 | margin:0; 216 | width:100%; 217 | height:calc(100% - 50px); 218 | position:absolute; 219 | top:50px; 220 | opacity:0; 221 | transition: opacity 500ms; 222 | z-index: 0; 223 | } 224 | 225 | #vizualisation{ 226 | border:none; 227 | margin:0; 228 | width:100%; 229 | height:calc(100% - 50px); 230 | position:absolute; 231 | top:50px; 232 | z-index: 0; 233 | } 234 | 235 | .hidden{ 236 | display:none; 237 | } 238 | 239 | /* Dropdown Button */ 240 | /* .dropbtn { 241 | background-color: #3498DB; 242 | color: white; 243 | padding: 16px; 244 | font-size: 16px; 245 | border: none; 246 | cursor: pointer; 247 | } */ 248 | 249 | 250 | 251 | /* The container
- needed to position the dropdown content */ 252 | 253 | 254 | /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ 255 | .show {display:block;} 256 | 257 | #visit-bar { 258 | width:270px; 259 | bottom: 0px; 260 | right: 50px; 261 | margin: auto; 262 | background: #282E42; 263 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 264 | z-index: 1; 265 | position:absolute; 266 | transition: opacity 500ms; 267 | display: none; 268 | } 269 | 270 | .visit{ 271 | height:300px; 272 | } 273 | 274 | .reduce{ 275 | height:40px; 276 | } 277 | 278 | nav .visit{ 279 | width:210px; 280 | height:70%; 281 | font-size:12px; 282 | line-height:30px; 283 | border-radius: 5px; 284 | border: 1px solid #e4e4e4; 285 | margin:5px 5px 5px 30px; 286 | padding: 0 3px; 287 | box-shadow: none; 288 | position:relative; 289 | } 290 | 291 | /* The Modal (background) */ 292 | .modal { 293 | display: none; /* Hidden by default */ 294 | position: fixed; /* Stay in place */ 295 | z-index: 1; /* Sit on top */ 296 | padding-top: 100px; /* Location of the box */ 297 | left: 0; 298 | top: 0; 299 | width: 100%; /* Full width */ 300 | height: 100%; /* Full height */ 301 | overflow: auto; /* Enable scroll if needed */ 302 | background-color: rgb(0,0,0); /* Fallback color */ 303 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 304 | } 305 | 306 | /* Modal Content */ 307 | .modal-content { 308 | background-color: #fefefe; 309 | margin: auto; 310 | padding: 20px; 311 | border: 1px solid #888; 312 | width: 80%; 313 | text-align: center; 314 | } 315 | 316 | /* The Close Button */ 317 | .close { 318 | color: #aaaaaa; 319 | float: right; 320 | font-size: 28px; 321 | font-weight: bold; 322 | } 323 | 324 | .close:hover, 325 | .close:focus { 326 | color: #000; 327 | text-decoration: none; 328 | cursor: pointer; 329 | } -------------------------------------------------------------------------------- /css/cookieviz.css: -------------------------------------------------------------------------------- 1 | .link { 2 | stroke-opacity: 0.6; 3 | pointer-events: all; 4 | } 5 | 6 | .node circle { 7 | pointer-events: all; 8 | stroke: #fff; 9 | stroke-width: 1.5px; 10 | } 11 | 12 | 13 | div.tooltip { 14 | position: absolute; 15 | background-color: white; 16 | max-width: 200px; 17 | height: auto; 18 | padding: 1px; 19 | border-style: solid; 20 | border-radius: 4px; 21 | border-width: 1px; 22 | box-shadow: 3px 3px 10px rgba(0, 0, 0, .5); 23 | pointer-events: none; 24 | } 25 | 26 | .column { 27 | float: left; 28 | width: 50%; 29 | height: 100px; 30 | background-color: red; 31 | position: relative; 32 | margin: auto; 33 | } 34 | 35 | .content{ 36 | position: absolute; 37 | top: 50%; left: 50%; 38 | transform: translate(-50%,-50%); 39 | width: 200px; 40 | height: 200px; 41 | } 42 | 43 | 44 | .row:after { 45 | content: ""; 46 | display: table; 47 | clear: both; 48 | } 49 | 50 | #cookieviz{ 51 | float:left; 52 | width:100%; 53 | height:calc(70% - 50px); 54 | top:50px; 55 | opacity:0; 56 | transition: opacity 500ms; 57 | z-index: 0; 58 | } 59 | 60 | #treemap{ 61 | margin-top: 50px; 62 | border:none; 63 | height:calc(60% - 50px); 64 | top:50px; 65 | z-index: 0; 66 | } 67 | 68 | #hist{ 69 | width:100%; 70 | margin-top: 30px; 71 | height:calc(40% + 50px); 72 | z-index: 1; 73 | z-index: 1; 74 | clear:both; 75 | } -------------------------------------------------------------------------------- /css/hist.css: -------------------------------------------------------------------------------- 1 | .bar { 2 | fill:#fb8761; 3 | } 4 | 5 | .bar:hover { 6 | fill: #b5367a; 7 | } 8 | 9 | 10 | .bar:hover { 11 | fill: #4f127b; 12 | } 13 | 14 | rect.zoom-panel { 15 | cursor: move; 16 | fill: #fff; 17 | pointer-events: all; 18 | } -------------------------------------------------------------------------------- /css/voronoi.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | text.tiny { 4 | font-size: 10pt; 5 | } 6 | text.light { 7 | fill: lightgrey 8 | } 9 | 10 | .world { 11 | stroke: lightgrey; 12 | stroke-width: 4px; 13 | } 14 | 15 | .cell { 16 | stroke: white; 17 | stroke-width: 1px; 18 | } 19 | 20 | .label { 21 | text-anchor: middle; 22 | fill: white; 23 | } 24 | 25 | .label>.name { 26 | dominant-baseline: text-after-edge; 27 | } 28 | 29 | .label>.value { 30 | dominant-baseline: text-before-edge; 31 | } 32 | 33 | .hoverer { 34 | fill: transparent; 35 | stroke: white; 36 | stroke-width:0px; 37 | } 38 | 39 | .hoverer:hover { 40 | stroke-width: 3px; 41 | } 42 | 43 | .legend-color { 44 | stroke-width: 1px; 45 | stroke:darkgrey; 46 | } -------------------------------------------------------------------------------- /css/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /css/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /css/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /css/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /css/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /css/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /css/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /css/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /css/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /css/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /css/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /css/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/css/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /data/adv.json: -------------------------------------------------------------------------------- 1 | { 2 | "doubleclick.net": { 3 | "purpose": [ 4 | "ads" 5 | ], 6 | "privacy":"https://policies.google.com/technologies/types?hl=fr" 7 | }, 8 | "google.com":{ 9 | "purpose": [ 10 | "ads" 11 | ], 12 | "privacy":"https://policies.google.com/technologies/types?hl=fr" 13 | }, 14 | "1rx.io":{ 15 | "purpose": [ 16 | "ads" 17 | ], 18 | "privacy":"https://www.rhythmone.com/privacy-policy/" 19 | }, 20 | "sabavision.com":{ 21 | "purpose": [ 22 | "ads" 23 | ], 24 | "privacy":"https://www.sabavision.com/fa/target" 25 | }, 26 | "facebook.com":{ 27 | "purpose": [ 28 | "ads" 29 | ], 30 | "privacy":"https://www.facebook.com/policies/cookies/" 31 | }, 32 | "amazon-adsystem.com":{ 33 | "purpose": [ 34 | "ads" 35 | ], 36 | "privacy":"https://www.amazon.com/gp/help/customer/display.html/?nodeId=201890250" 37 | }, 38 | "rubiconproject.com":{ 39 | "purpose": [ 40 | "ads" 41 | ], 42 | "privacy":"https://rubiconproject.com/privacy-policy" 43 | }, 44 | "pubmatic.com":{ 45 | "purpose": [ 46 | "ads" 47 | ], 48 | "privacy":"https://pubmatic.com/legal/platform-cookie-policy" 49 | }, 50 | "outbrain.com":{ 51 | "purpose": [ 52 | "ads" 53 | ], 54 | "privacy":"https://www.outbrain.com/legal/privacy#cookies" 55 | }, 56 | "criteo.com":{ 57 | "purpose": [ 58 | "ads" 59 | ], 60 | "privacy":"https://www.criteo.com/privacy/" 61 | }, 62 | "smartadserver.com":{ 63 | "purpose": [ 64 | "ads" 65 | ], 66 | "privacy":"https://smartadserver.fr/politique-confidentialite-client-final/" 67 | }, 68 | "taboola.com":{ 69 | "purpose": [ 70 | "ads" 71 | ], 72 | "privacy":"https://www.taboola.com/fr/cookie-policy" 73 | }, 74 | "yahoo.com":{ 75 | "purpose": [ 76 | "ads" 77 | ], 78 | "privacy":"https://policies.yahoo.com/xa/en/yahoo/privacy/topics/cookies/index.htm" 79 | }, 80 | "bidswitch.net":{ 81 | "purpose": [ 82 | "ads" 83 | ], 84 | "privacy":"https://www.bidswitch.com/cookie-statement" 85 | }, 86 | "advertising.com":{ 87 | "purpose": [ 88 | "ads" 89 | ], 90 | "privacy":"https://privacy.aol.com/legacy/cookies-web-beacons/index.html" 91 | }, 92 | "360yield.com":{ 93 | "purpose": [ 94 | "ads" 95 | ], 96 | "privacy":"https://www.360yieldcenter.com/privacy-policy/" 97 | }, 98 | "teads.tv":{ 99 | "purpose": [ 100 | "ads" 101 | ], 102 | "privacy":"https://www.teads.com/privacy-policy/" 103 | }, 104 | "media.net":{ 105 | "purpose": [ 106 | "ads" 107 | ], 108 | "privacy":"https://www.media.net/privacy-policy/" 109 | }, 110 | "lijit.com":{ 111 | "purpose": [ 112 | "ads" 113 | ], 114 | "privacy":"https://www.sovrn.com/about-our-cookies/" 115 | }, 116 | "sharethrough.com":{ 117 | "purpose": [ 118 | "ads" 119 | ], 120 | "privacy":"https://platform-cdn.sharethrough.com/privacy-policy" 121 | }, 122 | "spotxchange.com":{ 123 | "purpose": [ 124 | "ads" 125 | ], 126 | "privacy":"https://www.spotx.tv/privacy-policy/" 127 | }, 128 | "dailymotion.com":{ 129 | "purpose": [ 130 | ], 131 | "privacy":"https://www.dailymotion.com/legal/privacy" 132 | }, 133 | "emxdgt.com":{ 134 | "purpose": [ 135 | "ads" 136 | ], 137 | "privacy":"https://emxdigital.com/privacy/" 138 | }, 139 | "adotmob.com":{ 140 | "purpose": [ 141 | "ads" 142 | ], 143 | "privacy":"https://we-are-adot.com/privacy-policy/" 144 | }, 145 | "google.fr":{ 146 | "purpose": [ 147 | "ads" 148 | ], 149 | "privacy":"https://policies.google.com/technologies/types?hl=fr" 150 | }, 151 | "adnxs.com":{ 152 | "purpose": [ 153 | "ads" 154 | ], 155 | "privacy":"https://www.xandr.com//privacy/cookie-policy/" 156 | }, 157 | "twitter.com":{ 158 | "purpose": [ 159 | "ads" 160 | ], 161 | "privacy":"https://help.twitter.com/fr/rules-and-policies/twitter-cookies" 162 | }, 163 | "xiti.com":{ 164 | "purpose": [ 165 | ], 166 | "privacy":"https://developers.atinternet-solutions.com/javascript-fr/bien-commencer-javascript-fr/cookies-at-internet-javascript-fr/" 167 | }, 168 | "scorecardresearch.com":{ 169 | "purpose": [ 170 | "ads" 171 | ], 172 | "privacy":"https://www.scorecardresearch.com/privacy.aspx" 173 | }, 174 | "openx.net":{ 175 | "purpose": [ 176 | "ads" 177 | ], 178 | "privacy":"https://www.openx.com/legal/privacy-policy/" 179 | }, 180 | "consensu.org":{ 181 | "purpose": [ 182 | ], 183 | "privacy":"https://www.consensus.com/privacy-policy/" 184 | }, 185 | "consentframework.com":{ 186 | "purpose": [ 187 | ], 188 | "privacy":"https://iabeurope.eu/transparency-consent-framework/" 189 | }, 190 | "rlcdn.com":{ 191 | "purpose": [ 192 | "ads" 193 | ], 194 | "privacy":"https://liveramp.com/privacy/service-privacy-policy/" 195 | }, 196 | "tavoos.net":{ 197 | "purpose": [ 198 | "ads" 199 | ], 200 | "privacy":"https://tavoos.net/about" 201 | }, 202 | "bing.com":{ 203 | "purpose": [ 204 | "ads" 205 | ], 206 | "privacy":"https://privacy.microsoft.com/fr-fr/privacystatement" 207 | }, 208 | "adsrvr.org":{ 209 | "purpose": [ 210 | "ads" 211 | ], 212 | "privacy":"https://www.thetradedesk.com/general/privacy" 213 | }, 214 | "casalemedia.com":{ 215 | "purpose": [ 216 | "ads" 217 | ], 218 | "privacy":"https://casalemedia.com/" 219 | }, 220 | "youtube.com":{ 221 | "purpose": [ 222 | "ads" 223 | ], 224 | "privacy":"https://policies.google.com/technologies/types?hl=fr" 225 | }, 226 | "adition.com":{ 227 | "purpose": [ 228 | "ads" 229 | ], 230 | "privacy":"https://www.adition.com/datenschutz" 231 | }, 232 | "nr-data.net":{ 233 | "purpose": [ 234 | ], 235 | "privacy":"https://newrelic.com/termsandconditions/cookie-policy/cookie-table" 236 | }, 237 | "adform.net":{ 238 | "purpose": [ 239 | "ads" 240 | ], 241 | "privacy":"https://site.adform.com/privacy-center/adform-cookies/" 242 | }, 243 | "agkn.com":{ 244 | "purpose": [ 245 | "ads" 246 | ], 247 | "privacy":"https://www.home.neustar/privacy/privacy-policy#websitecookies" 248 | }, 249 | "demdex.net":{ 250 | "purpose": [ 251 | "ads" 252 | ], 253 | "privacy":"https://docs.adobe.com/content/help/fr-FR/core-services/interface/ec-cookies/cookies-am.html" 254 | }, 255 | "yandex.ru":{ 256 | "purpose": [ 257 | "ads" 258 | ], 259 | "privacy":"https://yandex.com/legal/confidential/" 260 | }, 261 | "everesttech.net":{ 262 | "purpose": [ 263 | "ads" 264 | ], 265 | "privacy":"https://docs.adobe.com/content/help/en/core-services/interface/ec-cookies/cookies-advertising-cloud.html" 266 | }, 267 | "quantserve.com":{ 268 | "purpose": [ 269 | "ads" 270 | ], 271 | "privacy":"https://www.quantcast.com/privacy/" 272 | }, 273 | "twimg.com":{ 274 | "purpose": [ 275 | ], 276 | "privacy":"https://help.twitter.com/fr/rules-and-policies/twitter-cookies" 277 | }, 278 | "commander1.com":{ 279 | "purpose": [ 280 | "ads" 281 | ], 282 | "privacy":"https://community.commandersact.com/platform/knowledge-base/cookies" 283 | }, 284 | "acpm.fr":{ 285 | "purpose": [ 286 | ], 287 | "privacy":"https://www.acpm.fr/Politique-Cookies" 288 | }, 289 | "addthis.com":{ 290 | "purpose": [ 291 | "ads" 292 | ], 293 | "privacy":"https://www.oracle.com/legal/privacy/addthis-privacy-policy.html" 294 | }, 295 | "estat.com":{ 296 | "purpose": [ 297 | ], 298 | "privacy":"https://www.mediametrie.fr/fr/gestion-des-cookies" 299 | }, 300 | "3lift.com":{ 301 | "purpose": [ 302 | "ads" 303 | ], 304 | "privacy":"https://triplelift.com/privacy/" 305 | }, 306 | "id5-sync.com":{ 307 | "purpose": [ 308 | "ads" 309 | ], 310 | "privacy":"https://www.id5.io/privacy-policy" 311 | }, 312 | "bluekai.com":{ 313 | "purpose": [ 314 | "ads" 315 | ], 316 | "privacy":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html" 317 | }, 318 | "linkedin.com":{ 319 | "purpose": [ 320 | "ads" 321 | ], 322 | "privacy":"https://www.linkedin.com/legal/l/cookie-table" 323 | }, 324 | "mathtag.com":{ 325 | "purpose": [ 326 | "ads" 327 | ], 328 | "privacy":"https://www.mediamath.com/fr/politique-de-confidentialite/" 329 | }, 330 | "yadro.ru":{ 331 | "purpose": [ 332 | ], 333 | "privacy":"https://yadro.ru" 334 | }, 335 | "omtrdc.net":{ 336 | "purpose": [ 337 | "ads" 338 | ], 339 | "privacy":"https://docs.adobe.com/content/help/en/core-services/interface/ec-cookies/cookies-analytics.html" 340 | }, 341 | "omnitagjs.com":{ 342 | "purpose": [ 343 | "ads" 344 | ], 345 | "privacy":"https://www.similarweb.com/corp/legal/privacy-policy/" 346 | }, 347 | "4dex.io":{ 348 | "purpose": [ 349 | "ads" 350 | ], 351 | "privacy":"https://adagio.io/privacy" 352 | }, 353 | "smaato.net":{ 354 | "purpose": [ 355 | "ads" 356 | ], 357 | "privacy":"https://www.smaato.com/applicant-privacy-policy/" 358 | }, 359 | "exelator.com":{ 360 | "purpose": [ 361 | "ads" 362 | ], 363 | "privacy":"https://www.nielsen.com/us/en/legal/privacy-statement/exelate-privacy-policy/services-privacy-policy-fr/" 364 | }, 365 | "tapad.com":{ 366 | "purpose": [ 367 | "ads" 368 | ], 369 | "privacy":"https://www.tapad.com/eu-privacy-policy" 370 | }, 371 | "weborama.fr":{ 372 | "purpose": [ 373 | "ads" 374 | ], 375 | "privacy":"https://weborama.com/respect-de-la-vie-privee-2-2/" 376 | }, 377 | "crwdcntrl.net":{ 378 | "purpose": [ 379 | "ads" 380 | ], 381 | "privacy":"https://www.lotame.com/about-lotame/privacy/lotames-products-services-privacy-policy/" 382 | }, 383 | "turn.com":{ 384 | "purpose": [ 385 | "ads" 386 | ], 387 | "privacy":"https://www.amobee.com/trust/privacy-guidelines/#cookies" 388 | }, 389 | "stickyadstv.com":{ 390 | "purpose": [ 391 | "ads" 392 | ], 393 | "privacy":"https://www.freewheel.com/privacy-policy-fr" 394 | }, 395 | "mookie1.com":{ 396 | "purpose": [ 397 | "ads" 398 | ], 399 | "privacy":"http://www.themig.com/en-uk/privacy.html" 400 | }, 401 | "dotomi.com":{ 402 | "purpose": [ 403 | "ads" 404 | ], 405 | "privacy":"https://www.conversantmedia.com/legal/privacy" 406 | }, 407 | "zemanta.com":{ 408 | "purpose": [ 409 | "ads" 410 | ], 411 | "privacy":"https://www.zemanta.com/legal/privacy/" 412 | }, 413 | "zeotap.com":{ 414 | "purpose": [ 415 | "ads" 416 | ], 417 | "privacy":"https://zeotap.com/wp-content/uploads/2019/07/FR_zeotap-Products-and-Services-Privacy-Policy.pdf" 418 | }, 419 | "adscale.de":{ 420 | "purpose": [ 421 | "ads" 422 | ], 423 | "privacy":"https://www.adscale.com/privacy-policy/" 424 | }, 425 | "najva.com":{ 426 | "purpose": [ 427 | "ads" 428 | ], 429 | "privacy":"https://www.najva.com/about-us/" 430 | }, 431 | "pinterest.com":{ 432 | "purpose": [ 433 | ], 434 | "privacy":"https://policy.pinterest.com/fr/privacy-policy" 435 | }, 436 | "mail.ru":{ 437 | "purpose": [ 438 | "ads" 439 | ], 440 | "privacy":"https://help.mail.ru/legal/terms/agent/eng/cookiepolicy" 441 | }, 442 | "serving-sys.com":{ 443 | "purpose": [ 444 | "ads" 445 | ], 446 | "privacy":"https://www.sizmek.com/privacy-policy/" 447 | }, 448 | "krxd.net":{ 449 | "purpose": [ 450 | "ads" 451 | ], 452 | "privacy":"https://www.salesforce.com/fr/company/privacy/full_privacy/" 453 | }, 454 | "sc-static.net":{ 455 | "purpose": [ 456 | ] 457 | }, 458 | "snapchat.com":{ 459 | "purpose": [ 460 | "ads" 461 | ], 462 | "privacy":"https://www.snap.com/fr-FR/privacy/cookie-information" 463 | }, 464 | "yektanet.com":{ 465 | "purpose": [ 466 | "ads" 467 | ], 468 | "privacy":"https://my.yektanet.com/" 469 | }, 470 | "imrworldwide.com":{ 471 | "purpose": [ 472 | "ads" 473 | ], 474 | "privacy":"https://priv-policy.imrworldwide.com/priv/browser/us/en/optout.html" 475 | }, 476 | "appconsent.io":{ 477 | "purpose": [ 478 | "ads" 479 | ], 480 | "privacy":"https://appconsent.io/en/privacy-policy" 481 | }, 482 | "education.gouv.fr":{ 483 | "purpose": [ 484 | ], 485 | "privacy":"https://data.education.gouv.fr/terms/cookies-information/" 486 | }, 487 | "iadvize.com":{ 488 | "purpose": [ 489 | ], 490 | "privacy":"https://privacy.iadvize.com/fr/userdata#collected-data" 491 | }, 492 | "vk.com":{ 493 | "purpose": [ 494 | "ads" 495 | ], 496 | "privacy":"https://vk.com/privacy?eu=1" 497 | }, 498 | "dmxleo.com":{ 499 | "purpose": [ 500 | ] 501 | }, 502 | "w55c.net":{ 503 | "purpose": [ 504 | "ads" 505 | ], 506 | "privacy":"https://docs.roku.com/published/userprivacypolicy/fr/fr" 507 | }, 508 | "bidr.io":{ 509 | "purpose": [ 510 | "ads" 511 | ], 512 | "privacy":"https://www.beeswax.com/privacy/" 513 | }, 514 | "abtasty.com":{ 515 | "purpose": [ 516 | ], 517 | "privacy":"https://www.abtasty.com/fr/mentions-legales/" 518 | }, 519 | "easydmp.net":{ 520 | "purpose": [ 521 | "ads" 522 | ], 523 | "privacy":"https://www.squadata.net/private-policy/" 524 | }, 525 | "adskeeper.co.uk":{ 526 | "purpose": [ 527 | "ads" 528 | ], 529 | "privacy":"https://www.adskeeper.co.uk/privacy-policy" 530 | }, 531 | "creativecdn.com":{ 532 | "purpose": [ 533 | "ads" 534 | ], 535 | "privacy":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/" 536 | }, 537 | "remarketingpixel.com":{ 538 | "purpose": [ 539 | "ads" 540 | ], 541 | "privacy":"https://remarketingpixel.com/" 542 | }, 543 | "tns-counter.ru":{ 544 | "purpose": [ 545 | ], 546 | "privacy":"http://tns-counter.ru/policy.html" 547 | }, 548 | "poool.fr":{ 549 | "purpose": [ 550 | ], 551 | "privacy":"https://poool.fr/privacy-policy" 552 | }, 553 | "eyeota.net":{ 554 | "purpose": [ 555 | "ads" 556 | ], 557 | "privacy":"https://www.eyeota.com/eyeota-privacy-policy" 558 | }, 559 | "mediarithmics.com":{ 560 | "purpose": [ 561 | "ads" 562 | ], 563 | "privacy":"https://www.mediarithmics.com/fr-fr/content/charte-de-protection-des-donnees-personnelles" 564 | }, 565 | "leadplace.fr":{ 566 | "purpose": [ 567 | "ads" 568 | ], 569 | "privacy":"https://temelio.com/vie-privee" 570 | }, 571 | "liadm.com":{ 572 | "purpose": [ 573 | "ads" 574 | ], 575 | "privacy":"https://www.liveintent.com/services-privacy-policy/" 576 | }, 577 | "tradelab.fr":{ 578 | "purpose": [ 579 | "ads" 580 | ], 581 | "privacy":"https://tradelab.com/en/privacy/" 582 | }, 583 | "zebestof.com":{ 584 | "purpose": [ 585 | "ads" 586 | ], 587 | "privacy":"https://zbo.media/mentions-legales/politique-de-confidentialite-site-web/" 588 | }, 589 | "yandex.com":{ 590 | "purpose": [ 591 | ], 592 | "privacy":"https://yandex.com/support/metrica/general/cookie-usage.html" 593 | }, 594 | "googlesyndication.com":{ 595 | "purpose": [ 596 | "ads" 597 | ], 598 | "privacy":"https://policies.google.com/technologies/types?hl=fr" 599 | }, 600 | "contextweb.com":{ 601 | "purpose": [ 602 | "ads" 603 | ], 604 | "privacy":"https://pulsepoint.com/legal/cookie-policy" 605 | }, 606 | "cloudflare.com":{ 607 | "purpose": [ 608 | ], 609 | "privacy":"https://support.cloudflare.com/hc/en-us/articles/200170156-Understanding-the-Cloudflare-Cookies" 610 | }, 611 | "adobedtm.com":{ 612 | "purpose": [ 613 | ], 614 | "privacy":"https://helpx.adobe.com/fr/dtm/kb/what-cookies-are-set-by-dtm.html" 615 | } 616 | } -------------------------------------------------------------------------------- /en.cookieviz.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 | 34 |
35 |
36 |
37 | Your navigation
38 |
39 |
40 | You have visited {{nb_visited}} websites. 41 |
42 | {{nb_visited_with_cookie}}% of them stored at least one cookie. 43 |
44 |
45 | These cookies potentially exchange 46 | information with {{nb_third}} third parties. 47 |
48 |
49 | 50 |
51 | 52 |
53 |
54 |
55 |
56 | 58 |
59 |
60 |
61 |
62 | 64 |
65 |
66 | 67 |
68 |
69 |
70 | Presence of third parties on websites 71 | During your navigation, third parties may deposit cookies to, among other things, track navigation. 72 | 73 | Between {{most_third[0]}} and {{most_third[1]}} third parties store cookies on 20% of visited websites. 74 | Between {{rest_third[0]}} and {{rest_third[1]}} third parties store cookies on 80% of visited websites. 75 |
76 |
77 |
78 |
79 | 80 | 83 |
84 |
85 |
86 | 103 |
104 |
105 | 106 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /es.cookieviz.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 | 34 |
35 |
36 |
37 | Su navegación
38 |
39 |
40 | Usted ha visitado {{nb_visited}} sitios web. 41 |
42 | {{nb_visited_with_cookie}}% de ellos almacenaron al menos una cookie. 43 |
44 |
45 | Esas cookies potencialmente intercambian 46 | información con {{nb_third}} terceras partes. 47 |
48 |
49 | 50 |
51 | 52 |
53 |
54 |
55 |
56 | 58 |
59 |
60 |
61 |
62 | 64 |
65 |
66 | 67 |
68 |
69 |
70 | Presencia de terceras partes en los sitios web 71 | Durante su navegación, terceras partes pueden instalar cookies para, entre otras cosas, el seguimiento de su navegación. 72 | 73 | Entre {{most_third[0]}} y {{most_third[1]}} terceras partes almacenan cookies en el 20% de los sitios web visitados. 74 | Entre {{rest_third[0]}} y {{rest_third[1]}} terceras partes almacenan cookies en el 80% de los sitios web visitados. 75 |
76 |
77 |
78 |
79 | 80 | 83 |
84 |
85 |
86 | 103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /fr.cookieviz.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 | 34 |
35 |
36 |
37 | Votre navigation
38 |
39 |
40 | Vous avez visité {{nb_visited}} 41 | sites. 42 |
43 | {{nb_visited_with_cookie}}% d’entre eux ont déposé au moins un 44 | cookie. 45 |
46 |
47 | Ces cookies échangent 48 | potentiellement des informations avec {{nb_third}} domaines 49 | tiers. 50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 |
59 | 61 |
62 |
63 |
64 |
65 | 67 |
68 |
69 | 70 |
71 |
72 |
73 | Présence des tiers sur les sites visités 74 | Lors de votre visite d’un site, des tiers peuvent déposer des 75 | cookies pour, entre autre, faire du suivi de navigation. 76 | Entre {{most_third[0]}} et 77 | {{most_third[1]}} tiers déposent des cookies 78 | sur 20% des sites visités. 79 | Entre {{rest_third[0]}} et 80 | {{rest_third[1]}} tiers déposent des cookies 81 | sur 80% des sites visites. 82 |
83 |
84 |
85 |
86 | 87 | 90 |
91 |
92 |
93 | 112 |
113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /icons/browser.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/browser.icns -------------------------------------------------------------------------------- /icons/browser.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/browser.ico -------------------------------------------------------------------------------- /icons/browser_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/browser_128x128.png -------------------------------------------------------------------------------- /icons/browser_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/browser_16x16.png -------------------------------------------------------------------------------- /icons/browser_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/browser_256x256.png -------------------------------------------------------------------------------- /icons/browser_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/browser_512x512.png -------------------------------------------------------------------------------- /icons/browser_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/browser_64x64.png -------------------------------------------------------------------------------- /icons/empty_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LINCnil/CookieViz/ad17c8075be1c0430a992e0a0a8a8f6046d54877/icons/empty_favicon.png -------------------------------------------------------------------------------- /installer.nsi: -------------------------------------------------------------------------------- 1 | # define name of installer 2 | OutFile "CookieViz 2.3.0 win x86.exe" 3 | 4 | # define installation directory 5 | InstallDir $APPDATA\CookieViz\ 6 | 7 | # For removing Start Menu shortcut in Windows 7 8 | RequestExecutionLevel user 9 | 10 | # start default section 11 | Section 12 | 13 | # set the installation directory as the destination for the following actions 14 | SetOutPath $INSTDIR 15 | 16 | # create the uninstaller 17 | WriteUninstaller "$INSTDIR\uninstall.exe" 18 | 19 | # create a shortcut named "new shortcut" in the start menu programs directory 20 | # point the new shortcut at the program uninstaller 21 | CreateShortcut "$SMPROGRAMS\Uinstall CookieViz.lnk" "$INSTDIR\uninstall.exe" 22 | SectionEnd 23 | 24 | # uninstaller section start 25 | Section "uninstall" 26 | 27 | # first, delete the uninstaller 28 | Delete "$INSTDIR\uninstall.exe" 29 | 30 | # second, remove the link from the start menu 31 | Delete "$SMPROGRAMS\Uinstall CookieViz.lnk" 32 | 33 | RMDir $INSTDIR 34 | # uninstaller section end 35 | SectionEnd -------------------------------------------------------------------------------- /js/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.7.9 3 | (c) 2010-2018 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(n,e){'use strict';function m(d,k,l){var a=l.baseHref(),h=d[0];return function(f,b,c){var d,g;c=c||{};g=c.expires;d=e.isDefined(c.path)?c.path:a;e.isUndefined(b)&&(g="Thu, 01 Jan 1970 00:00:00 GMT",b="");e.isString(g)&&(g=new Date(g));b=encodeURIComponent(f)+"="+encodeURIComponent(b);b=b+(d?";path="+d:"")+(c.domain?";domain="+c.domain:"");b+=g?";expires="+g.toUTCString():"";b+=c.secure?";secure":"";b+=c.samesite?";samesite="+c.samesite:"";c=b.length+1;4096 4096 bytes)!");h.cookie=b}}e.module("ngCookies",["ng"]).info({angularVersion:"1.7.9"}).provider("$cookies",[function(){var d=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(k,l){return{get:function(a){return k()[a]},getObject:function(a){return(a=this.get(a))?e.fromJson(a):a},getAll:function(){return k()},put:function(a,h,f){l(a,h,f?e.extend({},d,f):d)},putObject:function(a,d,f){this.put(a,e.toJson(d),f)},remove:function(a,h){l(a,void 0,h?e.extend({},d,h):d)}}}]}]);m.$inject= 8 | ["$document","$log","$browser"];e.module("ngCookies").provider("$$cookieWriter",function(){this.$get=m})})(window,window.angular); 9 | //# sourceMappingURL=angular-cookies.min.js.map 10 | -------------------------------------------------------------------------------- /js/angular-translate-loader-static-files.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * angular-translate - v2.18.2 - 2020-01-04 3 | * 4 | * Copyright (c) 2020 The angular-translate team, Pascal Precht; Licensed MIT 5 | */ 6 | !function(e,i){"function"==typeof define&&define.amd?define([],function(){return i()}):"object"==typeof module&&module.exports?module.exports=i():i()}(0,function(){function e(n,a){"use strict";return function(r){if(!(r&&(angular.isArray(r.files)||angular.isString(r.prefix)&&angular.isString(r.suffix))))throw new Error("Couldn't load static files, no files and prefix or suffix specified!");r.files||(r.files=[{prefix:r.prefix,suffix:r.suffix}]);for(var e=function(e){if(!e||!angular.isString(e.prefix)||!angular.isString(e.suffix))throw new Error("Couldn't load static file, no prefix or suffix specified!");var i=[e.prefix,r.key,e.suffix].join("");return angular.isObject(r.fileMap)&&r.fileMap[i]&&(i=r.fileMap[i]),a(angular.extend({url:i,method:"GET"},r.$http)).then(function(e){return e.data},function(){return n.reject(r.key)})},i=[],t=r.files.length,f=0;f1)&&(f-=Math.floor(f));var e=Math.abs(f-.5);return wf.h=360*f-100,wf.s=1.5-1.5*e,wf.l=.8-.9*e,wf+""},f.interpolateRdBu=x,f.interpolateRdGy=g,f.interpolateRdPu=N,f.interpolateRdYlBu=v,f.interpolateRdYlGn=C,f.interpolateReds=hf,f.interpolateSinebow=function(f){var e;return f=(.5-f)*Math.PI,Af.r=255*(e=Math.sin(f))*e,Af.g=255*(e=Math.sin(f+Pf))*e,Af.b=255*(e=Math.sin(f+Bf))*e,Af+""},f.interpolateSpectral=I,f.interpolateTurbo=function(f){return f=Math.max(0,Math.min(1,f)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+f*(1172.33-f*(10793.56-f*(33300.12-f*(38394.49-14825.05*f)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+f*(557.33+f*(1225.33-f*(3574.96-f*(1073.77+707.56*f)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+f*(3211.1-f*(15327.97-f*(27814-f*(22569.18-6838.66*f)))))))+")"},f.interpolateViridis=xf,f.interpolateWarm=yf,f.interpolateYlGn=Z,f.interpolateYlGnBu=U,f.interpolateYlOrBr=ff,f.interpolateYlOrRd=df,f.schemeAccent=b,f.schemeBlues=af,f.schemeBrBG=u,f.schemeBuGn=L,f.schemeBuPu=q,f.schemeCategory10=c,f.schemeDark2=t,f.schemeGnBu=T,f.schemeGreens=bf,f.schemeGreys=nf,f.schemeOrRd=k,f.schemeOranges=pf,f.schemePRGn=y,f.schemePaired=n,f.schemePastel1=r,f.schemePastel2=o,f.schemePiYG=w,f.schemePuBu=E,f.schemePuBuGn=W,f.schemePuOr=P,f.schemePuRd=H,f.schemePurples=of,f.schemeRdBu=G,f.schemeRdGy=R,f.schemeRdPu=K,f.schemeRdYlBu=Y,f.schemeRdYlGn=O,f.schemeReds=mf,f.schemeSet1=i,f.schemeSet2=l,f.schemeSet3=m,f.schemeSpectral=S,f.schemeTableau10=h,f.schemeYlGn=X,f.schemeYlGnBu=Q,f.schemeYlOrBr=$,f.schemeYlOrRd=ef,Object.defineProperty(f,"__esModule",{value:!0})}); 3 | -------------------------------------------------------------------------------- /js/d3-voronoi-map.js: -------------------------------------------------------------------------------- 1 | (function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?factory(exports,require('d3-polygon'),require('d3-weighted-voronoi')):typeof define==='function'&&define.amd?define(['exports','d3-polygon','d3-weighted-voronoi'],factory):(factory((global.d3=global.d3||{}),global.d3,global.d3));}(this,function(exports,d3Polygon,d3WeightedVoronoi){'use strict';function FlickeringMitigation(){this.growthChangesLength=DEFAULT_LENGTH;this.totalAvailableArea=NaN;this.lastAreaError=NaN;this.lastGrowth=NaN;this.growthChanges=[];this.growthChangeWeights=generateGrowthChangeWeights(this.growthChangesLength);this.growthChangeWeightsSum=computeGrowthChangeWeightsSum(this.growthChangeWeights);} 2 | var DEFAULT_LENGTH=10;function direction(h0,h1){return(h0>=h1)?1:-1;} 3 | function generateGrowthChangeWeights(length){var initialWeight=3;var weightDecrement=1;var minWeight=1;var weightedCount=initialWeight;var growthChangeWeights=[];for(var i=0;i0){this.growthChangesLength=Math.floor(parseInt(_));this.growthChangeWeights=generateGrowthChangeWeights(this.growthChangesLength);this.growthChangeWeightsSum=computeGrowthChangeWeightsSum(this.growthChangeWeights);}else{console.warn("FlickeringMitigation.length() accepts only positive integers; unable to handle "+_);} 9 | return this;};FlickeringMitigation.prototype.totalArea=function(_){if(!arguments.length){return this.totalAvailableArea;} 10 | if(parseFloat(_)>0){this.totalAvailableArea=parseFloat(_);}else{console.warn("FlickeringMitigation.totalArea() accepts only positive numbers; unable to handle "+_);} 11 | return this;};FlickeringMitigation.prototype.add=function(areaError){var secondToLastAreaError,secondToLastGrowth;secondToLastAreaError=this.lastAreaError;this.lastAreaError=areaError;if(!isNaN(secondToLastAreaError)){secondToLastGrowth=this.lastGrowth;this.lastGrowth=direction(this.lastAreaError,secondToLastAreaError);} 12 | if(!isNaN(secondToLastGrowth)){this.growthChanges.unshift(this.lastGrowth!=secondToLastGrowth);} 13 | if(this.growthChanges.length>this.growthChangesLength){this.growthChanges.pop();} 14 | return this;};FlickeringMitigation.prototype.ratio=function(){var weightedChangeCount=0;var ratio;if(this.growthChanges.lengththis.totalAvailableArea/10){return 0;} 16 | for(var i=0;i0){console.log("flickering mitigation ratio: "+Math.floor(ratio*1000)/1000);} 18 | return ratio;};function randomInitialPosition(){var clippingPolygon,extent,minX,maxX,minY,maxY,dx,dy;function _random(d,i,arr,voronoiMap){var shouldUpdateInternals=false;var x,y;if(clippingPolygon!==voronoiMap.clip()){clippingPolygon=voronoiMap.clip();extent=voronoiMap.extent();shouldUpdateInternals=true;} 19 | if(shouldUpdateInternals){updateInternals();} 20 | x=minX+dx*voronoiMap.prng()();y=minY+dy*voronoiMap.prng()();while(!d3Polygon.polygonContains(clippingPolygon,[x,y])){x=minX+dx*voronoiMap.prng()();y=minY+dy*voronoiMap.prng()();} 21 | return[x,y];};function updateInternals(){minX=extent[0][0];maxX=extent[1][0];minY=extent[0][1];maxY=extent[1][1];dx=maxX-minX;dy=maxY-minY;};return _random;};function pie(){var startAngle=0;var clippingPolygon,dataArray,dataArrayLength,clippingPolygonCentroid,halfIncircleRadius,angleBetweenData;function _pie(d,i,arr,voronoiMap){var shouldUpdateInternals=false;if(clippingPolygon!==voronoiMap.clip()){clippingPolygon=voronoiMap.clip();shouldUpdateInternals|=true;} 22 | if(dataArray!==arr){dataArray=arr;shouldUpdateInternals|=true;} 23 | if(shouldUpdateInternals){updateInternals();} 24 | return[clippingPolygonCentroid[0]+Math.cos(startAngle+i*angleBetweenData)*halfIncircleRadius+(voronoiMap.prng()()-0.5)*1E-3,clippingPolygonCentroid[1]+Math.sin(startAngle+i*angleBetweenData)*halfIncircleRadius+(voronoiMap.prng()()-0.5)*1E-3];};_pie.startAngle=function(_){if(!arguments.length){return startAngle;} 25 | startAngle=_;return _pie;};function updateInternals(){clippingPolygonCentroid=d3Polygon.polygonCentroid(clippingPolygon);halfIncircleRadius=computeMinDistFromEdges(clippingPolygonCentroid,clippingPolygon)/2;dataArrayLength=dataArray.length;angleBetweenData=2*Math.PI/dataArrayLength;};function computeMinDistFromEdges(vertex,clippingPolygon){var minDistFromEdges=Infinity,edgeIndex=0,edgeVertex0=clippingPolygon[clippingPolygon.length-1],edgeVertex1=clippingPolygon[edgeIndex];var distFromCurrentEdge;while(edgeIndex1){xx=x2;yy=y2;}else{xx=x1+param*C;yy=y1+param*D;} 30 | var dx=x-xx;var dy=y-yy;return Math.sqrt(dx*dx+dy*dy);} 31 | return _pie;} 32 | function halfAverageAreaInitialWeight(){var clippingPolygon,dataArray,siteCount,totalArea,halfAverageArea;function _halfAverageArea(d,i,arr,voronoiMap){var shouldUpdateInternals=false;if(clippingPolygon!==voronoiMap.clip()){clippingPolygon=voronoiMap.clip();shouldUpdateInternals|=true;} 33 | if(dataArray!==arr){dataArray=arr;shouldUpdateInternals|=true;} 34 | if(shouldUpdateInternals){updateInternals();} 35 | return halfAverageArea;};function updateInternals(){siteCount=dataArray.length;totalArea=d3Polygon.polygonArea(clippingPolygon);halfAverageArea=totalArea/siteCount/2;} 36 | return _halfAverageArea;};function voronoiMap(){var DEFAULT_CONVERGENCE_RATIO=0.01;var DEFAULT_MAX_ITERATION_COUNT=50;var DEFAULT_MIN_WEIGHT_RATIO=0.01;var DEFAULT_PRNG=Math.random;var DEFAULT_INITIAL_POSITION=randomInitialPosition();var DEFAULT_INITIAL_WEIGHT=halfAverageAreaInitialWeight();var RANDOM_INITIAL_POSITION=randomInitialPosition();var epsilon=1;var weight=function(d){return d.weight;};var convergenceRatio=DEFAULT_CONVERGENCE_RATIO;var maxIterationCount=DEFAULT_MAX_ITERATION_COUNT;var minWeightRatio=DEFAULT_MIN_WEIGHT_RATIO;var prng=DEFAULT_PRNG;var initialPosition=DEFAULT_INITIAL_POSITION;var initialWeight=DEFAULT_INITIAL_WEIGHT;var tick=function(polygons,i){return true;};var weightedVoronoi=d3WeightedVoronoi.weightedVoronoi();var siteCount,totalArea,areaErrorTreshold,flickeringMitigation=new FlickeringMitigation();var handleOverweightedVariant=1;var handleOverweighted;function sqr(d){return Math.pow(d,2);} 37 | function squaredDistance(s0,s1){return sqr(s1.x-s0.x)+sqr(s1.y-s0.y);} 38 | function _voronoiMap(data){setHandleOverweighted();siteCount=data.length;(totalArea=Math.abs(d3Polygon.polygonArea(weightedVoronoi.clip()))),(areaErrorTreshold=convergenceRatio*totalArea);flickeringMitigation.clear().totalArea(totalArea);var iterationCount=0,polygons=initialize(data),converged=false;var areaError;tick(polygons,iterationCount);while(!(converged||iterationCount>=maxIterationCount)){polygons=adapt(polygons,flickeringMitigation.ratio());iterationCount++;areaError=computeAreaError(polygons);flickeringMitigation.add(areaError);converged=areaErrortpj.weight){weightest=tpi;lightest=tpj;}else{weightest=tpj;lightest=tpi;} 59 | sqrD=squaredDistance(tpi,tpj);if(sqrDtpj.weight){weightest=tpi;lightest=tpj;}else{weightest=tpj;lightest=tpi;} 62 | sqrD=squaredDistance(tpi,tpj);if(sqrD=-epsilon;} 2 | function dot(v0,v1){return v0.x*v1.x+v0.y*v1.y+v0.z*v1.z;} 3 | function linearDependent(v0,v1){return(epsilonesque(v0.x*v1.y-v0.y*v1.x)&&epsilonesque(v0.y*v1.z-v0.z*v1.y)&&epsilonesque(v0.z*v1.x-v0.x*v1.z));} 4 | function polygonDirection(polygon){var direction,sign,crossproduct,p0,p1,p2,v0,v1,i;p0=polygon[polygon.length-2];p1=polygon[polygon.length-1];p2=polygon[0];v0=vect(p0,p1);v1=vect(p1,p2);crossproduct=calculateCrossproduct(v0,v1);sign=Math.sign(crossproduct);p0=p1;p1=p2;p2=polygon[1];v0=v1;v1=vect(p1,p2);crossproduct=calculateCrossproduct(v0,v1);if(Math.sign(crossproduct)!==sign){return undefined;} 5 | for(i=2;i0){this.x/=lenght;this.y/=lenght;this.z/=lenght;}} 48 | function HEdge(orig,dest,face){this.next=null;this.prev=null;this.twin=null;this.orig=orig;this.dest=dest;this.iFace=face;} 49 | HEdge.prototype.isHorizon=function(){return this.twin!==null&&!this.iFace.marked&&this.twin.iFace.marked;} 50 | HEdge.prototype.findHorizon=function(horizon){if(this.isHorizon()){if(horizon.length>0&&this===horizon[0]){return;}else{horizon.push(this);this.next.findHorizon(horizon);}}else{if(this.twin!==null){this.twin.next.findHorizon(horizon);}}} 51 | HEdge.prototype.isEqual=function(origin,dest){return((this.orig.equals(origin)&&this.dest.equals(dest))||(this.orig.equals(dest)&&this.dest.equals(origin)));} 52 | function Face(a,b,c,orient){this.conflicts=new ConflictList(true);this.verts=[a,b,c];this.marked=false;var t=(a.subtract(b)).crossproduct(b.subtract(c));this.normal=new Vector(-t.x,-t.y,-t.z);this.normal.normalize();this.createEdges();this.dualPoint=null;if(orient!=undefined){this.orient(orient);}} 53 | Face.prototype.getDualPoint=function(){if(this.dualPoint==null){var plane3d=new Plane3D(this);this.dualPoint=plane3d.getDualPointMappedToPlane();} 54 | return this.dualPoint;} 55 | Face.prototype.isVisibleFromBelow=function(){return(this.normal.z<-1.4259414393190911E-9);} 56 | Face.prototype.createEdges=function(){this.edges=[];this.edges[0]=new HEdge(this.verts[0],this.verts[1],this);this.edges[1]=new HEdge(this.verts[1],this.verts[2],this);this.edges[2]=new HEdge(this.verts[2],this.verts[0],this);this.edges[0].next=this.edges[1];this.edges[0].prev=this.edges[2];this.edges[1].next=this.edges[2];this.edges[1].prev=this.edges[0];this.edges[2].next=this.edges[0];this.edges[2].prev=this.edges[1];} 57 | Face.prototype.orient=function(orient){if(!(dot(this.normal,orient)dot(this.normal,this.verts[0])+epsilon);} 64 | Face.prototype.getHorizon=function(){for(var i=0;i<3;i++){if(this.edges[i].twin!==null&&this.edges[i].twin.isHorizon()){return this.edges[i];}} 65 | return null;} 66 | Face.prototype.removeConflict=function(){this.conflicts.removeAll();} 67 | function ConvexHull(){this.points=[];this.facets=[];this.created=[];this.horizon=[];this.visible=[];this.current=0;} 68 | ConvexHull.prototype.init=function(boundingSites,sites){this.points=[];for(var i=0;i0;i--){var ra=Math.floor(Math.random()*i);var temp=this.points[ra];temp.index=i;var currentItem=this.points[i];currentItem.index=ra;this.points.splice(ra,1,currentItem);this.points.splice(i,1,temp);}} 71 | ConvexHull.prototype.prep=function(){if(this.points.length<=3){console.log("ERROR: Less than 4 points");} 72 | for(var i=0;iv2.index){nCL.push(v1);i++;}else{nCL.push(v2);l++;}}else if(i=0;i--){v1=nCL[i];if(fn.conflict(v1)) 82 | this.addConflict(fn,v1);}} 83 | ConvexHull.prototype.addConflict=function(face,vert){var e=new ConflictListNode(face,vert);face.conflicts.add(e);vert.conflicts.add(e);} 84 | ConvexHull.prototype.removeConflict=function(f){f.removeConflict();var index=f.index;f.index=-1;if(index===this.facets.length-1){this.facets.splice(this.facets.length-1,1);return;} 85 | if(index>=this.facets.length||index<0) 86 | return;var last=this.facets.splice(this.facets.length-1,1);last[0].index=index;this.facets.splice(index,1,last[0]);} 87 | ConvexHull.prototype.addFacet=function(face){face.index=this.facets.length;this.facets.push(face);} 88 | ConvexHull.prototype.compute=function(){this.prep();while(this.currentepsilon||dy>epsilon){protopoly.push([x1,y1]);lastX=x1;lastY=y1;}} 111 | site.nonClippedPolygon=protopoly.reverse();if(!site.isDummy&&d3Polygon.polygonLength(site.nonClippedPolygon)>0){var clippedPoly=polygonClip(clippingPolygon,site.nonClippedPolygon);site.polygon=clippedPoly;clippedPoly.site=site;if(clippedPoly.length>0){polygons.push(clippedPoly);}}}}}} 112 | return polygons;} 113 | function weightedVoronoi(){var x=function(d){return d.x;};var y=function(d){return d.y;};var weight=function(d){return d.weight;};var clip=[[0,0],[0,1],[1,1],[1,0]];var extent=[[0,0],[1,1]];var size=[1,1];function _weightedVoronoi(data){var formatedSites;formatedSites=data.map(function(d){return new Vertex(x(d),y(d),null,weight(d),d,false);});return computePowerDiagramIntegrated(formatedSites,boundingSites(),clip);} 114 | _weightedVoronoi.x=function(_){if(!arguments.length){return x;} 115 | x=_;return _weightedVoronoi;};_weightedVoronoi.y=function(_){if(!arguments.length){return y;} 116 | y=_;return _weightedVoronoi;};_weightedVoronoi.weight=function(_){if(!arguments.length){return weight;} 117 | weight=_;return _weightedVoronoi;};_weightedVoronoi.clip=function(_){var direction,xExtent,yExtent;if(!arguments.length){return clip;} 118 | xExtent=d3Array.extent(_.map(function(c){return c[0];}));yExtent=d3Array.extent(_.map(function(c){return c[1];}));direction=polygonDirection(_);if(direction===undefined){clip=d3Polygon.polygonHull(_);}else if(direction===1){clip=_.reverse();}else{clip=_;} 119 | extent=[[xExtent[0],yExtent[0]],[xExtent[1],yExtent[1]]];size=[xExtent[1]-xExtent[0],yExtent[1]-yExtent[0]];return _weightedVoronoi;};_weightedVoronoi.extent=function(_){if(!arguments.length){return extent;} 120 | clip=[_[0],[_[0][0],_[1][1]],_[1],[_[1][0],_[0][1]]];extent=_;size=[_[1][0]-_[0][0],_[1][1]-_[0][1]];return _weightedVoronoi;};_weightedVoronoi.size=function(_){if(!arguments.length){return size;} 121 | clip=[[0,0],[0,_[1]],[_[0],_[1]],[_[0],0]];extent=[[0,0],_];size=_;return _weightedVoronoi;};function boundingSites(){var minX,maxX,minY,maxY,width,height,x0,x1,y0,y1,boundingData=[],boundingSites=[];minX=extent[0][0];maxX=extent[1][0];minY=extent[0][1];maxY=extent[1][1];width=maxX-minX;height=maxY-minY;x0=minX-width;x1=maxX+width;y0=minY-height;y1=maxY+height;boundingData[0]=[x0,y0];boundingData[1]=[x0,y1];boundingData[2]=[x1,y1];boundingData[3]=[x1,y0];for(var i=0;i<4;i++){boundingSites.push(new Vertex(boundingData[i][0],boundingData[i][1],null,epsilon,new Vertex(boundingData[i][0],boundingData[i][1],null,epsilon,null,true),true));} 122 | return boundingSites;} 123 | return _weightedVoronoi;} 124 | exports.weightedVoronoi=weightedVoronoi;Object.defineProperty(exports,'__esModule',{value:true});})); -------------------------------------------------------------------------------- /languages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "navigation":{ 4 | "PREV_PAGE": "Previous page", 5 | "NEXT_PAGE": "Next page", 6 | "REFRESH_PAGE": "Refresh page" 7 | }, 8 | "LANGUAGES":"Set language", 9 | "visualizations":{ 10 | "VIZ" : "Open viz" 11 | }, 12 | "parameters":{ 13 | "TITLE" : "Parameters", 14 | "CLEAN_DATA": "Clean data", 15 | "LOAD_VISIT_TXT": "Load a visit path from a text file" 16 | }, 17 | "visit":{ 18 | "HEAD_VISIT":"Define a visit path", 19 | "START_VISIT" : "Start visit", 20 | "REINIT_VISIT": "Reinitialize visit", 21 | "PREVIOUS_VISIT": "Previous page", 22 | "NEXT_VISIT":"Next page", 23 | "ADD_VISIT":"Add page", 24 | "REMOVE_VISIT":"Remove page", 25 | "MINIMIZE_VISIT":"Minimize/maximize visit", 26 | "CLOSE_VISIT":"Close visit", 27 | "EXPORT_VISIT" : "Export visit to text file", 28 | "IMPORT_VISIT" : "Import visit from text file", 29 | "DOWNLOAD_VISIT" : "Download visit list from Internet", 30 | "alexa":{ 31 | "HEAD_ALEXA":"Load a visit from Alexa Top Site", 32 | "VISIT_SHOW":"Show visit bar", 33 | "API_KEY" : "Api key", 34 | "LIST_SIZE" : "Number of website to retrieve", 35 | "COUNTRY_CODE" : "Country Code", 36 | "STORE_VISIT" : "Get Alexa list" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /languages/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "navigation":{ 4 | "PREV_PAGE": "Página anterior", 5 | "NEXT_PAGE": "Página siguiente", 6 | "REFRESH_PAGE": "Recargar página" 7 | }, 8 | "LANGUAGES":"Idiomas", 9 | "visualizations":{ 10 | "VIZ" : "Abrir viz" 11 | }, 12 | "parameters":{ 13 | "TITLE" : "Parámetros", 14 | "CLEAN_DATA": "Limpiar datos", 15 | "LOAD_VISIT_TXT": "Cargar lista de visitas de fichero de texto" 16 | }, 17 | "visit":{ 18 | "HEAD_VISIT":"Definir lista de visitas", 19 | "START_VISIT" : "Iniciar visitas", 20 | "REINIT_VISIT": "Reiniciar visitas", 21 | "PREVIOUS_VISIT": "Página anterior", 22 | "NEXT_VISIT":"Página siguiente", 23 | "ADD_VISIT":"Añadir página", 24 | "REMOVE_VISIT":"Eliminar página", 25 | "MINIMIZE_VISIT":"Minimizar/maximizar visita", 26 | "CLOSE_VISIT":"Cerrar visita", 27 | "EXPORT_VISIT" : "Exportar lista a fichero de texto", 28 | "IMPORT_VISIT" : "Importar lista de fichero de texto", 29 | "DOWNLOAD_VISIT" : "Descargar lista de sitios web de internet", 30 | "alexa":{ 31 | "HEAD_ALEXA":"Cargar una lista de sitios web Alexa Top Sites", 32 | "VISIT_SHOW":"Mostrar barra de visitas", 33 | "API_KEY" : "Api key", 34 | "LIST_SIZE" : "Número de sitios web a obtener", 35 | "COUNTRY_CODE" : "Código de país", 36 | "STORE_VISIT" : "Descargar lista de Alexa" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /languages/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "SPLASH": "Découvrez la face cachée du Web!", 4 | "navigation": { 5 | "PREV_PAGE": "Page précédente", 6 | "NEXT_PAGE": "Page suivante", 7 | "REFRESH_PAGE": "Actualiser cette page" 8 | }, 9 | "LANGUAGES": "Changer la langue", 10 | "visualizations": { 11 | "VIZ": "Ouvrir la fenêtre de visualisation" 12 | }, 13 | "parameters": { 14 | "TITLE": "Paramètres du navigateur", 15 | "CLEAN_DATA": "Nettoyer les données", 16 | "LOAD_VISIT_TXT": "Charger un chemin de visite depuis un fichier texte" 17 | }, 18 | "visit": { 19 | "HEAD_VISIT": "Définir un parcours de visite", 20 | "START_VISIT": "Commencer la visite", 21 | "REINIT_VISIT": "Réinitialiser la visite", 22 | "PREVIOUS_VISIT": "Page précédente", 23 | "NEXT_VISIT": "Page suivante", 24 | "ADD_VISIT": "Ajouter la page", 25 | "REMOVE_VISIT": "Enlever cette page", 26 | "MINIMIZE_VISIT": "Réduire/maximiser la fenêtre", 27 | "CLOSE_VISIT": "Fermer la fenêtre", 28 | "EXPORT_VISIT": "Exporter la navigation vers un fichier texte", 29 | "IMPORT_VISIT": "Importer la navigation depuis un fichier texte", 30 | "DOWNLOAD_VISIT" : "Télécharger la navigation depuis Internet", 31 | "alexa":{ 32 | "HEAD_ALEXA":"Charger la visite depuis le Alexa Top Site", 33 | "VISIT_SHOW":"Afficher la bar de visite", 34 | "API_KEY" : "Clef API amazon", 35 | "LIST_SIZE" : "Nombre de sites à charger", 36 | "COUNTRY_CODE" : "Code pays", 37 | "STORE_VISIT" : "Récupérer la liste Alexa" 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /languages/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "navigation":{ 4 | "PREV_PAGE": "Página anterior", 5 | "NEXT_PAGE": "Próxima página", 6 | "REFRESH_PAGE": "Recarregar página" 7 | }, 8 | "LANGUAGES":"Definir idioma", 9 | "visualizations":{ 10 | "VIZ" : "Abrir visualizador" 11 | }, 12 | "parameters":{ 13 | "TITLE" : "Parâmetros", 14 | "CLEAN_DATA": "Limpar dados", 15 | "LOAD_VISIT_TXT": "Carregar uma sequência de sites a partir de um arquivo de texto" 16 | }, 17 | "visit":{ 18 | "HEAD_VISIT":"Definir uma sequência de visitas", 19 | "START_VISIT" : "Iniciar a visita", 20 | "REINIT_VISIT": "Reiniciar a visita", 21 | "PREVIOUS_VISIT": "Página anterior", 22 | "NEXT_VISIT":"Próxima página", 23 | "ADD_VISIT":"Adicionar página", 24 | "REMOVE_VISIT":"Remover página", 25 | "MINIMIZE_VISIT":"Minimizar", 26 | "CLOSE_VISIT":"Fechar", 27 | "EXPORT_VISIT" : "Exportar sequência para arquivo de texto", 28 | "IMPORT_VISIT" : "Importar sequência de arquivo de texto", 29 | "DOWNLOAD_VISIT" : "Download de sequência de visitas a partir da Internet", 30 | "alexa":{ 31 | "HEAD_ALEXA":"Carregar uma sequência dos Top Sites da Alexa", 32 | "VISIT_SHOW":"Exibir a barra de visita", 33 | "API_KEY" : "Chave API da Amazon", 34 | "LIST_SIZE" : "Número de sites a carregar", 35 | "COUNTRY_CODE" : "Código do país", 36 | "STORE_VISIT" : "Obter a lista da Alexa" 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "browser.html", 3 | "name": "CookieViz", 4 | "description": "CookieViz est outil de visualisation qui permet de mesurer l'impact des cookies lors de votre propre navigation.", 5 | "version": "2.3.0", 6 | "nodejs": true, 7 | "keywords": [ 8 | "cookie", 9 | "CNIL", 10 | "CookieViz" 11 | ], 12 | "window": { 13 | "title": "CookieViz", 14 | "icon": "icons/browser-128x128.png", 15 | "toolbar": false, 16 | "frame": true, 17 | "width": 1024, 18 | "height": 700, 19 | "min_width": 250, 20 | "min_height": 250 21 | }, 22 | "webkit": { 23 | "plugin": true 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/LINCnil/CookieViz" 28 | }, 29 | "author": "Laboratoire de l'Innovation de la CNIL (LINC)", 30 | "license": "LGPL3", 31 | "bugs": { 32 | "url": "https://github.com/LINCnil/CookieViz/issues" 33 | }, 34 | "dependencies": { 35 | "ads.txt": "^0.4.0", 36 | "get-website-favicon": "0.0.7", 37 | "iso-639-1": "^2.1.1", 38 | "jszip": "^3.5.0", 39 | "psl": "^1.4.0" 40 | }, 41 | "devDependencies": { 42 | "nwjs-builder-phoenix": "^1.15.0" 43 | }, 44 | "scripts": { 45 | "dist": "build --tasks win-x86,win-x64,linux-x86,linux-x64,mac-x64 --mirror https://dl.nwjs.io/ .", 46 | "start": "run --x64 --mirror https://dl.nwjs.io/ ." 47 | }, 48 | "build": { 49 | "appId": "cnil.CookieViz", 50 | "nwVersion": "0.64.1", 51 | "packed": true, 52 | "targets": [ 53 | "zip", 54 | "nsis7z" 55 | ], 56 | "strippedProperties": [ 57 | "build" 58 | ], 59 | "outputPattern": "${NAME} ${VERSION} ${PLATFORM} ${ARCH}", 60 | "win": { 61 | "productName": "CookieViz", 62 | "companyName": "CNIL", 63 | "copyright": "CNIL", 64 | "icon": "./icons/browser.ico" 65 | }, 66 | "mac": { 67 | "displayName": "CookieViz", 68 | "copyright": "CNIL", 69 | "icon": "./icons/browser.icns", 70 | "plistStrings": { 71 | "CFBundleIdentifier": "cnil.CookieViz", 72 | "CFBundleDocumentTypes": [] 73 | } 74 | }, 75 | "nsis": { 76 | "installDirectory": "$APPDATA\\${_COMPANYNAME}\\${_APPNAME}", 77 | "diffUpdaters": true 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /parameters/clean_data.js: -------------------------------------------------------------------------------- 1 | function cleanCache() { 2 | function clearChrome() { 3 | window.nw.App.clearCache(); 4 | 5 | // cause significantly increase of shutdown duration 6 | window.chrome.browsingData.remove({ 7 | since: 0 8 | }, { 9 | appcache: true, 10 | cache: true, 11 | cookies: true, 12 | downloads: true, 13 | fileSystems: true, 14 | formData: true, 15 | history: true, 16 | indexedDB: true, 17 | localStorage: true, 18 | pluginData: true, 19 | passwords: true, 20 | serverBoundCertificates: true, 21 | serviceWorkers: true, 22 | webSQL: true 23 | }); 24 | } 25 | 26 | clearPlugins(); 27 | clearChrome(); 28 | viz_windows.forEach(viz_win => viz_win.close()); 29 | viz_windows.length = 0; 30 | } 31 | 32 | createDropdownElt('parameter-menus', "{{'browser.parameters.CLEAN_DATA' | translate}}", cleanCache, "clean_data"); -------------------------------------------------------------------------------- /parameters/visit_path.js: -------------------------------------------------------------------------------- 1 | 2 | var visit_timeout = null; 3 | const apiHost = 'ats.api.alexa.com'; 4 | 5 | async function checkUrls(sites) { 6 | const prefixs = ["https://www.", "https://", "http://www.", "http://"]; 7 | const ok_urls = []; 8 | for (const site of sites) { 9 | for (const prefix of prefixs) { 10 | const url = prefix + site.DataUrl; 11 | try { 12 | const response = await fetch(url); 13 | ok_urls.push(url); 14 | break; 15 | } catch (error) { 16 | continue; 17 | } 18 | } 19 | } 20 | 21 | openVisit(ok_urls); 22 | } 23 | 24 | async function callATS(apikey, country, count) { 25 | const list_interval = 10; 26 | const error_div = document.getElementById("alexa_response"); 27 | var i = 1; 28 | error_div.innerHTML = "0%"; 29 | while (i < count + 1) { 30 | const start = i; 31 | const count_frame = i + list_interval < count + 1 ? list_interval : count - i + 1; 32 | 33 | var uri = '/api?Action=TopSites&Count=' + count_frame + '&CountryCode=' + country + '&ResponseGroup=Country&Output=json&Start=' + start; 34 | 35 | var opts = { 36 | json: true, 37 | headers: { 'x-api-key': apikey }, 38 | resolveWithFullResponse: true 39 | } 40 | 41 | const response = await fetch('https://' + apiHost + uri, opts); 42 | const json = await response.json(); 43 | if (json.Ats.Results.ResponseStatus.StatusCode != "200") { 44 | error_div.innerHTML = 'failed to fetch list from alexa :' + json.Ats.Results.ResponseStatus.StatusCode; 45 | throw data.Ats.Results.Result.Alexa.Request.Errors.Error.ErrorCode 46 | } 47 | await checkUrls(json.Ats.Results.Result.Alexa.TopSites.Country.Sites.Site); 48 | i += list_interval; 49 | error_div.innerHTML = Math.round(100 * i / count).toString() + "%"; 50 | } 51 | error_div.innerHTML = "OK"; 52 | } 53 | 54 | function load_visit_txt(ffile) { 55 | // List of website to visit 56 | let visit_list = []; 57 | 58 | const lineReader = require('readline').createInterface({ 59 | input: require('fs').createReadStream(ffile) 60 | }); 61 | 62 | lineReader.on('line', function (line, last) { 63 | visit_list.push(line); 64 | }); 65 | 66 | lineReader.on('close', function () { 67 | openVisit(visit_list); 68 | }) 69 | 70 | } 71 | 72 | function closeVisit() { 73 | window.nwjsVisitBar.style.display = "none"; 74 | const L = window.nwjsVisitList.length; 75 | for (var i = L; i >= 0; i--) { 76 | window.nwjsVisitList.remove(i); 77 | } 78 | window.nwjsVisitBar.removeEventListener( 79 | 'click', 80 | navigate_visit 81 | ); 82 | 83 | window.nwjsVisitList.removeEventListener( 84 | 'change', 85 | visit_url 86 | ); 87 | } 88 | 89 | //Sortable chart windows 90 | function openVisit(visit_list) { 91 | visit_list.forEach((visit) => { 92 | let opt = document.createElement('option'); 93 | opt.appendChild(document.createTextNode(visit)); 94 | window.nwjsVisitList.appendChild(opt); 95 | }) 96 | 97 | window.nwjsVisitBar.style.display = "block"; 98 | 99 | window.nwjsVisitBar.addEventListener( 100 | 'click', 101 | navigate_visit 102 | ); 103 | 104 | window.nwjsVisitList.addEventListener( 105 | 'change', 106 | visit_url 107 | ); 108 | 109 | //Hidden filedialog 110 | const file_dialog_visit = document.createElement("INPUT"); 111 | file_dialog_visit.style = "display:none;"; 112 | file_dialog_visit.id = "fileDialog_path"; 113 | file_dialog_visit.type = "file"; 114 | file_dialog_visit.accept = ".txt,text/plain" 115 | document.querySelector('nav').appendChild(file_dialog_visit); 116 | window.nwjsDialogOpenVisit = file_dialog_visit; 117 | 118 | //Setup load txt trigger 119 | window.alexaModal = document.getElementById('visit_modal'); 120 | file_dialog_visit.addEventListener("change", function (evt) { 121 | load_visit_txt(this.value); 122 | this.value = ""; 123 | }, false); 124 | 125 | window.alexaForm = document.getElementById("alexa_form"); 126 | window.alexaForm.addEventListener("submit", function (event) { 127 | event.preventDefault(); 128 | callATS(event.target.api_key.value, event.target.country_select.value, parseInt(event.target.alexa_list_size.value)); 129 | }); 130 | }; 131 | 132 | function next_visit() { 133 | window.nwjsBrowser.removeEventListener("contentload", next_visit); 134 | clearTimeout(visit_timeout); 135 | setTimeout(function () { 136 | if (document.querySelector('#visit-play-pause').classList.contains("fa-pause") && window.nwjsVisitList.selectedIndex < window.nwjsVisitList.length - 1) { 137 | window.nwjsVisitList.selectedIndex++; 138 | visit_url(); 139 | } 140 | }, 1000); 141 | } 142 | 143 | function visit_url() { 144 | const url = window.nwjsVisitList.selectedOptions[0].text; 145 | window.nwjsBrowser.src = url; 146 | console.log("Visit -> Visiting url : " + url); 147 | nwjsHeader.querySelector('#address').value = url; 148 | nwjsBrowser.dispatchEvent(new_page_event); 149 | window.nwjsBrowser.addEventListener("contentload", next_visit); // In case page is loaded 150 | visit_timeout = setTimeout(next_visit, 60000); //In case page takes time to be fully loaded 151 | } 152 | 153 | function navigate_visit(e) { 154 | // Set action depending on click event 155 | switch (e.target.id) { 156 | case 'visit-close': 157 | closeVisit(); 158 | break; 159 | case 'visit-reduce': 160 | window.nwjsVisitBar.classList.toggle("reduce"); 161 | break; 162 | case 'visit-play-pause': 163 | if (e.target.classList.contains("fa-play")) { 164 | const idx = window.nwjsVisitList.selectedIndex; 165 | window.nwjsVisitList.selectedIndex = idx == -1 ? 0 : idx + 1; 166 | visit_url(); 167 | } 168 | e.target.classList.toggle("fa-play"); 169 | e.target.classList.toggle("fa-pause"); 170 | 171 | break; 172 | case 'visit-init': 173 | window.nwjsVisitList.selectedIndex = 0; 174 | visit_url(); 175 | break; 176 | case 'visit-next': 177 | if (window.nwjsVisitList.selectedIndex < window.nwjsVisitList.length - 1) { 178 | window.nwjsVisitList.selectedIndex++; 179 | visit_url(); 180 | } 181 | break; 182 | case 'visit-add': 183 | let opt = document.createElement('option'); 184 | const url = nwjsHeader.querySelector('#address').value; 185 | opt.appendChild(document.createTextNode(url)); 186 | window.nwjsVisitList.appendChild(opt); 187 | break; 188 | case 'visit-remove': 189 | window.nwjsVisitList.remove(window.nwjsVisitList.selectedOptions[0]); 190 | break; 191 | case 'visit-import': 192 | window.nwjsDialogOpenVisit.click(); 193 | break; 194 | case 'visit-export': 195 | const urls = Array.from(window.nwjsVisitList.options); 196 | var blob = new Blob([urls.map(x => x.text).join("\n")], { type: 'text/plain' }); 197 | var a = document.createElement("a"); 198 | document.body.appendChild(a); 199 | a.style = "display: none"; 200 | const export_url = window.URL.createObjectURL(blob); 201 | a.href = export_url; 202 | a.download = "exported-list-from-" + new Date().toLocaleDateString() + ".txt"; 203 | a.click(); 204 | window.URL.revokeObjectURL(export_url); 205 | break; 206 | case 'visit-download': 207 | // Show download modal 208 | const modal = document.getElementById("visit_modal"); 209 | modal.style.display = "block"; 210 | case 'visit-prev': 211 | if (window.nwjsVisitList.selectedIndex > 0) { 212 | window.nwjsVisitList.selectedIndex--; 213 | visit_url(); 214 | } 215 | break; 216 | } 217 | } 218 | 219 | 220 | 221 | 222 | createDropdownElt('parameter-menus', "{{'browser.visit.HEAD_VISIT' | translate}}", function () { 223 | if (window.nwjsVisitBar.style.display = "none") 224 | openVisit([]); 225 | }, "load_visit"); -------------------------------------------------------------------------------- /pt.cookieviz.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 | 34 |
35 |
36 |
37 | Your navigation
38 |
39 |
40 | You have visited {{nb_visited}} websites. 41 |
42 | {{nb_visited_with_cookie}}% of them stored at least one cookie. 43 |
44 |
45 | These cookies potentially exchange 46 | information with {{nb_third}} third parties. 47 |
48 |
49 | 50 |
51 | 52 |
53 |
54 |
55 |
56 | 58 |
59 |
60 |
61 |
62 | 64 |
65 |
66 | 67 |
68 |
69 |
70 | Presence of third parties on websites 71 | During your navigation, third parties may deposit cookies to, among other things, track navigation. 72 | 73 | Between {{most_third[0]}} and {{most_third[1]}} third parties store cookies on 20% of visited websites. 74 | Between {{rest_third[0]}} and {{rest_third[1]}} third parties store cookies on 80% of visited websites. 75 |
76 |
77 |
78 |
79 | 80 | 83 |
84 |
85 |
86 | 103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /visualization/export.js: -------------------------------------------------------------------------------- 1 | const getFavicons = require('get-website-favicon'); 2 | const JSZip = require("jszip"); 3 | const privacy_list = require('./data/adv.json'); 4 | 5 | async function export_data_analysis() { 6 | 7 | function zipGraphs(zip, nodes, links) { 8 | const graphs = {}; 9 | for (node of nodes) { 10 | const subgraph_links = links.filter(x => x.source == node.id || x.target == node.id); 11 | const subgraph_nodeset = new Set(); 12 | subgraph_nodeset.add(node.id); 13 | subgraph_links.forEach(x => subgraph_nodeset.add(x.source)); 14 | subgraph_links.forEach(x => subgraph_nodeset.add(x.target)); 15 | 16 | const subgraph_nodes = Array.from(subgraph_nodeset) 17 | .map(x => nodes.find(elt => elt.id == x)); 18 | 19 | const blob = new Blob([JSON.stringify({ nodes: subgraph_nodes, links: subgraph_links })], { type: 'application/json' }) 20 | zip.file("data/" + node.id + ".json", blob); 21 | } 22 | return graphs; 23 | } 24 | 25 | function getMostPresent(nodes, links) { 26 | const reverse = {}; 27 | const trigger = 1; 28 | const global_count = nodes.filter(x => x.visited == 1).length; 29 | const cookies = {}; 30 | 31 | // Reverse source and target 32 | for (const source in links) { 33 | for (const target in links[source]) { 34 | if (!(target in reverse)) reverse[target] = []; 35 | if (!reverse[target].includes(source)) reverse[target].push(source); 36 | 37 | for (const cookie in links[source][target]) { 38 | if (!(target in cookies)) cookies[target] = []; 39 | if (!cookies[target].includes(cookie)) cookies[target].push(cookie); 40 | } 41 | } 42 | } 43 | 44 | // Categorize results 45 | const to_study = Object.keys(reverse) 46 | .filter(x => privacy_list[x]) 47 | .filter(x => 100 * reverse[x].length / global_count >= trigger); 48 | 49 | const missing = Object.keys(reverse) 50 | .filter(x => !privacy_list[x]) 51 | .filter(x => 100 * reverse[x].length / global_count >= trigger); 52 | 53 | const no_to_study_size = Object.keys(reverse) 54 | .filter(x => !privacy_list[x]) 55 | .filter(x => 100 * reverse[x].length / global_count < trigger) 56 | .reduce((a, x) => a + reverse[x].length, 0); 57 | 58 | 59 | 60 | if (missing.length > 0) { 61 | console.log("warning this list has not been study : "); 62 | missing.forEach(x => { 63 | console.log(x + "with the following cookies : " + cookies[x]); 64 | }) 65 | } 66 | 67 | // Computing region 68 | const ads_list = to_study 69 | .filter(x => privacy_list[x].purpose.includes("ads")) 70 | .map(x => ({ 71 | name: x, 72 | code: x.substring(0, 2), 73 | cookie: cookies[x], 74 | ads: 1, 75 | privacy: privacy_list[x].privacy ? privacy_list[x].privacy : null, 76 | weight: (100 * reverse[x].length / global_count).toFixed(2) 77 | })) // percent of third domain presence 78 | .sort((first, second) => second.weight - first.weight); 79 | 80 | const nonads_list = to_study 81 | .filter(x => !privacy_list[x].purpose.includes("ads")) 82 | .map(x => ({ 83 | name: x, 84 | code: x.substring(0, 2), 85 | cookie: cookies[x], 86 | ads: 0, 87 | privacy: privacy_list[x].privacy ? privacy_list[x].privacy : null, 88 | weight: (100 * reverse[x].length / global_count).toFixed(2) 89 | })) // percent of third domain presence 90 | .sort((first, second) => second.weight - first.weight); 91 | 92 | const nonstudy_list = [{ 93 | name: "not_study", 94 | code: "not_study".substring(0, 2), 95 | cookie: null, 96 | ads: -1, 97 | privacy: null, 98 | weight: 100 * no_to_study_size / global_count 99 | }]; 100 | 101 | const voronoir = { children: [] }; 102 | 103 | voronoir.children.push({ name: "La politique de confidentialité indique une finalité publicitaire", color: "#fb8761", children: ads_list }); 104 | voronoir.children.push({ name: "La politique de confidentialité n'indique pas de finalité publicitaire", color: "#b5367a", children: nonads_list }); 105 | return voronoir; 106 | } 107 | 108 | async function add_favicons(zip, favicons, nodes) { 109 | let cpt_ico = 0; 110 | for (const site in favicons) { 111 | if (!favicons[site] || nodes.filter(node => node.id == site).length == 0) continue; 112 | let faviconBlob = null; 113 | try { 114 | const response = await fetch(favicons[site]); 115 | if (!response) continue; 116 | faviconBlob = await response.blob(); 117 | } catch (error) { 118 | continue; 119 | } 120 | 121 | if (faviconBlob && faviconBlob.size > 0) { 122 | const mime_ext = { 123 | "image/png": "png", 124 | "image/tiff": "tif", 125 | "image/vnd.wap.wbmp": "wbmp", 126 | "image/x-icon": "ico", 127 | "image/x-jng": "jng", 128 | "image/x-ms-bmp": "bmp", 129 | "image/svg+xml": "svg", 130 | "image/webp": "webp", 131 | "image/gif": "gif", 132 | "image/jpeg": "jpeg" 133 | } 134 | const fileext = mime_ext[faviconBlob.type] ? mime_ext[faviconBlob.type] : "ico" 135 | const icons_path = "icons/" + cpt_ico++ + "." + fileext; 136 | zip.file(icons_path, faviconBlob, { base64: true }); 137 | nodes.filter(node => node.id == site).forEach(x=> x.icon = icons_path); 138 | } 139 | }; 140 | }; 141 | 142 | var zip = new JSZip(); 143 | 144 | // Generate global graph 145 | let all_nodes = await loaded_plugins.requests.nodes_requests(); 146 | const link_requests = await loaded_plugins.requests.link_requests(); 147 | const links_with_cookies = await loaded_plugins.requests.link_requests_with_cookies(); 148 | const favicons = await loaded_plugins.favicons.get_all_favicons(); 149 | 150 | const links = []; 151 | const nodes_with_cookies = new Set(); 152 | for (const source in links_with_cookies) { 153 | if (source.endsWith(".safeframe.googlesyndication.com")) continue; // This url is polluting initatiator 154 | for (const target in links_with_cookies[source]) { 155 | const cookie_list = Object.keys(links_with_cookies[source][target]); 156 | links.push({ source: source, target: target, cookie: cookie_list.length }); 157 | nodes_with_cookies.add(all_nodes.find(x => x.id == source)); 158 | nodes_with_cookies.add(all_nodes.find(x => x.id == target)); 159 | } 160 | } 161 | 162 | all_nodes 163 | .filter(x => x.visited == 1) 164 | .filter(x => !x.id.endsWith(".safeframe.googlesyndication.com")) // This url is polluting initatiator 165 | .forEach(x => nodes_with_cookies.add(x)); 166 | const nodes = Array.from(nodes_with_cookies); 167 | 168 | await add_favicons(zip, favicons, nodes); 169 | 170 | const blob_global = new Blob([JSON.stringify({ nodes: nodes, links: links })], { type: 'application/json' }); 171 | zip.file("data/global.json", blob_global); 172 | zipGraphs(zip, nodes, links); 173 | 174 | // Reintegrate all nodes for the analysis 175 | const distrib = Object.keys(link_requests) 176 | .map((x) => ({ site: x, value: x in links_with_cookies ? Object.keys(links_with_cookies[x]).length : 0})) 177 | .sort((a, b) => b.value - a.value); 178 | const blob_distribution = new Blob([JSON.stringify(distrib)], { type: 'application/json' }); 179 | zip.file("data/distribution.json", blob_distribution); 180 | 181 | 182 | const mostPresent = getMostPresent(all_nodes, links_with_cookies); 183 | const blob_mostpresent = new Blob([JSON.stringify(mostPresent)], { type: 'application/json' }); 184 | zip.file("data/mostpresent.json", blob_mostpresent); 185 | 186 | zip.generateAsync({ type: "blob" }) 187 | .then(function (blob) { 188 | var a = document.createElement("a"); 189 | document.body.appendChild(a); 190 | a.style = "display: none"; 191 | const url = window.URL.createObjectURL(blob); 192 | a.href = url; 193 | a.download = "data.zip"; 194 | a.click(); 195 | window.URL.revokeObjectURL(url); 196 | }); 197 | } -------------------------------------------------------------------------------- /visualization/graph.js: -------------------------------------------------------------------------------- 1 | const loaded_plugins = {}; 2 | 3 | var g; 4 | var tooltip; 5 | var node; 6 | var link; 7 | var simulation; 8 | const scale = d3.scaleOrdinal(d3.schemeCategory10); 9 | var linkedByIndex = {}; 10 | var zoom_handler; 11 | var svg; 12 | var svg_width; 13 | var svg_height; 14 | 15 | function load_graph(nodes, links, size, force, zoom) { 16 | function zoom_actions() { 17 | g.attr("transform", d3.event.transform) 18 | } 19 | 20 | svg_width = size.width; 21 | svg_height= size.height; 22 | var transform = d3.zoomIdentity.scale(zoom); 23 | 24 | //add zoom capabilities 25 | zoom_handler = d3.zoom() 26 | .on("zoom", zoom_actions); 27 | 28 | d3.select("#cookieviz").selectAll("*").remove(); 29 | 30 | svg = d3.select("#cookieviz") 31 | .append("svg") 32 | .attr("width", "90%") 33 | .attr("height", "500px") 34 | .attr("viewBox", [-size.width / 2, -size.height, size.width, size.height]); // Calls/inits handleZoom; 35 | 36 | zoom_handler(svg); 37 | 38 | tooltip = d3.select("#cookieviz") 39 | .append("div") 40 | .attr("class", "tooltip") 41 | .style("opacity", 0); 42 | 43 | 44 | g = svg.append("g") 45 | .attr("class", "everything") 46 | .attr("transform", transform); 47 | 48 | svg.call(zoom_handler) // Adds zoom functionality 49 | .call(zoom_handler.transform, transform); 50 | 51 | simulation = d3.forceSimulation(nodes) 52 | .force("link", d3.forceLink(links).id(d => d.id).distance(100)) 53 | .force("charge", d3.forceManyBody().strength(force)) 54 | .force("x", d3.forceX()) 55 | .force("y", d3.forceY()) 56 | .alphaTarget(1) 57 | .on("tick", () => { 58 | link 59 | .attr("x1", d => d.source.x) 60 | .attr("y1", d => d.source.y) 61 | .attr("x2", d => d.target.x) 62 | .attr("y2", d => d.target.y); 63 | 64 | node 65 | .attr("transform", function (d) { 66 | return "translate(" + d.x + "," + d.y + ")"; 67 | }).attr("cx", function(d) { return d.x; }) 68 | .attr("cy", function(d) { return d.y; }) 69 | 70 | }); 71 | 72 | link = g.append("g") 73 | .attr("stroke", "#999") 74 | .attr("stroke-opacity", 0.6) 75 | .selectAll("line"); 76 | 77 | node = g.append("g") 78 | .attr("stroke", "#fff") 79 | .attr("stroke-width", 1.5) 80 | .selectAll("g"); 81 | 82 | update_graph(nodes, links); 83 | } 84 | 85 | 86 | function update_graph(nodes, links) { 87 | 88 | 89 | function releasenode(d) { 90 | d.fx = null; 91 | d.fy = null; 92 | } 93 | 94 | function releasenode(d) { 95 | d.fx = null; 96 | d.fy = null; 97 | } 98 | 99 | function fade(opacity) { 100 | return d => { 101 | node.style('stroke-opacity', function (o) { 102 | const thisOpacity = isConnected(d, o) ? 1 : opacity; 103 | this.setAttribute('opacity', thisOpacity); 104 | return thisOpacity; 105 | }); 106 | 107 | link.style('stroke-opacity', o => (o.source === d || o.target === d ? 1 : opacity)); 108 | 109 | }; 110 | } 111 | 112 | function dragstarted(d) { 113 | if (!d3.event.active) simulation.alphaTarget(0.3).restart(); 114 | d.fx = d.x; 115 | d.fy = d.y; 116 | } 117 | 118 | function dragged(d) { 119 | d.fx = d3.event.x; 120 | d.fy = d3.event.y; 121 | } 122 | 123 | function dragended(d) { 124 | if (!d3.event.active) simulation.alphaTarget(0); 125 | d.fx = null; 126 | d.fy = null; 127 | } 128 | 129 | function isConnected(a, b) { 130 | return linkedByIndex[`${a.index},${b.index}`] || linkedByIndex[`${b.index},${a.index}`] || a.index === b.index; 131 | } 132 | 133 | 134 | // Apply the general update pattern to the nodes. 135 | node = node.data(nodes, function (d) { return d.id; }); 136 | node.exit().remove(); 137 | node = node.enter().append("g").call(d3.drag() 138 | .on("start", dragstarted) 139 | .on("drag", dragged) 140 | .on("end", dragended)) 141 | .on('mouseover.tooltip', function (d) { 142 | tooltip.transition() 143 | .duration(300) 144 | .style("opacity", .8); 145 | tooltip.html(d.id) 146 | .style("left", (d3.event.pageX) + "px") 147 | .style("top", (d3.event.pageY + 10) + "px"); 148 | }) 149 | .on('mouseover.fade', fade(0.1)) 150 | .on("mouseout.tooltip", function () { 151 | tooltip.transition() 152 | .duration(100) 153 | .style("opacity", 0); 154 | }) 155 | .on('mouseout.fade', fade(1)) 156 | .on("mousemove", function () { 157 | tooltip.style("left", (d3.event.pageX) + "px") 158 | .style("top", (d3.event.pageY + 10) + "px"); 159 | }) 160 | .on('dblclick', releasenode) 161 | .merge(node); 162 | 163 | node.append("circle") 164 | .attr("r", 12) 165 | .attr("fill", (d) => { return scale(d.visited); }); 166 | 167 | node.append("image") 168 | .attr("xlink:href", (d) => { return d.icon ? d.icon : "../icons/empty_favicon.png"; }) 169 | .attr("x", "-8") 170 | .attr("y", "-8") 171 | .attr("width", "16") 172 | .attr("height", "16"); 173 | 174 | // Apply the general update pattern to the links. 175 | link = link.data(links, function (d) { return d.source.id + "-" + d.target.id; }); 176 | link.exit().remove(); 177 | link = link.enter().append("line") 178 | .attr("stroke-width", d => d.cookie ? 2 : 1) 179 | .attr("stroke", d => d.cookie ? 'red' : '#999') 180 | .attr('class', 'link') 181 | .on('mouseover.tooltip', function (d) { 182 | tooltip.transition() 183 | .duration(300) 184 | .style("opacity", .8); 185 | }) 186 | .on("mouseout.tooltip", function () { 187 | tooltip.transition() 188 | .duration(100) 189 | .style("opacity", 0); 190 | }) 191 | .on('mouseout.fade', fade(1)) 192 | .on("mousemove", function () { 193 | tooltip.style("left", (d3.event.pageX) + "px") 194 | .style("top", (d3.event.pageY + 10) + "px"); 195 | }).merge(link); 196 | 197 | // Update and restart the simulation. 198 | simulation.nodes(nodes); 199 | simulation.force("link").links(links); 200 | simulation.alpha(1).restart(); 201 | linkedByIndex ={}; 202 | links.forEach(d => { 203 | linkedByIndex[`${d.source.index},${d.target.index}`] = 1; 204 | }); 205 | } 206 | 207 | -------------------------------------------------------------------------------- /visualization/hist.js: -------------------------------------------------------------------------------- 1 | var ordinals = []; 2 | var x, y, bars, xAxis, yAxis, width_hist, heigth_hist, svg_hist, div_hist, xScale, yScale; 3 | var duration_hist = 1000; 4 | 5 | function update_hist(data) { 6 | ordinals = data.map(x => x.site); 7 | 8 | xScale = x.domain([-1, ordinals.length]) 9 | yScale = y.domain([0, d3.max(data, function (d) { return d.value })]) 10 | 11 | let xBand = d3.scaleBand().domain(d3.range(-1, ordinals.length)).range([0, width_hist]) 12 | 13 | const threshold_max = Math.round(data.length * 0.8); 14 | const threshold_min = Math.round(data.length* 0.2); 15 | 16 | var range_min = 0; 17 | var range_max = 0; 18 | 19 | if (data[threshold_min]) 20 | range_min = data[threshold_min].value; 21 | 22 | if (data[threshold_max]) 23 | range_max = data[threshold_max].value; 24 | 25 | let bars_tmp = bars.selectAll('.bar') 26 | .data(data); 27 | 28 | if (data.length == 0) bars.selectAll('.bar').remove(); 29 | 30 | bars_tmp.enter() 31 | .append('rect') 32 | .attr('class', 'bar') 33 | .attr('height', 0) 34 | .attr('y', heigth_hist) 35 | .style("fill", d => d.value >= range_min ? "#fb8761" : d.value < range_max ? "#4f127b" : "#b5367a") 36 | .on("click", function (d) { 37 | filter_node = d.site; 38 | }) 39 | .merge(bars_tmp) 40 | .transition() 41 | .duration(duration_hist) 42 | .attr('x', function (d, i) { 43 | return xScale(i) - xBand.bandwidth() * 0.9 / 2 44 | }) 45 | .attr('y', function (d, i) { 46 | return yScale(d.value) 47 | }) 48 | .attr('width', xBand.bandwidth() * 0.9) 49 | .attr('height', function (d) { 50 | return heigth_hist - yScale(d.value) 51 | }) 52 | .style("fill", d => d.value >= range_min ? "#fb8761" : d.value < range_max ? "#4f127b" : "#b5367a") 53 | 54 | bars 55 | .exit() 56 | .transition() 57 | .duration(duration_hist) 58 | .attr('height', 0) 59 | .attr('y', heigth_hist) 60 | .remove(); 61 | 62 | yAxis 63 | .transition() 64 | .duration(duration_hist) 65 | .call(d3.axisLeft(yScale)); 66 | 67 | } 68 | 69 | function load_hist(data, size) { 70 | 71 | let margin = { 72 | top: 10, right: 10, bottom: 20, left: 30 73 | }; 74 | 75 | width_hist = size.width - margin.left - margin.right; 76 | heigth_hist = size.height - margin.top - margin.bottom; 77 | var radius = (Math.min(size.width, size.height) / 2) - 10; 78 | var node; 79 | 80 | 81 | svg_hist = d3.select("#hist").append("svg") 82 | .attr('width', "100%") 83 | .attr('height', "100%") 84 | .append('g') 85 | .attr('transform', `translate(${margin.left}, ${margin.top})`) 86 | .call( 87 | d3.zoom() 88 | .translateExtent([[0, 0], [width_hist, heigth_hist]]) 89 | .extent([[0, 0], [width_hist, heigth_hist]]) 90 | .on('zoom', zoom) 91 | ) 92 | 93 | div_hist = d3.select("#hist").append("div") 94 | .attr("class", "tooltip") 95 | .style("opacity", 0); 96 | 97 | 98 | // the scale 99 | x = d3.scaleLinear().range([0, width_hist]) 100 | y = d3.scaleLinear().range([heigth_hist, 0]) 101 | let color = d3.scaleOrdinal(d3.schemeCategory10) 102 | // for the width of rect 103 | 104 | // zoomable rect 105 | svg_hist.append('rect') 106 | .attr('class', 'zoom-panel') 107 | .attr('width', width_hist) 108 | .attr('height', heigth_hist) 109 | 110 | // x axis 111 | xAxis = svg_hist.append('g') 112 | .attr('class', 'xAxis') 113 | .attr('transform', `translate(0, ${heigth_hist})`); 114 | 115 | 116 | // y axis 117 | yAxis = svg_hist.append('g') 118 | .attr('class', 'y axis'); 119 | 120 | let defs = svg_hist.append('defs') 121 | 122 | bars = svg_hist.append('g') 123 | .attr('clip-path', 'url(#my-clip-path)') 124 | 125 | // use clipPath 126 | defs.append('clipPath') 127 | .attr('id', 'my-clip-path') 128 | .append('rect') 129 | .attr('width', width_hist) 130 | .attr('height', heigth_hist); 131 | 132 | function zoom() { 133 | if (d3.event.transform.k < 1) { 134 | d3.event.transform.k = 1 135 | return 136 | } 137 | 138 | // the bars transform 139 | bars.attr("transform", "translate(" + d3.event.transform.x + ",0)scale(" + d3.event.transform.k + ",1)") 140 | 141 | } 142 | update_hist(data); 143 | }; -------------------------------------------------------------------------------- /visualization/search.js: -------------------------------------------------------------------------------- 1 | const max_proposition = 3; 2 | 3 | function autocomplete(inp, arr) { 4 | var currentFocus; 5 | 6 | inp.addEventListener("input", function(e) { 7 | var a, b, i, val = this.value; 8 | var nb_prop = 0; 9 | closeAllLists(); 10 | if (!val) { return false;} 11 | currentFocus = -1; 12 | 13 | a = document.createElement("DIV"); 14 | a.setAttribute("id", this.id + "autocomplete-list"); 15 | a.setAttribute("class", "autocomplete-items"); 16 | this.parentNode.appendChild(a); 17 | for (i = 0; i < arr.length; i++) { 18 | if (arr[i] && arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase()) { 19 | nb_prop++; 20 | b = document.createElement("DIV"); 21 | b.innerHTML = "" + arr[i].substr(0, val.length) + ""; 22 | b.innerHTML += arr[i].substr(val.length); 23 | b.innerHTML += ""; 24 | b.addEventListener("click", function(e) { 25 | inp.value = this.getElementsByTagName("input")[0].value; 26 | filter_node = inp.value; 27 | closeAllLists(); 28 | }); 29 | a.appendChild(b); 30 | if (nb_prop>= max_proposition)return; 31 | } 32 | } 33 | }); 34 | inp.addEventListener("keydown", function(e) { 35 | var x = document.getElementById(this.id + "autocomplete-list"); 36 | if (x) x = x.getElementsByTagName("div"); 37 | if (e.keyCode == 40) { 38 | currentFocus++; 39 | addActive(x); 40 | } else if (e.keyCode == 38) { 41 | currentFocus--; 42 | addActive(x); 43 | } else if (e.keyCode == 13) { 44 | e.preventDefault(); 45 | if (currentFocus > -1) { 46 | if (x) x[currentFocus].click(); 47 | } 48 | filter_node = inp.value; 49 | } 50 | }); 51 | function addActive(x) { 52 | if (!x) return false; 53 | removeActive(x); 54 | if (currentFocus >= x.length) currentFocus = 0; 55 | if (currentFocus < 0) currentFocus = (x.length - 1); 56 | x[currentFocus].classList.add("autocomplete-active"); 57 | } 58 | function removeActive(x) { 59 | for (var i = 0; i < x.length; i++) { 60 | x[i].classList.remove("autocomplete-active"); 61 | } 62 | } 63 | function closeAllLists(elmnt) { 64 | var x = document.getElementsByClassName("autocomplete-items"); 65 | for (var i = 0; i < x.length; i++) { 66 | if (elmnt != x[i] && elmnt != inp) { 67 | x[i].parentNode.removeChild(x[i]); 68 | } 69 | } 70 | } 71 | document.addEventListener("click", function (e) { 72 | closeAllLists(e.target); 73 | }); 74 | } 75 | 76 | -------------------------------------------------------------------------------- /visualization/voronoi.js: -------------------------------------------------------------------------------- 1 | //begin: constants 2 | var _2PI = 2 * Math.PI; 3 | //end: constants 4 | 5 | //begin: layout conf. 6 | 7 | //end: layout conf. 8 | const treemapRadius = 150; 9 | 10 | //begin: treemap conf. 11 | var _voronoiTreemap = d3.voronoiTreemap(); 12 | var hierarchy, circlingPolygon; 13 | //end: treemap conf. 14 | 15 | //begin: drawing conf. 16 | var fontScale = d3.scaleLinear(); 17 | //end: drawing conf. 18 | 19 | //begin: reusable d3Selection 20 | var svg_tree, drawingArea, treemapContainer; 21 | //end: reusable d3Selection 22 | 23 | function update_voronoi(rootData) { 24 | 25 | if (!rootData.children) return; 26 | 27 | const num_childen = rootData.children.reduce((sum, child)=> sum + child.children.length,0); 28 | 29 | if(num_childen == 0){ 30 | svg_tree.style("visibility", "hidden"); 31 | return; 32 | }else{ 33 | svg_tree.style("visibility", "visible"); 34 | } 35 | 36 | hierarchy = d3.hierarchy(rootData).sum(function (d) { return d.weight; }); 37 | _voronoiTreemap 38 | .clip(circlingPolygon) 39 | (hierarchy); 40 | 41 | drawTreemap(hierarchy); 42 | }; 43 | 44 | 45 | function computeCirclingPolygon(radius) { 46 | var points = 60, 47 | increment = _2PI / points, 48 | circlingPolygon = []; 49 | 50 | for (var a = 0, i = 0; i < points; i++, a += increment) { 51 | circlingPolygon.push( 52 | [radius + radius * Math.cos(a), radius + radius * Math.sin(a)] 53 | ) 54 | } 55 | 56 | return circlingPolygon; 57 | }; 58 | 59 | function load_voronoi(data, size) { 60 | svgWidth = size.width; 61 | svgHeight = size.height; 62 | var margin = { top: 20, right: 20, bottom: 20, left: 20 }, 63 | height = svgHeight - margin.top - margin.bottom, 64 | width = svgWidth - margin.left - margin.right, 65 | halfWidth = width / 2, 66 | halfHeight = height / 2, 67 | quarterWidth = width / 4, 68 | quarterHeight = height / 4, 69 | titleY = 20, 70 | legendsMinY = height + 50, 71 | treemapCenter = [halfWidth, halfHeight + 5]; 72 | 73 | circlingPolygon = computeCirclingPolygon(treemapRadius); 74 | fontScale.domain([4, 60]).range([8, 15]).clamp(true); 75 | 76 | svg_tree = d3.select("#treemap") 77 | .append("svg") 78 | .attr("width", "100%") 79 | .attr("height", "100%"); 80 | 81 | drawingArea = svg_tree.append("g") 82 | .classed("drawingArea", true) 83 | .attr("transform", "translate(" + [margin.left +10 , margin.top+10] + ")"); 84 | 85 | treemapContainer = drawingArea.append("g") 86 | .classed("treemap-container", true) 87 | .attr("transform", "translate(" + treemapCenter + ")"); 88 | 89 | treemapContainer.append("path") 90 | .classed("world", true) 91 | .attr("transform", "translate(" + [-treemapRadius, -treemapRadius] + ")") 92 | .attr("d", "M" + circlingPolygon.join(",") + "Z"); 93 | 94 | update_voronoi(data); 95 | } 96 | 97 | function drawLegends(rootData) { 98 | var legendHeight = 13, 99 | interLegend = 4, 100 | colorWidth = legendHeight * 6, 101 | continents = rootData.children.reverse(); 102 | 103 | var legendContainer = drawingArea.append("g") 104 | .classed("legend", true) 105 | .attr("transform", "translate(" + [0, legendsMinY] + ")"); 106 | 107 | var legends = legendContainer.selectAll(".legend") 108 | .data(continents) 109 | .enter(); 110 | 111 | var legend = legends.append("g") 112 | .classed("legend", true) 113 | .attr("transform", function (d, i) { 114 | return "translate(" + [0, -i * (legendHeight + interLegend)] + ")"; 115 | }) 116 | 117 | legend.append("rect") 118 | .classed("legend-color", true) 119 | .attr("y", -legendHeight) 120 | .attr("width", colorWidth) 121 | .attr("height", legendHeight) 122 | .style("fill", function (d) { return d.color; }); 123 | legend.append("text") 124 | .classed("tiny", true) 125 | .attr("transform", "translate(" + [colorWidth + 5, -2] + ")") 126 | .text(function (d) { return d.name; }); 127 | } 128 | 129 | 130 | function drawTreemap(hierarchy) { 131 | 132 | 133 | const div_tooltip = d3.select("#treemap").append("div") 134 | .attr("class", "tooltip") 135 | .style("opacity", 0); 136 | 137 | 138 | var leaves = hierarchy.leaves(); 139 | 140 | var cells = treemapContainer.append("g") 141 | .classed('cells', true) 142 | .attr("transform", "translate(" + [-treemapRadius, -treemapRadius] + ")") 143 | .selectAll(".cell") 144 | .data(leaves) 145 | .enter() 146 | .append("path") 147 | .classed("cell", true) 148 | .attr("d", function (d) { return "M" + d.polygon.join(",") + "z"; }) 149 | .style("fill", function (d) { 150 | return d.parent.data.color; 151 | }); 152 | 153 | var labels = treemapContainer.append("g") 154 | .classed('labels', true) 155 | .attr("transform", "translate(" + [-treemapRadius, -treemapRadius] + ")") 156 | .selectAll(".label") 157 | .data(leaves) 158 | .enter() 159 | .append("g") 160 | .classed("label", true) 161 | .attr("transform", function (d) { 162 | return "translate(" + [d.polygon.site.x, d.polygon.site.y] + ")"; 163 | }) 164 | .style("font-size", function (d) { 165 | return fontScale(d.data.weight)+"px"; 166 | }) 167 | .style("opacity", d => d.data.weight > 2 ? 1 : 0); 168 | 169 | labels.append("text") 170 | .classed("name", true) 171 | .html(function (d) { 172 | return (d.data.weight < 13) ? d.data.code : d.data.name; 173 | }); 174 | labels.append("text") 175 | .classed("value", true) 176 | .text(d => d.data.weight > 4 ? d.data.weight + "%" : null); 177 | 178 | var hoverers = treemapContainer.append("g") 179 | .classed('hoverers', true) 180 | .attr("transform", "translate(" + [-treemapRadius, -treemapRadius] + ")") 181 | .selectAll(".hoverer") 182 | .data(leaves) 183 | .enter() 184 | .append("path") 185 | .classed("hoverer", true) 186 | .attr("d", function (d) { return "M" + d.polygon.join(",") + "z"; }); 187 | 188 | hoverers.append("title") 189 | .text(function (d) { return d.data.name + "\n" + d.value + "%"; }); 190 | 191 | 192 | hoverers 193 | .on("click", function (d) { 194 | filter_node = d.data.name; 195 | }); 196 | 197 | } --------------------------------------------------------------------------------