27 |
28 |
29 |
31 |
33 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
62 |
63 |
64 |
65 |
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 | }
--------------------------------------------------------------------------------