ntopng/httpdocs/js/heatmap.js
2020-02-22 09:47:22 +01:00

683 lines
No EOL
20 KiB
JavaScript

var map = (function () {
var interval = 5000;
var intervalID;
var stopInterval = false;
var host_ip;
var original_pkt_num = 0;
var margin = {top: 80, right: 20, bottom: 120, left: 150},
width = 1100 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
//window height and width
var w_h, w_w;
var max_X_elem, max_Y_elem;
//flag used to alternate the svg containers (for updates)
var svgFlag = true;
var svg;
//axis
var x,y;
//square dim
var sq_h = 12;
var sq_w = 12;
var tooltip;
//axis elements (label)
var X_elements;
var Y_elements;
//color scale
var myColor;
var sendersTotPkts = {};
var receiversTotPkts = {};
var maxTotPkt = 0;
var sendersNum = 0;
var receiversNum = 0;
var excludedSendersNum = 0;
var excludedReceiversNum = 0;
//----------MOUSE EVENT---------------
var mouseoverSquare = function(d) {
tooltip.style("opacity", 0.9)
d3.select(this)
.style("stroke", "black")
tooltip.html("Pkt num: " + d.value)
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY - 35) + "px")
d3.selectAll(".x_label")
.filter(function(e){
return e == d.x_label;
})
.style("fill", "red" )
.style("font-size", 11)
d3.selectAll(".y_label")
.filter(function(e){
return e == d.y_label;
})
.style("fill", "red" )
.style("font-size", 11)
}
var mouseleaveSquare = function(d) {
tooltip.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
d3.selectAll(".x_label, .y_label")
.style("fill", "black" )
.style("font-size", 11)
}
var labelClick = function(d){
var url = window.location.href;
var segements = url.split("/");
segements[segements.length - 1] = "host_details.lua?host="+d;
window.location.href = segements.join("/");
}
var mouseoverYAxisSquare = function(d){
d3.selectAll(".y_label")
.filter(function(e){
return e == d.y_label;
})
.style("fill", "red" )
.style("font-size", 11)
var match = new Array();
d3.selectAll(".squares").filter(function(e){
if (e.y_label == d.y_label)
match.push(e.x_label);
})
d3.selectAll(".x_label")
.filter(function(e){
return match.includes(e);
})
.style("fill", "red" )
.style("font-size", 11)
d3.selectAll(" .squares")
.filter(function(e){
return d.y_label == e.y_label;
})
.style("stroke", "black")
};
var mouseleaveYAxisSquare = function(d){
d3.selectAll(".x_label, .y_label")
.style("fill", "black" )
.style("font-size", 11)
d3.selectAll(" .squares")
.style("stroke", "none")
};
var mouseoverYLabel = function(d){
tooltip.style("opacity", 0.9)
tooltip.html("sent: " + sendersTotPkts[d])
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY - 35) + "px")
d3.select(this)
.style("fill", "red" )
.style("font-size", 11);
var match = new Array();
d3.selectAll(".squares").filter(function(e){
if (e.y_label == d)
match.push(e.x_label);
})
d3.selectAll(".x_label")
.filter(function(e){
return match.includes(e);
})
.style("fill", "red" )
.style("font-size", 11);
d3.selectAll(" .squares")
.filter(function(e){
return d == e.y_label;
})
.style("stroke", "black");
};
var mouseleaveYlabel = function(d){
tooltip.style("opacity", 0)
d3.selectAll(".x_label, .y_label")
.style("fill", "black" )
.style("font-size", 11)
d3.selectAll(" .squares")
.style("stroke", "none")
};
var mouseoverXLabel = function(d){
tooltip.style("opacity", 0.9)
tooltip.html("rcvd: " + receiversTotPkts[d])
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY - 35) + "px")
d3.select(this)
.style("fill", "red" )
.style("font-size", 11);
var match = new Array();
d3.selectAll(".squares").filter(function(e){
if (e.y_label == d)
match.push(e.y_label);
})
d3.selectAll(".y_label")
.filter(function(e){
return match.includes(e);
})
.style("fill", "red" )
.style("font-size", 11);
d3.selectAll(" .squares")
.filter(function(e){
return d == e.x_label;
})
.style("stroke", "black");
};
var mouseleaveXlabel = function(d){
tooltip.style("opacity", 0)
d3.selectAll(".x_label, .y_label")
.style("fill", "black" )
.style("font-size", 11)
d3.selectAll(" .squares")
.style("stroke", "none")
};
//---------END-MOUSE-EVENT------------
//TODO: put all css stylesheet in heatmap.css
var setSvgDim = function(){
width = ( Object.keys(X_elements).length * sq_w) + margin.left+margin.right;
height = ( Object.keys(Y_elements).length * sq_h);
//min size for not clip the text
if (height + margin.top + margin.bottom < 250 ) height = 250 - margin.top - margin.bottom;
if ( width + margin.left + margin.right < 680 ) width = 680 - margin.left - margin.right;
//apply svg resize
d3.select( getCurrentContainerID() )
.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
};
var setXaxis = function(){
var w = Object.keys(X_elements).length * sq_w ;
if (w > width - margin.left - margin.right) w = width - margin.left - margin.right;
x = d3.scaleBand()
.range([ 0, w ])
.domain(X_elements)
.padding(0.07);
svg.append("g")
.style("font-size", 11)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(5))
.selectAll("text")
.attr("class", "x_label")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)")
.on("mouseover", mouseoverXLabel )
.on("mouseleave", mouseleaveXlabel );
};
var setYaxis = function(){
y = d3.scaleBand()
.range([ height, 0 ])
.domain(Y_elements)
.padding(0.05);
svg.append("g")
.style("font-size", 11)
.call(d3.axisLeft(y).tickSize(3))
.selectAll("text")
.attr("class", "y_label")
.attr("dx", "-2.5em")
.on("mouseover", mouseoverYLabel )
.on("mouseleave", mouseleaveYlabel );
};
var setYaxisSquare = function(data){
svg.selectAll()
.data(data, function(d) {return d.y_label;})
.enter()
.append("rect")
.attr("x", function(d) { return -16 })
.attr("y", function(d) { return y(d.y_label) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", 8 )
.attr("height", y.bandwidth() )
.style("fill",
function(d) {
if (d.value == 0)
return "whitesmoke";
else
return myColor( sendersTotPkts[d.y_label] );
}
)
.on("mouseover", mouseoverYAxisSquare)
.on("mouseleave", mouseleaveYAxisSquare);
};
var createSquares = function(data){
svg.selectAll()
.data(data, function(d) {return d.x_label+':'+d.y_label;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.x_label) })
.attr("y", function(d) { return y(d.y_label) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.attr("class","squares")
.style("fill",
function(d) {
if (d.value == 0)
return "white";
else
return myColor(d.value);
}
)
.style("stroke-width", 3)
.style("stroke", "none")
.style("opacity", 1)
.on("mouseover", mouseoverSquare)
.on("mouseleave", mouseleaveSquare);
};
var createTooltip = function(){
tooltip = d3.select( getCurrentContainerID() )
.append("div")
.attr("class", "tooltip-heatmap")
.style("opacity", 0);
};
var createSvg = function(){
svg = d3.select(getCurrentContainerID())
.insert("svg")
.insert("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
};
var changeContainerID = function (){
svgFlag = !svgFlag;
};
var printNoHost = function(){
svg.append("text")
.attr("x", - margin.left)
.attr("y", 12)
.attr("text-anchor", "left")
.style("font-size", "14px")
.text("No Requests");
};
var printText = function(){
// Add title to graph
svg.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("Top Talkers ARP-Map");
// Add subtitle to graph
svg.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("Senders on Y axis, Receivers on X axis ");
// heatmap range info
svg.append("text")
.attr("x", 292)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.text("1 pkt");
// heatmap range info
svg.append("text")
.attr("x", 292 + 170)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.text(maxTotPkt+" pkt");
//color scale image near the title
svg.append("image")
.attr("x", 80)
.attr("y", -70)
.attr("height", 20)
.attr("width", 600)
.attr("xlink:href", "../img/inferno.png")
.style("stroke","black")
.style("stroke-width", "2px");
//border of color scale image
svg.append("rect")
.attr("x", 295)
.attr("y", -71)
.attr("height", 22)
.attr("width", 170)
.style("fill", "transparent")
.style("stroke","black")
.style("stroke-width", "2px");
//X axis description
svg.append('text')
.attr('x', margin.left - 120 )
.attr('y', height + 110 )
.attr('text-anchor', 'middle')
.text('Receivers ( '+ (receiversNum - excludedReceiversNum)+" / "+receiversNum+" )");
//Y axis description
svg.append('text')
.attr('x', -40 )
.attr('y', -margin.left/2 - 55)
.attr('transform', 'rotate(-90)')
.attr('text-anchor', 'middle')
.text('Senders ( ' + (sendersNum-excludedSendersNum)+" / "+sendersNum+" )" );
};
var getCurrentContainerID = function(){
return svgFlag ? "#container" : "#container2";
};
var startInterval = function(_interval) {
intervalID = setInterval(function() {
build(interval, host_ip);
}, _interval);
}
var stopUpdate = function (){
stopInterval = true;
};
var startUpdate = function (){
stopInterval = false;
};
var printVoidGraph = function(){
var div = document.getElementById("container");
div.innerHTML = "Nothing to show. Make sure the ArpMatrix is activated, check the Preference -> Misc ";
div.style.textAlign = "center";
$(".control-group").remove();
};
//########################################################################################
//the calling order of the functions is important (most variable are global)
var buildMap = function(data) {
//NOTE: temporary solution
//disable scroll
$('html, body').css({
'overflow': 'hidden',
'height': '100%'
})
w_h = window.innerHeight - margin.top - margin.bottom - 80;
w_w = window.innerWidth - margin.left - margin.right - 30;
max_X_elem = Math.floor(w_w / sq_w);
max_Y_elem = Math.floor(w_h / sq_h);
createSvg();
X_elements = d3.map(data, function(d){return d.x_label;}).keys()
Y_elements = d3.map(data, function(d){return d.y_label;}).keys()
if ( Object.keys(Y_elements).length == 0 || w_w < sq_w){
printVoidGraph();
return;
}
sendersTotPkts = {};
receiversTotPkts = {};
maxTotPkt = 0;
d3.map(data).values().forEach(e => {
if ( sendersTotPkts.hasOwnProperty(e.y_label) )
sendersTotPkts[e.y_label] += e.value;
else
sendersTotPkts[e.y_label] = e.value;
if (receiversTotPkts.hasOwnProperty(e.x_label) )
receiversTotPkts[e.x_label] += e.value;
else
receiversTotPkts[e.x_label] = e.value;
if (sendersTotPkts[e.y_label] > maxTotPkt)
maxTotPkt = sendersTotPkts[e.y_label];
});
//if host is selected put it first
if (host_ip){
original_pkt_num = sendersTotPkts[host_ip];
sendersTotPkts[host_ip] = Number.MAX_SAFE_INTEGER;
}
Y_elements.sort(function(a,b){ return sendersTotPkts[b] - sendersTotPkts[a] });
X_elements.sort(function(a,b){ return receiversTotPkts[b] - receiversTotPkts[a] });
sendersNum = Object.keys(Y_elements).length;
receiversNum = Object.keys(X_elements).length;
excludedSendersNum = 0;
excludedReceiversNum = 0;
//NOTE: slice(start, end) NOT include the "start" and "end" indexes
var sliced = false;
if (sendersNum > max_Y_elem ){
excludedSendersNum = sendersNum - max_Y_elem ;
Y_elements = Y_elements.slice(0, max_Y_elem+1 );
sliced = true;
}
if (receiversNum > max_X_elem ){
excludedReceiversNum = receiversNum - max_X_elem;
X_elements = X_elements.slice(0, max_X_elem+1 );
sliced = true;
}
//NOTE: forEach() iterate in ascending order
if (sliced){
data = data.filter( function(d){
return ( Y_elements.includes( d.y_label) && X_elements.includes(d.x_label) )
});
X_elements = X_elements.filter( e => {
var f = false;
data.forEach(d => {
if (d.x_label == e){ f = true; return;}
})
if(!f) excludedReceiversNum ++;
return f;
});
}
// list of chromatic scale [ https://github.com/d3/d3-scale-chromatic ]
myColor = d3.scaleSequential().interpolator(d3.interpolateInferno).domain([1,maxTotPkt]);
Y_elements.reverse();
setSvgDim();
//if host is selected highlights it
if (host_ip){
sendersTotPkts[host_ip] = original_pkt_num;
svg.append("line")
.attr("x1", -margin.left+40)
.attr("y1", 6)
.attr("x2", width - margin.left )
.attr("y2", 6)
.attr("stroke-width", 11)
.attr("stroke", "yellow")
.style("opacity",0.4);
}
setXaxis();
setYaxis();
d3.selectAll('.tick text').on('click',labelClick);
setYaxisSquare(data);
createTooltip();
createSquares(data);
printText();
//only now (new svg drawn) i can remove (and replace) the old svg
changeContainerID();
d3.select( getCurrentContainerID() ).selectAll("*").remove();
//NOTE: temporary solution
//enable scroll
$('html, body').css({
'overflow': 'auto',
'height': 'auto'
})
};
//########################################################################################
//NOTE: NOT CURRENTLY USED
var buildMiniMap = function(data){
createSvg();
var svg_x = document.getElementById("container").getBoundingClientRect().left;
w_w = window.innerWidth - margin.left - margin.right - svg_x;
//max_X_elem = Math.floor(w_w / sq_w);
max_X_elem = 5;
if (max_X_elem < 0 ) max_X_elem = 0;
X_elements = d3.map(data, function(d){return d.x_label;}).keys()
Y_elements = d3.map(data, function(d){return d.y_label;}).keys()
if ( Object.keys(X_elements).length == 0 || w_w < sq_w){
height = 20;
d3.select( getCurrentContainerID() )
.select("svg")
.attr("height", height);
//printNoHost();
changeContainerID();
d3.select( getCurrentContainerID() ).selectAll("*").remove();
return;
}
sendersTotPkts = {};
receiversTotPkts = {};
maxTotPkt = 0;
d3.map(data).values().forEach(e => {
if ( sendersTotPkts.hasOwnProperty(e.y_label) )
sendersTotPkts[e.y_label] += e.value;
else
sendersTotPkts[e.y_label] = e.value;
if (receiversTotPkts.hasOwnProperty(e.x_label) )
receiversTotPkts[e.x_label] += e.value;
else
receiversTotPkts[e.x_label] = e.value;
if (sendersTotPkts[e.y_label] > maxTotPkt)
maxTotPkt = sendersTotPkts[e.y_label];
});
X_elements.sort(function(a,b){ return receiversTotPkts[b] - receiversTotPkts[a] });
receiversNum = Object.keys(X_elements).length;
excludedReceiversNum = 0;
if (receiversNum > max_X_elem ){
excludedReceiversNum = receiversNum - max_X_elem;
X_elements = X_elements.slice(0, max_X_elem+1 );
}
sq_w = 20;
width = ( Object.keys(X_elements).length * sq_w + margin.left + margin.right);
height = 100 - margin.top - margin.bottom;
if ( width > 800) width = 800 - margin.left - margin.right;
//apply svg resize
d3.select( getCurrentContainerID() )
.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
myColor = d3.scaleSequential().interpolator(d3.interpolateInferno).domain([1,maxTotPkt]);
setXaxis();
//setYaxis();
y = d3.scaleBand()
.range([ height, 0 ])
.domain(Y_elements)
.padding(0.05);
d3.selectAll('.tick text').on('click',labelClick);
createTooltip();
createSquares(data);
//TODO: set hint text
changeContainerID();
d3.select( getCurrentContainerID() ).selectAll("*").remove();
};
//########################################################################################
var build = function(refresh,host) {
console.log("refresh: "+refresh+" host: "+ host);
host ? host_ip = host : host_ip = null;
clearInterval(intervalID);
if ( !stopInterval ){
if(refresh){
interval = refresh;
//host ? host_ip = host : host_ip = null;
startInterval(interval);
d3.json("/lua/get_arp_matrix_data.lua", buildMap);
}else{ /* case: refresh frequency -> never */
d3.json("/lua/get_arp_matrix_data.lua", buildMap);
}
}
};
return {
build:build,
stopUpdate:stopUpdate,
startUpdate:startUpdate
}
})();