ntopng/http_src/utilities/sequence_sunburst.js
2025-05-22 13:09:57 +02:00

400 lines
12 KiB
JavaScript

function do_sequence_sunburst(circle_name, sequence_name, refresh, update_url, url_params, title, units) {
const graph = new SequenceSunburst(circle_name, sequence_name, refresh, update_url, url_params, title, units);
graph.setInterval(window.setInterval(function() {
graph.update();
}, refresh));
// Return new class instance, with
return [graph];
}
function SequenceSunburst(circle_name, sequence_name, refresh, update_url, url_params, title, units) {
// Add object properties like this
this.circle_name = circle_name;
this.sequence_name = sequence_name;
this.update_url = update_url;
this.url_params = url_params;
this.units = units;
this.refresh = refresh;
let oldPieData = [];
let filteredPieData = [];
const rsp = create_sequence_sunburst(circle_name, sequence_name, title, units);
const color = rsp[0];
const partition = rsp[1];
let totalSize = rsp[2];
const arc = rsp[3];
const arc_group = rsp[4];
const trail = rsp[5];
let totalLabel = rsp[7];
let totalValue = rsp[8];
let totalUnits = rsp[9];
const width = rsp[11];
const b = rsp[13];
let last_process = '';
let last_byte = 0;
const substring_limit = 8;
// to run each time data is generated
this.update = function() {
$.ajax({
type: 'GET',
url: this.update_url,
data: this.url_params,
success: function(content) {
update_sequence_sunburst(jQuery.parseJSON(content));
},
error: function(content) {
console.log('error');
},
});
};
// /////////////////////////////////////////////////////////
// STREAKER CONNECTION ////////////////////////////////////
// /////////////////////////////////////////////////////////
// Needed to draw the pie immediately
this.update();
// var updateInterval = window.setInterval(update, refresh);
// /////////////////////////////////////////////////////////
// UPDATE FUNCTIONS ////////////////////////////////////
// /////////////////////////////////////////////////////////
this.flashUpdate = function(start, end) {
arc_group.selectAll('path').style('opacity', start)
.transition().duration(200).style('opacity', end);
};
function update_sequence_sunburst(data) {
const streakerDataAdded = data;
// console.log(data);
oldPieData = filteredPieData;
const pieData = partition.nodes(streakerDataAdded);
filteredPieData = pieData.filter(function(d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
if ((filteredPieData.length > 0) && (oldPieData.length > 0)) {
// REMOVE PLACEHOLDER CIRCLE
arc_group.selectAll('circle').remove();
arc_group.selectAll('path').remove();
// alert("Update");
}
const paths = arc_group.selectAll('path').data(filteredPieData);
paths.enter().append('svg:path')
.attr('display', function(d) {
return d.depth ? null : 'none';
})
.attr('d', arc)
.attr('fill-rule', 'evenodd')
.style('fill', function(d) {
return color(d.name+d.id);
})
.style('opacity', 1)
.on('mouseover', mouseover)
.on('dblclick', function(d) {
if (d.url) window.location.href = d.url;
});
// Add the mouseleave handler to the bounding circle.
d3.select('#container'+circle_name).on('mouseleave', mouseleave);
// Get total size of the tree = value of root node from partition.
totalSize = paths.node().__data__.value;
filteredPieData.forEach(function(d) {
if ((last_process.localeCompare(d.name) == 0) && (d.name != '')) {
const data = NtopUtils.bytesToVolumeAndLabel(d.value);
let value;
if (last_byte < d.value) {
value = data[0]+ ' \uf062';
} else if (last_byte > d.value) {
value = data[0]+ ' \uf063';
} else {
value = data[0]+ ' \uf068';
}
let name = d.name.substring(0, substring_limit);
if (d.name.length > substring_limit) {
name = name + '..';
}
totalValue.text(value);
totalLabel.text(name);
totalUnits.text(data[1]);
};
});
}
// /////////////////////////////////////////////////////////
// UTILS FUNCTIONS ////////////////////////////////////
// /////////////////////////////////////////////////////////
function mouseover(d) {
const percentage = (100 * d.value / totalSize).toPrecision(3);
let percentageString = percentage + '%';
if (percentage < 0.1) {
percentageString = '< 0.1%';
}
const data = NtopUtils.bytesToVolumeAndLabel(d.value);
totalValue.text(data[0]);
totalLabel.text(d.name);
totalUnits.text(data[1]);
last_process = d.name;
last_byte = d.value;
const sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray, percentageString);
// Fade all the segments.
arc_group.selectAll('path')
.style('opacity', 0.3);
// Then highlight only those that are an ancestor of the current segment.
arc_group.selectAll('path')
.filter(function(node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style('opacity', 1);
};
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
// Hide the breadcrumb trail
trail.style('visibility', 'hidden');
// Deactivate all segments during transition.
arc_group.selectAll('path').on('mouseover', null);
// Transition each segment to full opacity and then reactivate it.
arc_group.selectAll('path')
.transition()
.duration(500)
.style('opacity', 1)
.each('end', function() {
d3.select(this).on('mouseover', mouseover);
});
};
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
const path = [];
let current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
// Initialize the Breadcrumb as default
function initializeBreadcrumbTrail(circle_name, sequence_name) {
// Add the svg area.
const trail = d3.select('#'+sequence_name).append('svg:svg')
.attr('width', width)
.attr('height', 50)
.attr('id', 'trail');
// Add the label at the end, for the percentage.
trail.append('svg:text')
.attr('id', 'endlabel')
.style('fill', '#000');
return trail;
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
const points = [];
points.push('0,0');
points.push(b.w + ',0');
points.push(b.w + b.t + ',' + (b.h / 2));
points.push(b.w + ',' + b.h);
points.push('0,' + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + ',' + (b.h / 2));
}
return points.join(' ');
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
const g = trail.selectAll('g')
.data(nodeArray, function(d) {
return d.name + d.depth;
});
// Add breadcrumb and label for entering nodes.
const entering = g.enter().append('svg:g');
entering.append('svg:polygon')
.attr('points', breadcrumbPoints)
.style('fill', function(d) {
return color(d.name+d.id);
});
entering.append('svg:text')
.attr('x', (b.w + b.t) / 2)
.attr('y', b.h / 2)
.attr('dy', '0.35em')
.attr('text-anchor', 'middle')
.text(function(d) {
let name = d.name.substring(0, substring_limit);
if (d.name.length > substring_limit) {
name = name + '..';
}
return (name);
});
// Set position for entering and updating nodes.
g.attr('transform', function(d, i) {
return 'translate(' + i * (b.w + b.s) + ', 0)';
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
trail.select('#endlabel')
.attr('x', (nodeArray.length + 0.5) * (b.w + b.s))
.attr('y', b.h / 2)
.attr('dy', '0.35em')
.attr('text-anchor', 'middle')
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
trail.style('visibility', '');
}
// /////////////////////////////////////////////////////////
// INIT FUNCTIONS ////////////////////////////////////
// /////////////////////////////////////////////////////////
function create_sequence_sunburst(circle_name, sequence_name, title, units) {
// Dimensions of sunburst.
const width = 600;
const height = 280;
const radius = 120;
// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
const b = {
w: 60, h: 25, s: 3, t: 10,
};
// D3 helper function to create colors from an ordinal scale
const color = d3.scale.category20c();
const partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function(d) {
return d.size;
});
var totalSize = 0;
// D3 helper function to draw arcs, populates parameter "d" in path object
const arc = d3.svg.arc()
.startAngle(function(d) {
return d.x;
})
.endAngle(function(d) {
return d.x + d.dx;
})
.innerRadius(function(d) {
return Math.sqrt(d.y);
})
.outerRadius(function(d) {
return Math.sqrt(d.y + d.dy);
});
const vis = d3.select('#'+circle_name).append('svg:svg')
.attr('width', width)
.attr('height', height);
const arc_group = vis.append('svg:g')
.attr('id', 'container'+circle_name)
.attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');
// Basic setup of page elements.
const trail = initializeBreadcrumbTrail(circle_name, sequence_name);
const whiteCircle = arc_group.append('svg:circle')
.attr('fill', 'white')
.attr('r', radius);
// LABEL
totalLabel = arc_group.append('svg:text')
.attr('class', 'label_sunburst')
.attr('dy', -25)
.attr('text-anchor', 'middle')
.attr('font-family', 'FontAwesome')
.text('');
// PERCENT
totalValue = arc_group.append('svg:text')
.attr('class', 'total')
.attr('dy', 7)
.attr('text-anchor', 'middle')
.attr('font-family', 'FontAwesome')
.text('');
// UNITS LABEL
totalUnits = arc_group.append('svg:text')
.attr('class', 'units')
.attr('dy', 30)
.attr('text-anchor', 'middle')
.text('');
return ([color, partition, totalSize, arc, arc_group, trail, whiteCircle, totalLabel, totalValue, totalUnits, radius, width, height, b]);
} // End function create_sequence_sunburst
this.resetCentralCirleText = function() {
totalValue.text(' ');
totalLabel.text(' ');
totalUnits.text('');
};
}
// /////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ////////////////////////////////////
// /////////////////////////////////////////////////////////
SequenceSunburst.prototype.setUrlParams = function(url_params) {
this.url_params = url_params;
this.resetCentralCirleText();
this.forceUpdate();
};
SequenceSunburst.prototype.forceUpdate = function(url_params) {
// this.stopInterval();
this.flashUpdate(0, 1);
this.update();
// this.startInterval();
};
SequenceSunburst.prototype.setInterval = function(p_sunburstInterval) {
this.sunburstInterval = p_sunburstInterval;
};
SequenceSunburst.prototype.stopInterval = function() {
// disabled graph interval
window.clearInterval(this.sunburstInterval);
};
SequenceSunburst.prototype.startInterval = function() {
this.sunburstInterval = window.setInterval(this.update(), this.refresh);
};