mirror of https://github.com/MISP/MISP
chg: [widgets:multiline] Added possibility to pick datapoint and see the
deltaspull/5767/head
parent
e166f7489b
commit
708d76a801
|
@ -59,10 +59,13 @@ function init() { // variables and functions have their own scope (no override)
|
|||
var svg;
|
||||
var width, height, svg_width, svg_height;
|
||||
var xAxis, yAxis, cursorX, cursorY;
|
||||
var x, y, xGrid, yGrid, value_line
|
||||
var series, line_guides, points, pointsGroup, labels
|
||||
var x, y, xGrid, yGrid, value_line;
|
||||
var overlayLeft, overlayRight, tooltipPickedNodes;
|
||||
var series, line_guides, points, pointsGroup, labels;
|
||||
var colors = d3.scale.category10();
|
||||
|
||||
var pickedNodes = {start: null, end: null};
|
||||
|
||||
var options = <?= json_encode(isset($config['widget_config']) ? $config['widget_config'] : array()) ?>;
|
||||
var options = $.extend(true, {}, default_options, options);
|
||||
options = _validateOptions(options);
|
||||
|
@ -106,6 +109,10 @@ function init() { // variables and functions have their own scope (no override)
|
|||
})
|
||||
}
|
||||
|
||||
function getX(datum) {
|
||||
return options.abscissa_linear ? datum.index : datum.date;
|
||||
}
|
||||
|
||||
function _init() {
|
||||
$loadingContainer = $('<div id="loadingChartContainer" style="background: #ffffff9f"><span class="fa fa-spinner fa-spin" style="font-size: xx-large;"></span></div>').css({
|
||||
position: 'absolute',
|
||||
|
@ -126,6 +133,16 @@ function init() { // variables and functions have their own scope (no override)
|
|||
.style('color', 'white')
|
||||
.style('border-radius', '5px')
|
||||
.style('display', 'none');
|
||||
tooltipPickedNodes = d3.select('body').append('div')
|
||||
.attr('class', 'tooltip tooltipPickedNodes')
|
||||
.style('opacity', 0)
|
||||
.style('min-width', '120px')
|
||||
.style('padding', '3px')
|
||||
.style('background-color', '#fff')
|
||||
.style('color', 'black')
|
||||
.style('border', '1px solid black')
|
||||
.style('border-radius', '5px')
|
||||
.style('display', 'none');
|
||||
$container.append($loadingContainer);
|
||||
timeFormatter = d3.time.format(options.time_format).parse;
|
||||
}
|
||||
|
@ -139,8 +156,8 @@ function init() { // variables and functions have their own scope (no override)
|
|||
|
||||
if (options.abscissa_linear) {
|
||||
x = d3.scale.linear()
|
||||
.domain(d3.extent(data, function(d) { return d.index; }))
|
||||
.range([ 0, width ]);
|
||||
.domain(d3.extent(data, function(d) { return d.index; }))
|
||||
.range([ 0, width ]);
|
||||
} else {
|
||||
x = d3.time.scale()
|
||||
.domain(d3.extent(data, function(d) { return d.date; }))
|
||||
|
@ -159,7 +176,7 @@ function init() { // variables and functions have their own scope (no override)
|
|||
.tickFormat("");
|
||||
|
||||
value_line = d3.svg.line()
|
||||
.x(function(d) { return x(options.abscissa_linear ? d.index : d.date); })
|
||||
.x(function(d) { return x(getX(d)); })
|
||||
.y(function(d) { return y(d.count); });
|
||||
|
||||
svg = d3.select(container_id)
|
||||
|
@ -262,6 +279,23 @@ function init() { // variables and functions have their own scope (no override)
|
|||
svg.append('g')
|
||||
.classed('point-group', true);
|
||||
|
||||
overlayLeft = svg.append('rect')
|
||||
.attr('fill', 'black')
|
||||
.attr('opacity', 0.6)
|
||||
.attr('class', 'overlay-left')
|
||||
.attr('width', 0)
|
||||
.attr('height', height)
|
||||
.attr('x', 0)
|
||||
.on('click', clearPickedNodes);
|
||||
overlayRight = svg.append('rect')
|
||||
.attr('fill', 'black')
|
||||
.attr('opacity', 0.6)
|
||||
.attr('class', 'overlay-right')
|
||||
.attr('width', 0)
|
||||
.attr('height', height)
|
||||
.attr('x', 0)
|
||||
.on('click', clearPickedNodes);
|
||||
|
||||
window.addEventListener("resize", function() {
|
||||
if (resize_timeout !== undefined) {
|
||||
clearTimeout(resize_timeout);
|
||||
|
@ -350,7 +384,7 @@ function init() { // variables and functions have their own scope (no override)
|
|||
data_nodes_active = data_nodes.filter(function(d) {
|
||||
return !d.disabled;
|
||||
})
|
||||
x.domain(d3.extent(chart_data, function(d) { return options.abscissa_linear ? d.index : d.date; }))
|
||||
x.domain(d3.extent(chart_data, function(d) { return getX(d); }))
|
||||
y.domain([
|
||||
d3.min(data_nodes_active, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
|
||||
d3.max(data_nodes_active, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
|
||||
|
@ -401,10 +435,10 @@ function init() { // variables and functions have their own scope (no override)
|
|||
points
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('class', 'datapoint d3-line-circle')
|
||||
.attr('class', 'datapoint d3-line-circle useCursorPointer')
|
||||
.attr('r', 5)
|
||||
points // Update
|
||||
.attr('cx', function (d) { return x(options.abscissa_linear ? d.index : d.date); })
|
||||
.attr('cx', function (d) { return x(getX(d)); })
|
||||
.attr('cy', function (d) { return y(d.count); })
|
||||
.style("fill", function(d) { return colors(d.name); })
|
||||
.on('mouseover', function(d) {
|
||||
|
@ -413,6 +447,9 @@ function init() { // variables and functions have their own scope (no override)
|
|||
.on('mouseout', function() {
|
||||
tooltipDate(false);
|
||||
})
|
||||
.on('click', function(d) {
|
||||
handleMarkerClick(d);
|
||||
})
|
||||
pointsGroup.exit().remove();
|
||||
}
|
||||
|
||||
|
@ -511,6 +548,136 @@ function init() { // variables and functions have their own scope (no override)
|
|||
var html = $('<p></p>').text(datum.name).html() + ' (' + formated_date + ', <strong>' + $('<p></p>').text(datum.count).html() + '</strong>) ';
|
||||
return html;
|
||||
}
|
||||
|
||||
function handleMarkerClick(datum) {
|
||||
var xVal = getX(datum);
|
||||
if (pickedNodes.start === null) {
|
||||
pickedNodes.start = datum;
|
||||
} else {
|
||||
if (getX(pickedNodes.start) < xVal) {
|
||||
pickedNodes.end = datum;
|
||||
} else {
|
||||
pickedNodes.end = pickedNodes.start;
|
||||
pickedNodes.start = datum;
|
||||
}
|
||||
}
|
||||
updatePickedNodesOverlays();
|
||||
}
|
||||
|
||||
function clearPickedNodes() {
|
||||
pickedNodes.start = null;
|
||||
pickedNodes.end = null;
|
||||
updatePickedNodesOverlays();
|
||||
}
|
||||
|
||||
function updatePickedNodesOverlays() {
|
||||
if (pickedNodes.start === null) {
|
||||
overlayLeft.attr('width', 0);
|
||||
overlayRight.attr('x', 0)
|
||||
.attr('width', 0);
|
||||
togglePickedNodeTooltip(false);
|
||||
} else {
|
||||
overlayLeft.attr('width', x(getX(pickedNodes.start)));
|
||||
if (pickedNodes.end !== null) {
|
||||
overlayRight.attr('x', x(getX(pickedNodes.end)))
|
||||
.attr('width', width - x(getX(pickedNodes.end)));
|
||||
togglePickedNodeTooltip(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function togglePickedNodeTooltip(show) {
|
||||
if (show) {
|
||||
tooltipPickedNodes.html(genTooltipPickedNodeHtml());
|
||||
tooltipPickedNodes
|
||||
.style('display', 'block')
|
||||
.style('opacity', '0.8');
|
||||
|
||||
var overlayLeftBCR = overlayLeft.node().getBoundingClientRect();
|
||||
var overlayRightBCR = overlayRight.node().getBoundingClientRect();
|
||||
var tooltipBCR = tooltipPickedNodes.node().getBoundingClientRect();
|
||||
var left = (overlayLeftBCR.width - overlayRightBCR.width > 0 ?
|
||||
overlayLeftBCR.left + overlayLeftBCR.width/2 :
|
||||
overlayRightBCR.left + overlayRightBCR.width/2) - tooltipBCR.width / 2;
|
||||
var top = overlayLeftBCR.top + 30;
|
||||
|
||||
tooltipPickedNodes
|
||||
.style('left', left + 'px')
|
||||
.style('top', top + 'px')
|
||||
} else {
|
||||
tooltipPickedNodes.style('display', 'none');
|
||||
}
|
||||
return tooltipPickedNodes;
|
||||
}
|
||||
|
||||
function genTooltipPickedNodeHtml() {
|
||||
var xValueStart = getX(pickedNodes.start)
|
||||
var xValueEnd = getX(pickedNodes.end)
|
||||
var yValues = []
|
||||
data_nodes_active.forEach(function(serie) {
|
||||
var startPoint = serie.values.find(function(point) {
|
||||
return getX(point) == xValueStart;
|
||||
})
|
||||
var endPoint = serie.values.find(function(point) {
|
||||
return getX(point) == xValueEnd;
|
||||
})
|
||||
if (startPoint !== undefined && endPoint !== undefined)
|
||||
var deltaY = endPoint.count - startPoint.count;
|
||||
var deltaYPerc = startPoint.count != 0 ? Math.abs(100*deltaY / startPoint.count).toFixed(2) : '-';
|
||||
yValues.push({
|
||||
name: serie.name,
|
||||
nameColor: colors(serie.name),
|
||||
deltaY: deltaY,
|
||||
deltaYPerc: deltaYPerc + '%',
|
||||
yColor: deltaY == 0 ? '' : (deltaY > 0 ? 'success' : 'error')
|
||||
})
|
||||
})
|
||||
if (!options.abscissa_linear) {
|
||||
xValueStart = d3.time.format(options.time_format)(xValueStart);
|
||||
xValueEnd = d3.time.format(options.time_format)(xValueEnd);
|
||||
}
|
||||
var $content = $('<div></div>').append(
|
||||
$('<div style="display: flex; justify-content: space-between;"></div>').append(
|
||||
$('<span class="bold"></span>').text(xValueStart),
|
||||
$('<i class="fas fa-arrow-right"></i>'),
|
||||
$('<span class="bold"></span>').text(xValueEnd)
|
||||
)
|
||||
);
|
||||
var $table = $('<table class="table table-condensed"></table>').append(
|
||||
$('<thead></thead>').append($('<tr></tr>').append(
|
||||
$('<th></th>').text('Name'),
|
||||
$('<th></th>').text('Delta'),
|
||||
$('<th></th>').text('Delta %')
|
||||
)
|
||||
)
|
||||
);
|
||||
yValues.forEach(function(serie) {
|
||||
$table.append(
|
||||
$('<tbody></tbody>').append($('<tr></tr>').append(
|
||||
$('<td></td>').append(
|
||||
$('<svg height="10px" width="15px"></svg>').append($('<circle></circle>')
|
||||
.attr('cx', 5)
|
||||
.attr('cy', 5)
|
||||
.attr('r', 5)
|
||||
.css('fill', serie.nameColor)
|
||||
),
|
||||
$('<span></span>').text(serie.name)
|
||||
),
|
||||
$('<td></td>')
|
||||
.addClass('text-'+serie.yColor)
|
||||
.text(serie.deltaY)
|
||||
.append($('<i></i>').addClass(serie.deltaY > 0 ? 'fas fa-caret-up' : 'fas fa-caret-down')),
|
||||
$('<td></td>')
|
||||
.addClass('text-'+serie.yColor)
|
||||
.text(serie.deltaYPerc)
|
||||
.append($('<i></i>').addClass(serie.deltaY > 0 ? 'fas fa-caret-up' : 'fas fa-caret-down')),
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$content.append($table);
|
||||
return $content[0].outerHTML;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -578,4 +745,8 @@ function init() { // variables and functions have their own scope (no override)
|
|||
.axis.grid path {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.overlay-right, .overlay-left {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue