Periodically update the slices in the dashboard (#374)

* Periodically update the slices in the dashboard

* Make the refresh interval changeable

* Add the button and the modal for the user to change the refresh interval

* Don't use callback for refreshing

* Randomize to prevent all widgets refreshing at the same time

* Show the loading icon as an overlay when the slices refresh
This commit is contained in:
x4base 2016-04-20 17:35:07 -07:00 committed by Maxime Beauchemin
parent 9a33557112
commit d8a2b621d8
7 changed files with 174 additions and 104 deletions

View File

@ -31,10 +31,11 @@ var Dashboard = function (dashboardData) {
slice.render(true);
});
sliceObjects.push(slice);
slice.render();
}
});
this.slices = sliceObjects;
this.refreshTimer = null;
this.startPeriodicRender(0);
},
setFilter: function (slice_id, col, vals) {
this.addFilter(slice_id, col, vals, false);
@ -57,6 +58,36 @@ var Dashboard = function (dashboardData) {
// Returns a list of human readable active filters
return JSON.stringify(this.filters, null, 4);
},
stopPeriodicRender: function () {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
},
startPeriodicRender: function (interval) {
this.stopPeriodicRender();
var dash = this;
var maxRandomDelay = Math.min(interval * 0.1, 5000);
var refreshAll = function () {
dash.slices.forEach(function (slice) {
setTimeout(function () {
slice.render(true);
},
//Randomize to prevent all widgets refreshing at the same time
maxRandomDelay * Math.random());
});
};
var fetchAndRender = function () {
refreshAll();
if (interval > 0) {
dash.refreshTimer = setTimeout(function () {
fetchAndRender();
}, interval);
}
};
fetchAndRender();
},
refreshExcept: function (slice_id) {
var immune = this.metadata.filter_immune_slice || [];
this.slices.forEach(function (slice) {
@ -191,6 +222,10 @@ var Dashboard = function (dashboardData) {
body: "The following global filters are currently applied:<br/>" + dashboard.readFilters()
});
});
$("#refresh_dash_interval").on("change", function () {
var interval = $(this).find('option:selected').val() * 1000;
dashboard.startPeriodicRender(interval);
});
$('#refresh_dash').click(function () {
dashboard.slices.forEach(function (slice) {
slice.render(true);

View File

@ -314,8 +314,6 @@ var px = (function () {
}
this.force = force;
token.find("img.loading").show();
container.hide();
container.html('');
container.css('height', slice.height());
dttm = 0;
timer = setInterval(stopwatch, 10);
@ -325,9 +323,7 @@ var px = (function () {
},
resize: function () {
token.find("img.loading").show();
container.hide();
container.css('height', slice.height());
container.html('');
this.viz.render();
this.viz.resize();
},

View File

@ -184,6 +184,7 @@ img.loading {
.dashboard img.loading {
width: 20px;
margin: 5px;
position: absolute;
}
img.viz-thumb-option {
width: 100px;

View File

@ -16,6 +16,8 @@ function bigNumberVis(slice) {
slice.error(error.responseText);
return '';
}
div.html(''); //reset
var fd = payload.form_data;
var json = payload.data;
var color_range = [-1, 1];

View File

@ -20,116 +20,120 @@ function nvd3Vis(slice) {
var colorKey = 'key';
nv.addGraph(function () {
switch (viz_type) {
case 'line':
if (fd.show_brush) {
chart = nv.models.lineWithFocusChart();
chart.lines2.xScale(d3.time.scale.utc());
chart.x2Axis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
} else {
chart = nv.models.lineChart();
}
// To alter the tooltip header
// chart.interactiveLayer.tooltip.headerFormatter(function(){return '';});
chart.xScale(d3.time.scale.utc());
chart.interpolate(fd.line_interpolation);
chart.xAxis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
break;
if (!chart) {
switch (viz_type) {
case 'line':
if (fd.show_brush) {
chart = nv.models.lineWithFocusChart();
chart.lines2.xScale(d3.time.scale.utc());
chart.x2Axis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
} else {
chart = nv.models.lineChart();
}
// To alter the tooltip header
// chart.interactiveLayer.tooltip.headerFormatter(function(){return '';});
chart.xScale(d3.time.scale.utc());
chart.interpolate(fd.line_interpolation);
chart.xAxis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
break;
case 'bar':
chart = nv.models.multiBarChart()
.showControls(true)
.groupSpacing(0.1);
case 'bar':
chart = nv.models.multiBarChart()
.showControls(true)
.groupSpacing(0.1);
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
chart.stacked(fd.bar_stacked);
break;
chart.stacked(fd.bar_stacked);
break;
case 'dist_bar':
chart = nv.models.multiBarChart()
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.reduceXTicks(false)
.rotateLabels(45)
.groupSpacing(0.1); //Distance between each group of bars.
case 'dist_bar':
chart = nv.models.multiBarChart()
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.reduceXTicks(false)
.rotateLabels(45)
.groupSpacing(0.1); //Distance between each group of bars.
chart.xAxis
.showMaxMin(false);
chart.xAxis
.showMaxMin(false);
chart.stacked(fd.bar_stacked);
break;
chart.stacked(fd.bar_stacked);
break;
case 'pie':
chart = nv.models.pieChart();
colorKey = 'x';
chart.valueFormat(f);
if (fd.donut) {
chart.donut(true);
case 'pie':
chart = nv.models.pieChart();
colorKey = 'x';
chart.valueFormat(f);
if (fd.donut) {
chart.donut(true);
chart.labelsOutside(true);
}
chart.labelsOutside(true);
}
chart.labelsOutside(true);
chart.cornerRadius(true);
break;
chart.cornerRadius(true);
break;
case 'column':
chart = nv.models.multiBarChart()
.reduceXTicks(false)
.rotateLabels(45);
break;
case 'column':
chart = nv.models.multiBarChart()
.reduceXTicks(false)
.rotateLabels(45);
break;
case 'compare':
chart = nv.models.cumulativeLineChart();
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
case 'compare':
chart = nv.models.cumulativeLineChart();
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
case 'bubble':
var row = function (col1, col2) {
return "<tr><td>" + col1 + "</td><td>" + col2 + "</td></tr>";
};
chart = nv.models.scatterChart();
chart.showDistX(true);
chart.showDistY(true);
chart.tooltip.contentGenerator(function (obj) {
var p = obj.point;
var s = "<table>";
s += '<tr><td style="color:' + p.color + ';"><strong>' + p[fd.entity] + '</strong> (' + p.group + ')</td></tr>';
s += row(fd.x, f(p.x));
s += row(fd.y, f(p.y));
s += row(fd.size, f(p.size));
s += "</table>";
return s;
});
chart.pointRange([5, fd.max_bubble_size * fd.max_bubble_size]);
break;
case 'bubble':
var row = function (col1, col2) {
return "<tr><td>" + col1 + "</td><td>" + col2 + "</td></tr>";
};
chart = nv.models.scatterChart();
chart.showDistX(true);
chart.showDistY(true);
chart.tooltip.contentGenerator(function (obj) {
var p = obj.point;
var s = "<table>";
s += '<tr><td style="color:' + p.color + ';"><strong>' + p[fd.entity] + '</strong> (' + p.group + ')</td></tr>';
s += row(fd.x, f(p.x));
s += row(fd.y, f(p.y));
s += row(fd.size, f(p.size));
s += "</table>";
return s;
});
chart.pointRange([5, fd.max_bubble_size * fd.max_bubble_size]);
break;
case 'area':
chart = nv.models.stackedAreaChart();
chart.style(fd.stacked_style);
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
case 'area':
chart = nv.models.stackedAreaChart();
chart.style(fd.stacked_style);
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
case 'box_plot':
colorKey = 'label';
chart = nv.models.boxPlotChart();
chart.x(function (d) { return d.label; });
chart.staggerLabels(true);
chart.maxBoxWidth(75); // prevent boxes from being incredibly wide
break;
case 'box_plot':
colorKey = 'label';
chart = nv.models.boxPlotChart();
chart.x(function (d) {
return d.label;
});
chart.staggerLabels(true);
chart.maxBoxWidth(75); // prevent boxes from being incredibly wide
break;
default:
throw new Error("Unrecognized visualization for nvd3" + viz_type);
default:
throw new Error("Unrecognized visualization for nvd3" + viz_type);
}
}
if ("showLegend" in chart && typeof fd.show_legend !== 'undefined') {
@ -197,8 +201,12 @@ function nvd3Vis(slice) {
return px.color.category21(d[colorKey]);
});
d3.select(slice.selector).html('');
d3.select(slice.selector).append("svg")
var svg = d3.select(slice.selector).select("svg");
if (svg.empty()) {
svg = d3.select(slice.selector).append("svg");
}
svg
.datum(payload.data)
.transition().duration(500)
.attr('height', height)

View File

@ -35,7 +35,7 @@ function tableVis(slice) {
maxes[metrics[i]] = d3.max(col(metrics[i]));
}
var table = d3.select(slice.selector).append('table')
var table = d3.select(slice.selector).html('').append('table')
.classed('dataframe dataframe table table-striped table-bordered table-condensed table-hover dataTable no-footer', true)
.attr('width', '100%');

View File

@ -37,6 +37,31 @@
</div>
</div>
</div>
<div class="modal fade" id="refresh_modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Refresh Interval</h4>
<h6><strong>Choose how frequent should the dashboard refresh</strong></h6>
</div>
<div class="modal-body">
<select id="refresh_dash_interval" class="select2" style="margin-bottom: 5px;">
<option value="0">Don't refresh</option>
<option value="10">10 seconds</option>
<option value="30">30 seconds</option>
<option value="60">1 minute</option>
<option value="300">5 minutes</option>
</select><br>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div class="title">
<div class="row">
@ -52,6 +77,9 @@
<button type="button" id="refresh_dash" class="btn btn-default" data-toggle="tooltip" title="Force refresh the whole dashboard">
<i class="fa fa-refresh"></i>
</button>
<button type="button" id="refresh_dash_periodic" class="btn btn-default" data-toggle="modal" data-target="#refresh_modal">
<i class="fa fa-clock-o" data-toggle="tooltip" title="Edit the dashboard's CSS"></i>
</button>
<button type="button" id="filters" class="btn btn-default" data-toggle="tooltip" title="View the list of active filters">
<i class="fa fa-filter"></i>
</button>