Skip to content

Commit 005f7b7

Browse files
committed
add asn-map visualization
1 parent 458b1b7 commit 005f7b7

File tree

4 files changed

+174
-2
lines changed

4 files changed

+174
-2
lines changed

visualization/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Use:
1717
3. For testing - run the minimal python3 webserver to enable JS to access the JSON file:
1818

1919
```bash
20-
python3 world_map_test.py
20+
python3 test_server.py
2121
```
2222

2323
4. Open the file in your browser: [http://localhost:8000/world_map.html](http://localhost:8000/world_map.html)

visualization/asn_map.html

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<link rel="icon" type="image/svg" href="https://files.oxl.at/img/oxl3_sm.png">
7+
8+
<script src="https://d3js.org/d3.v4.min.js"></script>
9+
<style>
10+
.bubble {
11+
overflow: hidden;
12+
}
13+
14+
.bubble-asn {
15+
font-weight: bold;
16+
}
17+
18+
.bubble-info {
19+
visibility: hidden;
20+
display: none;
21+
}
22+
23+
#infos {
24+
top: 10px;
25+
left: 20px;
26+
width: 40vw;
27+
height: 30vh;
28+
background-color: whitesmoke;
29+
opacity: 0.7;
30+
z-index: -1;
31+
border-radius: 10px;
32+
border-width: 2px;
33+
border-color: black;
34+
border-style: solid;
35+
position: absolute;
36+
padding: 20px;
37+
overflow: hidden;
38+
}
39+
40+
#chart {
41+
display: inline-block;
42+
}
43+
44+
</style>
45+
</head>
46+
<body>
47+
<!-- see: https://observablehq.com/@d3/bubble-chart/2 -->
48+
<div id="infos"></div>
49+
<svg id="chart"></svg>
50+
<script>
51+
// start test webserver: 'python3 test_server.py'
52+
53+
const TOP_N = 30;
54+
const WIDTH = window.innerWidth;
55+
const HEIGHT = window.innerHeight - 50;
56+
const BUBBLE_PAD = 50;
57+
const COLORSCHEME = [
58+
// red-ish
59+
"#B93022", "#FF383D", "#D32669", "#C43F03", "#E70291", "#E211C0", "#6A3D52", "#C75839", "#E7A8B0",
60+
"#DB7277", "#A23E48",
61+
// purple-ish
62+
"#9F8AD2", "#4B2ED1", "#9C1091", "#EA77DF", "#8D0BE8", "#BB65BC", "#993CF3", "#9F09EF", "#CD0099",
63+
"#EA2794", "#DC8581", "#9C1686",
64+
// orange-ish
65+
"#E37F6F", "#C5824E", "#FDCA29", "#D99620", "#E7C014", "#F9C630", "#F6BC55", "#B9AB58", "#DB8E94",
66+
"#D27E7E",
67+
];
68+
69+
function numberWithDot(x) {
70+
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
71+
}
72+
73+
function showASNInfos(e) {
74+
let infos = JSON.parse(e.currentTarget.querySelectorAll(".bubble-info")[0].textContent);
75+
let infoContainer = document.getElementById('infos');
76+
infoContainer.innerHTML = '<b>ASN</b>: ' + infos.asn + '<br>';
77+
infoContainer.innerHTML += '<b>Org</b>: ' + infos.info.org.name + '<br>';
78+
if (infos.info.org.country != '' && infos.info.org.country !== null) {
79+
infoContainer.innerHTML += '<b>Country</b>: ' + infos.info.org.country + '<br>';
80+
}
81+
infoContainer.innerHTML += '<b>IPv4 Addresses</b>: ' + numberWithDot(infos.info.ipv4) + '<br>';
82+
infoContainer.innerHTML += '<b>IPv6 Addresses</b>: ' + numberWithDot(infos.info.ipv6) + '<br>';
83+
let kinds = [];
84+
for (let [k, v] of Object.entries(infos.kind)) {
85+
if (v === true) {
86+
kinds.push(k);
87+
}
88+
}
89+
if (kinds.length > 0) {
90+
infoContainer.innerHTML += '<b>Kind</b>: ' + kinds.join(', ') + '<br>';
91+
92+
}
93+
for (let [k, v] of Object.entries(infos.reports)) {
94+
let r = '<small><b>Reports ' + k + '</b>: ';
95+
if (k == 'rel_by_ip4') {
96+
r += v;
97+
} else {
98+
r += numberWithDot(v);
99+
}
100+
infoContainer.innerHTML += r + '</small><br>';
101+
}
102+
}
103+
104+
function createChart() {
105+
d3.json('risk_asn_med.json', function(resp) {
106+
let data = [];
107+
108+
for (let [asn, values] of Object.entries(resp)) {
109+
data.push({
110+
asn: asn, info: values.info, kind: values.kind, reports: values.reports,
111+
sortBy: values.reports.all,
112+
})
113+
}
114+
115+
const sortedData = data.sort((a, b) => b.reports.all - a.reports.all).slice(0, TOP_N);
116+
console.log(sortedData);
117+
118+
const colorScale = d3.scaleOrdinal()
119+
.domain(sortedData.map(d => d.asn))
120+
.range(COLORSCHEME);
121+
122+
const pack = d3.pack()
123+
.size([WIDTH - BUBBLE_PAD, HEIGHT - BUBBLE_PAD])
124+
.padding(5);
125+
126+
const hierarchy = d3.hierarchy({children: sortedData})
127+
.sum(d => d.sortBy);
128+
129+
const root = pack(hierarchy);
130+
131+
const svg = d3.select("#chart")
132+
.attr("width", WIDTH)
133+
.attr("height", HEIGHT);
134+
135+
const bubbles = svg.selectAll(".bubble")
136+
.data(root.descendants().slice(1))
137+
.enter()
138+
.append("g")
139+
.attr("class", "bubble")
140+
.attr("transform", d => `translate(${d.x + BUBBLE_PAD}, ${d.y + BUBBLE_PAD})`);
141+
142+
bubbles.append("circle")
143+
.attr("r", d => d.r)
144+
.attr("fill", d => colorScale(d.data.asn));
145+
146+
bubbles.append("text")
147+
.style("text-anchor", "middle")
148+
.append("tspan")
149+
.attr("class", "bubble-asn")
150+
.text(d => d.data.asn);
151+
152+
// hover infos
153+
bubbles.append("text")
154+
.style("text-anchor", "bottom")
155+
.append("tspan")
156+
.attr("class", "bubble-info")
157+
.text(d => JSON.stringify(d.data));
158+
159+
d3.selectAll("text").style("fill", "black");
160+
161+
for (let b of document.querySelectorAll(".bubble")) {
162+
b.addEventListener("mouseover", showASNInfos);
163+
}
164+
165+
});
166+
167+
}
168+
169+
createChart();
170+
</script>
171+
</body>
172+
</html>
File renamed without changes.

visualization/world_map.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<!-- see: https://stephanwagner.me/create-world-map-charts-with-svgmap#svgMapDemoGDP -->
1414
<div id="svgMap"></div>
1515
<script>
16-
// start test webserver: 'python3 world_map_test.py'
16+
// start test webserver: 'python3 test_server.py'
1717
const RISK_DB_MAP_DATA = 'http://localhost:8000/world_map.json';
1818

1919
function createMap(resp) {

0 commit comments

Comments
 (0)