First try at a bubble map.

This commit is contained in:
Herbert Wolverson 2024-03-20 08:38:25 -05:00
parent 280447f79d
commit c6830abae6
4 changed files with 137 additions and 93 deletions

View File

@ -135,7 +135,7 @@ pub enum BusResponse {
CurrentEndpointsByCountry(Vec<(String, [u64; 2], [f32; 2])>),
/// Current Lat/Lon of endpoints
CurrentLatLon(Vec<(f64, f64)>),
CurrentLatLon(Vec<(f64, f64, String, u64, f32)>),
/// Summary of Ether Protocol
EtherProtocols{

View File

@ -60,7 +60,7 @@ pub async fn flows_by_country() -> NoCache<Json<Vec<(String, [u64; 2], [f32; 2])
}
#[get("/api/flows/lat_lon")]
pub async fn flows_lat_lon() -> NoCache<Json<Vec<(f64, f64)>>> {
pub async fn flows_lat_lon() -> NoCache<Json<Vec<(f64, f64, String, u64, f32)>>> {
let responses =
bus_request(vec![BusRequest::CurrentEndpointLatLon]).await.unwrap();
let result = match &responses[0] {

View File

@ -6,103 +6,146 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript"
src="https://fastly.jsdelivr.net/npm/echarts@5.5.0/dist/extension/dataTool.min.js"></script>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts-gl@2/dist/echarts-gl.min.js"></script>
<script type="text/javascript"
src="https://fastly.jsdelivr.net/npm/echarts-stat@latest/dist/ecStat.min.js"></script>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@4.9.0/map/js/china.js"></script>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@4.9.0/map/js/world.js"></script>
<script src="/vendor/plotly-2.16.1.min.js"></script>
<script src="/vendor/jquery.min.js"></script>
</head>
<body style="background: black">
<div id="chart" style="width:100%; height: 700px; background: black;">
<body style="background: black; font-family: Arial, Helvetica, sans-serif; height: 100%; margin: 0;">
<p style="font-size: 20pt; text-align: center; color: #dddddd" id="heading"></p>
<p style="font-size: 12pt; text-align: center; color: #dddddd">
<a href="#" onclick="init()">Refresh</a>
</p>
<div id="chart" style="width:100vw; height: 100bh; background: black;">
</div>
<script>
var chartDom;
var myChart;
var option;
var routes = [];
function lerpColor(color1, color2, weight) {
var r = Math.round(color1[0] + (color2[0] - color1[0]) * weight);
var g = Math.round(color1[1] + (color2[1] - color1[1]) * weight);
var b = Math.round(color1[2] + (color2[2] - color1[2]) * weight);
return `rgb(${r}, ${g}, ${b})`;
}
function getColorForWeight(weight) {
// Define our colors as [R, G, B]
const green = [0, 128, 0];
const orange = [255, 165, 0];
const red = [255, 0, 0];
if (weight <= 0.5) {
// Scale weight to be from 0 to 1 for the green to orange transition
const adjustedWeight = weight * 2;
return lerpColor(green, orange, adjustedWeight);
} else {
// Scale weight to be from 0 to 1 for the orange to red transition
const adjustedWeight = (weight - 0.5) * 2;
return lerpColor(orange, red, adjustedWeight);
}
}
function init() {
chartDom = document.getElementById('chart');
myChart = echarts.init(chartDom);
option;
myChart.showLoading();
$.get("/api/flows/lat_lon", (data) => {
for (var i=0; i<data.length; i++) {
data[i] = [
[data[i][1], data[i][0]],
[-92.328636, 38.951561]
];
}
routes = data;
console.log(routes);
myChart.hideLoading();
myChart.setOption({
geo3D: {
map: 'world',
shading: 'realistic',
silent: true,
environment: '#333',
realisticMaterial: {
roughness: 0.8,
metalness: 0
},
postEffect: {
enable: true
},
groundPlane: {
show: false
},
light: {
main: {
intensity: 1,
alpha: 30
},
ambient: {
intensity: 0
}
},
viewControl: {
distance: 70,
alpha: 89,
panMouseButton: 'left',
rotateMouseButton: 'right'
},
itemStyle: {
color: '#000'
},
regionHeight: 0.5
},
series: [
{
type: 'lines3D',
coordinateSystem: 'geo3D',
effect: {
show: true,
trailWidth: 1,
trailOpacity: 0.5,
trailLength: 0.2,
constantSpeed: 5
},
blendMode: 'lighter',
lineStyle: {
width: 0.2,
opacity: 0.05
},
data: routes
$.get("/api/flows/lat_lon", (worldData) => {
let condensed = {};
let totalBytes = 0;
for (let i = 0; i < worldData.length; i++) {
let label = worldData[i][2];
if (label in condensed) {
condensed[label][3] += worldData[i][3]; // Bytes
totalBytes += worldData[i][3];
if (worldData[i][4] != 0) {
condensed[label][4].push(worldData[i][4]); // RTT
}
]
});
} else {
condensed[label] = worldData[i];
worldData[i][4] = [worldData[i][4]];
totalBytes += worldData[i][3];
}
}
option && myChart.setOption(option);
let entries = [];
for (const [key, value] of Object.entries(condensed)) {
value[3] = value[3] / totalBytes;
entries.push(value);
}
$("#heading").text("World Data. Now tracking " + worldData.length + " flows and " + entries.length + " locations.");
var data = [{
type: 'scattergeo',
//locationmode: 'world',
lat: [],
lon: [],
hoverinfo: 'text',
text: [],
marker: {
size: [],
color: [],
line: {
color: [],
width: 2
},
}
}];
for (let i = 0; i < entries.length; i++) {
var flow = entries[i];
if (flow[4].length != 0) {
var lat = flow[0];
var lon = flow[1];
var text = flow[2];
var bytes = flow[3] * 20;
if (bytes < 5) bytes = 5;
let middle = flow[4].length -1;
var rtt = flow[4][middle] / 1000000;
rtt = rtt / 200;
if (rtt > 1) rtt = 1;
var color = getColorForWeight(rtt);
data[0].lat.push(lat);
data[0].lon.push(lon);
data[0].text.push(text);
data[0].marker.size.push(bytes);
data[0].marker.line.color.push(color);
data[0].marker.color.push(color);
}
}
var layout = {
autosize: true,
margin: {
l: 0,
r: 0,
b: 0,
t: 0,
pad: 0
},
paper_bgcolor: 'black',
geo: {
scope: 'world',
projection: {
type: 'natural earth'
},
showland: true,
showocean: true,
showlakes: true,
showrivers: true,
showcountries: true,
landcolor: 'rgb(217, 217, 217)',
subunitwidth: 1,
countrywidth: 1,
subunitcolor: 'rgb(255,255,255)',
countrycolor: 'rgb(255,255,255)',
framecolor: 'black',
bgcolor: 'black',
},
};
Plotly.newPlot('chart', data, layout, { responsive: true, displayModeBar: false });
});
}

View File

@ -36,17 +36,18 @@ impl TimeBuffer {
buffer.push(entry);
}
pub fn lat_lon_endpoints(&self) -> Vec<(f64, f64)> {
pub fn lat_lon_endpoints(&self) -> Vec<(f64, f64, String, u64, f32)> {
let buffer = self.buffer.lock().unwrap();
let mut my_buffer = buffer
.iter()
.map(|v| {
let (key, _data, _analysis) = &v.data;
let (key, data, _analysis) = &v.data;
let (lat, lon) = get_asn_lat_lon(key.remote_ip.as_ip());
(lat, lon)
let (_name, country) = get_asn_name_and_country(key.remote_ip.as_ip());
(lat, lon, country, data.bytes_sent[1], data.rtt[1].as_nanos() as f32)
})
.filter(|(lat, lon)| *lat != 0.0 && *lon != 0.0)
.collect::<Vec<(f64, f64)>>();
.filter(|(lat, lon, ..)| *lat != 0.0 && *lon != 0.0)
.collect::<Vec<(f64, f64, String, u64, f32)>>();
// Sort by lat/lon
my_buffer.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());