diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2019-12-30 18:44:56 +0100 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2019-12-30 23:16:24 +0100 |
commit | 63802b984cee7b1e930b4c9d992910c731dd16ba (patch) | |
tree | a776e58ef543d0f4c42d450ade71fe5f34476e75 /src/munin-plot.js | |
parent | 9cf7cb407183e5bc0159d789bd684dcab8f404e2 (diff) |
Add navigation bar with date range selector
This also adds a few small style tweaks and ensures that webpack does
not minimise development builds.
Diffstat (limited to 'src/munin-plot.js')
-rw-r--r-- | src/munin-plot.js | 996 |
1 files changed, 528 insertions, 468 deletions
diff --git a/src/munin-plot.js b/src/munin-plot.js index 46c1494..2aa7388 100644 --- a/src/munin-plot.js +++ b/src/munin-plot.js @@ -23,523 +23,583 @@ require('bootstrap') require('jquery-ui/ui/widgets/draggable') require('jquery-ui/ui/widgets/sortable') +require('daterangepicker') $(document).ready(function () { - jQuery(function ($) { - var panelList = $('#draggablelist') - panelList.sortable({ - handle: '.draghandle' - }) + // make list of graphs draggable + $('#draggablelist').sortable({ + handle: '.draghandle' }) -}) -var defaultColors = [ - '#00cc00', '#0066b3', '#ff8000', '#dbc300', '#330099', '#990099', - '#bce617', '#ff0000', '#808080', '#008f00', '#00487d', '#b35a00', - '#b38f00', '#6b006b', '#8fb300', '#b30000', '#bebebe', '#80ff80', - '#80c9ff', '#ffc080', '#ffe680', '#aa80ff', '#ee00cc', '#ff8080', - '#666600', '#ffbfff', '#00ffcc', '#cc6699', '#999900'] -var baseLayout = { - margin: {l: 48, t: 0, r: 8, b: 32}, - autosize: true, - showlegend: false, - dragmode: 'pan', - selectdirection: 'h', - xaxis: { - type: 'date', - tickfont: { - size: 10, - color: '#7f7f7f' + moment.fn.round10Minutes = function (how) { + how = how || 'round' + return this.minutes(Math[how](this.minutes() / 10) * 10).seconds(0).seconds(0).milliseconds(0) + } + + // set the date range across graphs and date range picker + function setDateRange(start, end) { + // ensure start and end are moments + if (typeof start !== 'object') { + start = moment(start) + } + if (typeof end !== 'object') { + end = moment(end) + } + // round times to 10 minute intervals + start.round10Minutes('floor') + end.round10Minutes('ceil') + // update the date range picker + var daterangepicker = $('#reportrange').data('daterangepicker') + daterangepicker.startDate = start + daterangepicker.endDate = end + // ensure start and end are strings + start = start.format('YYYY-MM-DD HH:mm') + end = end.format('YYYY-MM-DD HH:mm') + // update range for picker label + $('#reportrange span').html(start + ' - ' + end) + // update graphs as needed + $('.myplot').each(function () { + if (this.layout) { + const xaxis = this.layout.xaxis + if ((xaxis.range[0] !== start) || (xaxis.range[1] !== end)) { + Plotly.relayout(this, {'xaxis.range': [start, end]}) + } + } + }) + } + + // initialise the date range picker + $('#reportrange').daterangepicker({ + locale: { + format: 'YYYY-MM-DD HH:mm' }, - hoverformat: '%a %Y-%m-%d %H:%M' - }, - yaxis: { - fixedrange: true, - tickfont: { - size: 10, - color: '#7f7f7f' + opens: 'left', + timePicker: true, + timePickerIncrement: 10, + timePicker24Hour: true, + showDropdowns: true, + showCustomRangeLabel: false, + alwaysShowCalendars: true, + ranges: { + Today: [moment().subtract(1, 'days').round10Minutes(), moment().add(1, 'hour').round10Minutes('ceil')], + Yesterday: [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day').round10Minutes('ceil')], + 'Last 7 days': [moment().subtract(6, 'days').startOf('day'), moment().endOf('day').round10Minutes('ceil')], + 'Last 30 days': [moment().subtract(29, 'days').startOf('day'), moment().endOf('day').round10Minutes('ceil')], + 'This month': [moment().startOf('month'), moment().endOf('month').round10Minutes('ceil')], + 'Last month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month').round10Minutes('ceil')], + 'This year': [moment().subtract(365, 'days').startOf('month'), moment().endOf('month').round10Minutes('ceil')] + } + }, setDateRange) + + // set a default date range + setDateRange(moment().subtract(2, 'days'), moment().add(1, 'hour').round10Minutes('ceil')) + + var defaultColors = [ + '#00cc00', '#0066b3', '#ff8000', '#dbc300', '#330099', '#990099', + '#bce617', '#ff0000', '#808080', '#008f00', '#00487d', '#b35a00', + '#b38f00', '#6b006b', '#8fb300', '#b30000', '#bebebe', '#80ff80', + '#80c9ff', '#ffc080', '#ffe680', '#aa80ff', '#ee00cc', '#ff8080', + '#666600', '#ffbfff', '#00ffcc', '#cc6699', '#999900'] + + var baseLayout = { + margin: {l: 48, t: 0, r: 8, b: 32}, + autosize: true, + showlegend: false, + dragmode: 'pan', + selectdirection: 'h', + xaxis: { + type: 'date', + tickfont: { + size: 10, + color: '#7f7f7f' + }, + hoverformat: '%a %Y-%m-%d %H:%M' }, - titlefont: { - size: 10, - color: '#7f7f7f' + yaxis: { + fixedrange: true, + tickfont: { + size: 10, + color: '#7f7f7f' + }, + titlefont: { + size: 10, + color: '#7f7f7f' + }, + exponentformat: 'SI', + hoverformat: '.4s' }, - exponentformat: 'SI', - hoverformat: '.4s' - }, - legend: { - bgcolor: '#ffffffa0', - xanchor: 'auto', - x: 1.2 - }, - datarevision: 1 -} + legend: { + bgcolor: '#ffffffa0', + xanchor: 'auto', + x: 1.2 + }, + datarevision: 1 + } -var config = { - showLink: false, - displaylogo: false, - autosizable: true, - responsive: true, - scrollZoom: true, - displayModeBar: false, - modeBarButtonsToRemove: [ - 'sendDataToCloud', - 'toImage', - 'lasso2d', - 'resetScale2d', - 'hoverClosestCartesian', - 'hoverCompareCartesian' - ], - showTips: false + var config = { + showLink: false, + displaylogo: false, + autosizable: true, + responsive: true, + scrollZoom: true, + displayModeBar: false, + modeBarButtonsToRemove: [ + 'sendDataToCloud', + 'toImage', + 'lasso2d', + 'resetScale2d', + 'hoverClosestCartesian', + 'hoverCompareCartesian' + ], + showTips: false // toImageButtonOptions -} + } -// whether new data should be loaded -var updatedata = false + // whether new data should be loaded + var updatedata = false -function htmlescape(text) { - var p = document.createElement('p') - p.appendChild(document.createTextNode(text)) - return p.innerHTML.replace(/'/g, '"') -} + function htmlescape(text) { + var p = document.createElement('p') + p.appendChild(document.createTextNode(text)) + return p.innerHTML.replace(/'/g, '"') + } -// update the legend -function updateLegend(plot, tracebyfield, legendbyfield) { - var [minx, maxx] = plot.layout.xaxis.range - Object.keys(legendbyfield).forEach(function (field) { - var columns = legendbyfield[field].getElementsByTagName('td') - // calculate minimum - var mintrace = tracebyfield[field + '.min'] - var minvalue = Math.min.apply(null, mintrace.y.filter(function (el, idx) { - var x = mintrace.x[idx] - return x >= minx && x <= maxx - })) - // calculate average - var avgtrace = tracebyfield[field] - if (avgtrace.y.length) { - var avgvalue = avgtrace.y.map(function (current, idx) { - var x = avgtrace.x[idx] - if (idx > 0 && x >= minx && x <= maxx) { - return [current, Date.parse(x) - Date.parse(avgtrace.x[idx - 1])] - } else { - return [current, 0] - } - }).reduce(function (acc, current, currentIndex, array) { - return [acc[0] + (current[0] * current[1]), acc[1] + current[1]] - }) - avgvalue = avgvalue[0] / avgvalue[1] - } else { - avgvalue = undefined - } - // calculate maximum - var maxtrace = tracebyfield[field + '.max'] - var maxvalue = Math.max.apply(null, maxtrace.y.filter(function (el, idx) { - var x = maxtrace.x[idx] - return x >= minx && x <= maxx - })) - // update legend - columns[2].textContent = (isNaN(minvalue) || !isFinite(minvalue)) ? '-' : Plotly.d3.format('.4s')(minvalue) - columns[3].textContent = (isNaN(avgvalue) || !isFinite(avgvalue)) ? '-' : Plotly.d3.format('.4s')(avgvalue) - columns[4].textContent = (isNaN(maxvalue) || !isFinite(maxvalue)) ? '-' : Plotly.d3.format('.4s')(maxvalue) - }) -} + // update the legend + function updateLegend(plot, tracebyfield, legendbyfield) { + var [minx, maxx] = plot.layout.xaxis.range + Object.keys(legendbyfield).forEach(function (field) { + var columns = legendbyfield[field].getElementsByTagName('td') + // calculate minimum + var mintrace = tracebyfield[field + '.min'] + var minvalue = Math.min.apply(null, mintrace.y.filter(function (el, idx) { + var x = mintrace.x[idx] + return x >= minx && x <= maxx + })) + // calculate average + var avgtrace = tracebyfield[field] + if (avgtrace.y.length) { + var avgvalue = avgtrace.y.map(function (current, idx) { + var x = avgtrace.x[idx] + if (idx > 0 && x >= minx && x <= maxx) { + return [current, Date.parse(x) - Date.parse(avgtrace.x[idx - 1])] + } else { + return [current, 0] + } + }).reduce(function (acc, current, currentIndex, array) { + return [acc[0] + (current[0] * current[1]), acc[1] + current[1]] + }) + avgvalue = avgvalue[0] / avgvalue[1] + } else { + avgvalue = undefined + } + // calculate maximum + var maxtrace = tracebyfield[field + '.max'] + var maxvalue = Math.max.apply(null, maxtrace.y.filter(function (el, idx) { + var x = maxtrace.x[idx] + return x >= minx && x <= maxx + })) + // update legend + columns[2].textContent = (isNaN(minvalue) || !isFinite(minvalue)) ? '-' : Plotly.d3.format('.4s')(minvalue) + columns[3].textContent = (isNaN(avgvalue) || !isFinite(avgvalue)) ? '-' : Plotly.d3.format('.4s')(avgvalue) + columns[4].textContent = (isNaN(maxvalue) || !isFinite(maxvalue)) ? '-' : Plotly.d3.format('.4s')(maxvalue) + }) + } -// load graph data into the plot -function loadGraph(plot, legend, graph) { + // load graph data into the plot + function loadGraph(plot, legend, graph) { // prepare the graph configuration - var layout = JSON.parse(JSON.stringify(baseLayout)) - if (graph.graph_vlabel) { - layout.yaxis.title = graph.graph_vlabel - } - if (graph.graph_args && graph.graph_args.match(/--logarithmic/)) { - layout.yaxis.type = 'log' - layout.yaxis.exponentformat = 'E' - } - // get x axis zoom from another plot - var existingplot = document.querySelector('#draggablelist .myplot') - if (existingplot && existingplot.layout && existingplot.layout.xaxis.range) { - layout.xaxis.range = [existingplot.layout.xaxis.range[0], existingplot.layout.xaxis.range[1]] - } - // prepare the data series configuration - var traces = [] - var tracebyfield = {} - plot.tracebyfield = tracebyfield - var stackgroup = 0 - for (var i = 0; i < graph.fields.length; i++) { - var field = graph.fields[i] - var color = field.colour ? '#' + field.colour : defaultColors[i % defaultColors.length] - if (field.draw === 'AREA' || field.draw === 'STACK' || field.draw === 'AREASTACK') { - if (!field.draw.match(/STACK/) && (!graph.fields[i + 1] || graph.fields[i + 1].draw.match(/STACK/))) { - stackgroup += 1 - } - var trace = { - field_name: field.name, - name: field.label || field.name, - info: field.info || '', - line: {width: 0}, - fillcolor: color + 'c0', - hoverlabel: {bgcolor: color + 'c0'}, - stackgroup: 'stack' + stackgroup - } - traces.push(trace) - tracebyfield[field.name] = trace - tracebyfield[field.name + '.min'] = {} - tracebyfield[field.name + '.max'] = {} - } else if (field.draw) { - trace = { - field_name: field.name, - name: field.label || field.name, - info: field.info || '', - line: {color: color}, - hoverlabel: {bgcolor: color + 'c0'} - } - var minTrace = { - field_name: field.name, - showlegend: false, - hoverinfo: 'skip', - line: {width: 0} - } - var maxTrace = { - field_name: field.name, - showlegend: false, - hoverinfo: 'skip', - line: {width: 0}, - fill: 'tonexty', - fillcolor: color + '20' - } - traces.push(trace, minTrace, maxTrace) - tracebyfield[field.name] = trace - tracebyfield[field.name + '.min'] = minTrace - tracebyfield[field.name + '.max'] = maxTrace + var layout = JSON.parse(JSON.stringify(baseLayout)) + if (graph.graph_vlabel) { + layout.yaxis.title = graph.graph_vlabel } - } - // make placeholders for data in traces - Object.keys(tracebyfield).forEach(function (field) { - tracebyfield[field].x = [] - tracebyfield[field].y = [] - }) - // build the legend - var legendbyfield = {} - traces.slice().reverse().forEach(function (trace) { - if (trace.showlegend !== false) { - var legendrow = document.createElement('tr') - var style - if (trace.fillcolor) { - style = 'stroke: ' + trace.fillcolor + ';stroke-width:8' - } else { - style = 'stroke: ' + trace.line.color + ';stroke-width:2' + if (graph.graph_args && graph.graph_args.match(/--logarithmic/)) { + layout.yaxis.type = 'log' + layout.yaxis.exponentformat = 'E' + } + // get x axis zoom from date range selector + var daterangepicker = $('#reportrange').data('daterangepicker') + layout.xaxis.range = [ + daterangepicker.startDate.format('YYYY-MM-DD HH:mm'), + daterangepicker.endDate.format('YYYY-MM-DD HH:mm')] + // prepare the data series configuration + var traces = [] + var tracebyfield = {} + plot.tracebyfield = tracebyfield + var stackgroup = 0 + for (var i = 0; i < graph.fields.length; i++) { + var field = graph.fields[i] + var color = field.colour ? '#' + field.colour : defaultColors[i % defaultColors.length] + if (field.draw === 'AREA' || field.draw === 'STACK' || field.draw === 'AREASTACK') { + if (!field.draw.match(/STACK/) && (!graph.fields[i + 1] || graph.fields[i + 1].draw.match(/STACK/))) { + stackgroup += 1 + } + var trace = { + field_name: field.name, + name: field.label || field.name, + info: field.info || '', + line: {width: 0}, + fillcolor: color + 'c0', + hoverlabel: {bgcolor: color + 'c0'}, + stackgroup: 'stack' + stackgroup + } + traces.push(trace) + tracebyfield[field.name] = trace + tracebyfield[field.name + '.min'] = {} + tracebyfield[field.name + '.max'] = {} + } else if (field.draw) { + trace = { + field_name: field.name, + name: field.label || field.name, + info: field.info || '', + line: {color: color}, + hoverlabel: {bgcolor: color + 'c0'} + } + var minTrace = { + field_name: field.name, + showlegend: false, + hoverinfo: 'skip', + line: {width: 0} + } + var maxTrace = { + field_name: field.name, + showlegend: false, + hoverinfo: 'skip', + line: {width: 0}, + fill: 'tonexty', + fillcolor: color + '20' + } + traces.push(trace, minTrace, maxTrace) + tracebyfield[field.name] = trace + tracebyfield[field.name + '.min'] = minTrace + tracebyfield[field.name + '.max'] = maxTrace } - legendrow.innerHTML += '<td style="width: 30px;"><svg height="10" width="20"><line x1="0" y1="5" x2="20" y2="5" style="' + style + '" /></svg></td>' - legendrow.innerHTML += '<td><span title="' + htmlescape(trace.info) + '">' + htmlescape(trace.name) + '</span></td>' - legendrow.innerHTML += '<td></td><td></td><td></td>' - legend.getElementsByTagName('tbody')[0].appendChild(legendrow) - legendbyfield[trace.field_name] = legendrow - // handle showing/hiding the trace - legendrow.addEventListener('click', function () { - var visible = (trace.visible === false) - legendrow.style.opacity = visible ? 1 : 0.2 - plot.data.forEach(function (t) { - if (t.field_name === trace.field_name) { - t.visible = visible + } + // make placeholders for data in traces + Object.keys(tracebyfield).forEach(function (field) { + tracebyfield[field].x = [] + tracebyfield[field].y = [] + }) + // build the legend + var legendbyfield = {} + traces.slice().reverse().forEach(function (trace) { + if (trace.showlegend !== false) { + var legendrow = document.createElement('tr') + var style + if (trace.fillcolor) { + style = 'stroke: ' + trace.fillcolor + ';stroke-width:8' + } else { + style = 'stroke: ' + trace.line.color + ';stroke-width:2' + } + legendrow.innerHTML += '<td style="width: 30px;"><svg height="10" width="20"><line x1="0" y1="5" x2="20" y2="5" style="' + style + '" /></svg></td>' + legendrow.innerHTML += '<td><span title="' + htmlescape(trace.info) + '">' + htmlescape(trace.name) + '</span></td>' + legendrow.innerHTML += '<td></td><td></td><td></td>' + legend.getElementsByTagName('tbody')[0].appendChild(legendrow) + legendbyfield[trace.field_name] = legendrow + // handle showing/hiding the trace + legendrow.addEventListener('click', function () { + var visible = (trace.visible === false) + legendrow.style.opacity = visible ? 1 : 0.2 + plot.data.forEach(function (t) { + if (t.field_name === trace.field_name) { + t.visible = visible + } + }) + Plotly.redraw(plot) + }) + // highlight the trace by lowering the opacity of the others + legendrow.addEventListener('mouseover', function () { + if (plot.data) { + var vals = plot.data.map(t => t.field_name === trace.field_name ? 1 : 0.1) + Plotly.restyle(plot, 'opacity', vals) + vals = plot.data.map(function (t) { + if (t.showlegend === false) { + return t.fillcolor + } + return (t.fillcolor || '#ffffff').substring(0, 7) + (t.field_name === trace.field_name ? 'ff' : '30') + }) + Plotly.restyle(plot, 'fillcolor', vals) } }) - Plotly.redraw(plot) - }) - // highlight the trace by lowering the opacity of the others - legendrow.addEventListener('mouseover', function () { - var vals = plot.data.map(t => t.field_name === trace.field_name ? 1 : 0.1) - Plotly.restyle(plot, 'opacity', vals) - vals = plot.data.map(function (t) { + } + }) + // reset opacity after exiting the legend + legend.addEventListener('mouseout', function () { + if (plot.data) { + Plotly.restyle(plot, 'opacity', plot.data.map(t => 1)) + var vals = plot.data.map(function (t) { if (t.showlegend === false) { return t.fillcolor } - return (t.fillcolor || '#ffffff').substring(0, 7) + (t.field_name === trace.field_name ? 'ff' : '30') + return (t.fillcolor || '#ffffff').substring(0, 7) + 'c0' }) Plotly.restyle(plot, 'fillcolor', vals) - }) - } - }) - // reset opacity after exiting the legend - legend.addEventListener('mouseout', function () { - Plotly.restyle(plot, 'opacity', plot.data.map(t => 1)) - var vals = plot.data.map(function (t) { - if (t.showlegend === false) { - return t.fillcolor } - return (t.fillcolor || '#ffffff').substring(0, 7) + 'c0' }) - Plotly.restyle(plot, 'fillcolor', vals) - }) - // fetch the data and plot it - Plotly.d3.csv('data/' + graph.name, function (data) { - for (var i = 0; i < data.length; i++) { - var row = data[i] - Object.keys(tracebyfield).forEach(function (field) { - tracebyfield[field].x.push(row.time) - tracebyfield[field].y.push(Number(row[field])) - }) - } - plot.innerHTML = '' - Plotly.react(plot, traces, layout, config) - updateLegend(plot, tracebyfield, legendbyfield) - updatedata = true - // handle plot changes - plot.on('plotly_relayout', function (ed) { - updatedata = true; - // make zoom levels consistent across graphs - [].forEach.call(document.getElementsByClassName('myplot'), plot => { - if (plot.layout) { - const xaxis = plot.layout.xaxis - if ((ed['xaxis.range[0]'] && xaxis.range[0] !== ed['xaxis.range[0]']) || - (ed['xaxis.range[1]'] && xaxis.range[1] !== ed['xaxis.range[1]']) || - (ed['xaxis.autorange'] && ed['xaxis.autorange'] !== xaxis.autorange)) { - Plotly.relayout(plot, ed) - } + // fetch the data and plot it + // TODO: probably skip this initial load??? + Plotly.d3.csv('data/' + graph.name, function (data) { + for (var i = 0; i < data.length; i++) { + var row = data[i] + Object.keys(tracebyfield).forEach(function (field) { + tracebyfield[field].x.push(row.time) + tracebyfield[field].y.push(Number(row[field])) + }) + } + plot.innerHTML = '' + Plotly.react(plot, traces, layout, config) + updateLegend(plot, tracebyfield, legendbyfield) + updatedata = true + // handle plot changes + plot.on('plotly_relayout', function (ed) { + updatedata = true + if (ed['xaxis.range[0]'] && ed['xaxis.range[1]']) { + setDateRange(ed['xaxis.range[0]'], ed['xaxis.range[1]']) } + // update the legend values + updateLegend(plot, tracebyfield, legendbyfield) }) - // update the legend values - updateLegend(plot, tracebyfield, legendbyfield) }) - }) -} + } -// check if the axis match the data range and load more data as needed -function checkDataUpdates() { - try { - if (updatedata) { - updatedata = false; - // go over all plots - [].forEach.call(document.getElementsByClassName('myplot'), plot => { - if (plot && plot.layout) { + // check if the axis match the data range and load more data as needed + function checkDataUpdates() { + try { + if (updatedata) { + updatedata = false; + // go over all plots + [].forEach.call(document.getElementsByClassName('myplot'), plot => { + if (plot && plot.layout) { // range of the x axis - var [amin, amax] = plot.layout.xaxis.range - // range of the currently loaded data - var dmin = plot.data.map(t => t.x[0]).reduce((a, c) => a < c ? a : c) - var dmax = plot.data.map(t => t.x[t.x.length - 1]).reduce((a, c) => a > c ? a : c) - // range that we have marked as loaded - // (to avoid retrying to load data that isn't there) - if (!plot.lmin) { - plot.lmin = dmin - } - if (!plot.lmax) { - plot.lmax = dmax - } - // see if we need to load data before the currently loaded range - if (amin < plot.lmin) { - plot.lmin = amin - var url = 'data/' + plot.graph.name + '?start=' + amin.split('.')[0] + '&end=' + dmin.split('.')[0] - Plotly.d3.csv(url, function (data) { + var [amin, amax] = plot.layout.xaxis.range + // range of the currently loaded data + var dmin = plot.data.map(t => t.x[0]).reduce((a, c) => a < c ? a : c) + var dmax = plot.data.map(t => t.x[t.x.length - 1]).reduce((a, c) => a > c ? a : c) + // range that we have marked as loaded + // (to avoid retrying to load data that isn't there) + if (!plot.lmin) { + plot.lmin = dmin + } + if (!plot.lmax) { + plot.lmax = dmax + } + // see if we need to load data before the currently loaded range + if (amin < plot.lmin) { + plot.lmin = amin + var url = 'data/' + plot.graph.name + '?start=' + amin.split('.')[0] + '&end=' + dmin.split('.')[0] + Plotly.d3.csv(url, function (data) { // prepend new data - if (data) { - for (var i = data.length - 1; i >= 0; i--) { - var row = data[i] - var time = row.time - Object.keys(plot.tracebyfield).forEach(function (field) { - var trace = plot.tracebyfield[field] - if (time < trace.x[0]) { - trace.x.splice(0, 0, time) - trace.y.splice(0, 0, Number(row[field])) - } - }) + if (data) { + for (var i = data.length - 1; i >= 0; i--) { + var row = data[i] + var time = row.time + Object.keys(plot.tracebyfield).forEach(function (field) { + var trace = plot.tracebyfield[field] + if (time < trace.x[0]) { + trace.x.splice(0, 0, time) + trace.y.splice(0, 0, Number(row[field])) + } + }) + } + plot.layout.datarevision += 1 + Plotly.react(plot, plot.data, plot.layout) } - plot.layout.datarevision += 1 - Plotly.react(plot, plot.data, plot.layout) - } - }) - } - // see if we need to load data paste the currently loaded range - if (amax > plot.lmax) { - plot.lmax = amax - // load data from dmax to amax and append - url = 'data/' + plot.graph.name + '?start=' + dmax.split('.')[0] + '&end=' + amax.split('.')[0] - Plotly.d3.csv(url, function (data) { + }) + } + // see if we need to load data paste the currently loaded range + if (amax > plot.lmax) { + plot.lmax = amax + // load data from dmax to amax and append + url = 'data/' + plot.graph.name + '?start=' + dmax.split('.')[0] + '&end=' + amax.split('.')[0] + Plotly.d3.csv(url, function (data) { // append new data - if (data) { - for (var i = 0; i < data.length; i++) { - var row = data[i] - var time = row.time - Object.keys(plot.tracebyfield).forEach(function (field) { - var trace = plot.tracebyfield[field] - if (time > trace.x[trace.x.length - 1]) { - trace.x.push(time) - trace.y.push(Number(row[field])) - } - }) + if (data) { + for (var i = 0; i < data.length; i++) { + var row = data[i] + var time = row.time + Object.keys(plot.tracebyfield).forEach(function (field) { + var trace = plot.tracebyfield[field] + if (time > trace.x[trace.x.length - 1]) { + trace.x.push(time) + trace.y.push(Number(row[field])) + } + }) + } + plot.layout.datarevision += 1 + Plotly.react(plot, plot.data, plot.layout) } - plot.layout.datarevision += 1 - Plotly.react(plot, plot.data, plot.layout) - } - }) + }) + } } - } - }) + }) + } + } finally { + setTimeout(checkDataUpdates, 1000) } - } finally { - setTimeout(checkDataUpdates, 1000) } -} -setTimeout(checkDataUpdates, 1000) + setTimeout(checkDataUpdates, 1000) -// every minute check if there is any new data -function checkNewData() { - setTimeout(checkNewData, 60000); - [].forEach.call(document.getElementsByClassName('myplot'), plot => { - plot.lmax = undefined - }) - updatedata = true -} -setTimeout(checkNewData, 60000) + // every minute check if there is any new data + function checkNewData() { + setTimeout(checkNewData, 60000); + [].forEach.call(document.getElementsByClassName('myplot'), plot => { + plot.lmax = undefined + }) + updatedata = true + } + setTimeout(checkNewData, 60000) -function addGraph(graph, size = '150px') { - var clone = document.getElementById('template').firstElementChild.cloneNode(true) - var plot = clone.getElementsByClassName('myplot')[0] - var legend = clone.getElementsByClassName('mylegend')[0] - plot.graph = graph; - // update the graph info - [].forEach.call(clone.querySelectorAll('.graphinfo .dropdown-menu'), em => { - var info = '<tt class="dropdown-item">' + htmlescape(graph.group + '/' + graph.host) + '</tt>' - info += '<h3 class="dropdown-item">' + graph.graph_title + '</h3>' - if (graph.graph_info) { - info += '<div class="dropdown-item">' + htmlescape(graph.graph_info) + '</div>' - } - em.innerHTML = info - }); - // set the size changing actions - [].forEach.call(clone.getElementsByClassName('setsize'), button => { - button.addEventListener('click', function () { - if (button.getElementsByClassName('sizesm').length) { - plot.style.height = '150px' - legend.style.height = '150px' - } else if (button.getElementsByClassName('sizemd').length) { - plot.style.height = '200px' - legend.style.height = '200px' - } else if (button.getElementsByClassName('sizelg').length) { - plot.style.height = '250px' - legend.style.height = '250px' + function addGraph(graph, size = '150px') { + var clone = document.getElementById('template').firstElementChild.cloneNode(true) + var plot = clone.getElementsByClassName('myplot')[0] + var legend = clone.getElementsByClassName('mylegend')[0] + plot.graph = graph; + // update the graph info + [].forEach.call(clone.querySelectorAll('.graphinfo .dropdown-menu'), em => { + var info = '<tt class="dropdown-item">' + htmlescape(graph.group + '/' + graph.host) + '</tt>' + info += '<h3 class="dropdown-item">' + graph.graph_title + '</h3>' + if (graph.graph_info) { + info += '<div class="dropdown-item">' + htmlescape(graph.graph_info) + '</div>' } - Plotly.relayout(plot, {}) + em.innerHTML = info + }); + // set the size changing actions + [].forEach.call(clone.getElementsByClassName('setsize'), button => { + button.addEventListener('click', function () { + if (button.getElementsByClassName('sizesm').length) { + plot.style.height = '150px' + legend.style.height = '150px' + } else if (button.getElementsByClassName('sizemd').length) { + plot.style.height = '200px' + legend.style.height = '200px' + } else if (button.getElementsByClassName('sizelg').length) { + plot.style.height = '250px' + legend.style.height = '250px' + } + Plotly.relayout(plot, {}) + }) }) - }) - // set the wanted size - plot.style.height = size - legend.style.height = size; - // set the close action - [].forEach.call(clone.getElementsByClassName('closegraph'), button => { - button.addEventListener('click', function () { - Plotly.purge(plot) - clone.parentNode.removeChild(clone) + // set the wanted size + plot.style.height = size + legend.style.height = size; + // set the close action + [].forEach.call(clone.getElementsByClassName('closegraph'), button => { + button.addEventListener('click', function () { + Plotly.purge(plot) + clone.parentNode.removeChild(clone) + }) }) - }) - // load the graph data - loadGraph(plot, legend, graph) - // show the graph - document.getElementById('draggablelist').appendChild(clone) -} + // load the graph data + loadGraph(plot, legend, graph) + // show the graph + document.getElementById('draggablelist').appendChild(clone) + } -// update the select widget to be able to list the known graphs -function updateSelect(graphs) { + // update the select widget to be able to list the known graphs + function updateSelect(graphs) { // make lists of groups, hosts and categories - var hosts = {} - var categories = [] - for (var graph in graphs) { - var parts = graph.split('/') - if (!hosts[parts[0]]) { - hosts[parts[0]] = [] - } - if (hosts[parts[0]].indexOf(parts[1]) < 0) { - hosts[parts[0]].push(parts[1]) - } - if (graphs[graph].category && categories.indexOf(graphs[graph].category) < 0) { - categories.push(graphs[graph].category) - } - } - // update options in host selector - var groups = Object.keys(hosts) - groups.sort() - var hostselect = document.getElementById('hostselect') - for (var i = 0; i < groups.length; i++) { - var group = groups[i] - var groupelement = document.createElement('optgroup') - groupelement.setAttribute('label', group) - hosts[group].sort() - for (var j = 0; j < hosts[group].length; j++) { - var hostelement = document.createElement('option') - hostelement.setAttribute('value', group + '/' + hosts[group][j]) - hostelement.textContent = hosts[group][j] - groupelement.appendChild(hostelement) - } - hostselect.appendChild(groupelement) - } - // update options in category selector - categories.sort() - var categoryselect = document.getElementById('categoryselect') - for (i = 0; i < categories.length; i++) { - var categoryelement = document.createElement('option') - categoryelement.setAttribute('value', categories[i]) - categoryelement.textContent = categories[i] - categoryselect.appendChild(categoryelement) - } - // build list of graphs - var graphnames = Object.keys(graphs) - graphnames.sort() - var graphfilter = document.getElementById('graphfilter') - // handler for updating the choices in the graph select - function updateGraphList() { - var host = hostselect.options[hostselect.selectedIndex].value - var category = categoryselect.options[categoryselect.selectedIndex].value - var search = graphfilter.value.toLowerCase().split(' ') - var graphselect = document.getElementById('graphselect') - graphselect.innerHTML = '' - graphnames.forEach(function (graph) { - if (host && !graph.startsWith(host)) { - return + var hosts = {} + var categories = [] + for (var graph in graphs) { + var parts = graph.split('/') + if (!hosts[parts[0]]) { + hosts[parts[0]] = [] } - if (category && graphs[graph].category !== category) { - return + if (hosts[parts[0]].indexOf(parts[1]) < 0) { + hosts[parts[0]].push(parts[1]) } - var descripton = (graph + ' ' + graphs[graph].graph_title + ' ' + graphs[graph].category).toLowerCase() - if (search.some(x => !descripton.includes(x))) { - return + if (graphs[graph].category && categories.indexOf(graphs[graph].category) < 0) { + categories.push(graphs[graph].category) } - var graphelement = document.createElement('a') - graphelement.setAttribute('href', '#') - graphelement.setAttribute('class', 'list-group-item list-group-item-action') - graphelement.setAttribute('data-toggle', 'collapse') - graphelement.setAttribute('data-target', '#addgraph') - graphelement.textContent = graphs[graph].graph_title || graph.split('/')[2] - // add the host graph unless a host graph has been selected - if (!host) { - var hostelement = document.createElement('small') - hostelement.textContent = graph.split('/')[1] + ' / ' - graphelement.prepend(hostelement) + } + // update options in host selector + var groups = Object.keys(hosts) + groups.sort() + var hostselect = document.getElementById('hostselect') + for (var i = 0; i < groups.length; i++) { + var group = groups[i] + var groupelement = document.createElement('optgroup') + groupelement.setAttribute('label', group) + hosts[group].sort() + for (var j = 0; j < hosts[group].length; j++) { + var hostelement = document.createElement('option') + hostelement.setAttribute('value', group + '/' + hosts[group][j]) + hostelement.textContent = hosts[group][j] + groupelement.appendChild(hostelement) } - graphselect.appendChild(graphelement) - graphelement.addEventListener('click', function () { - addGraph(graphs[graph]) + hostselect.appendChild(groupelement) + } + // update options in category selector + categories.sort() + var categoryselect = document.getElementById('categoryselect') + for (i = 0; i < categories.length; i++) { + var categoryelement = document.createElement('option') + categoryelement.setAttribute('value', categories[i]) + categoryelement.textContent = categories[i] + categoryselect.appendChild(categoryelement) + } + // build list of graphs + var graphnames = Object.keys(graphs) + graphnames.sort() + var graphfilter = document.getElementById('graphfilter') + // handler for updating the choices in the graph select + function updateGraphList() { + var host = hostselect.options[hostselect.selectedIndex].value + var category = categoryselect.options[categoryselect.selectedIndex].value + var search = graphfilter.value.toLowerCase().split(' ') + var graphselect = document.getElementById('graphselect') + graphselect.innerHTML = '' + graphnames.forEach(function (graph) { + if (host && !graph.startsWith(host)) { + return + } + if (category && graphs[graph].category !== category) { + return + } + var descripton = (graph + ' ' + graphs[graph].graph_title + ' ' + graphs[graph].category).toLowerCase() + if (search.some(x => !descripton.includes(x))) { + return + } + var graphelement = document.createElement('a') + graphelement.setAttribute('href', '#') + graphelement.setAttribute('class', 'list-group-item list-group-item-action') + graphelement.setAttribute('data-toggle', 'collapse') + graphelement.setAttribute('data-target', '#addgraph') + graphelement.textContent = graphs[graph].graph_title || graph.split('/')[2] + // add the host graph unless a host graph has been selected + if (!host) { + var hostelement = document.createElement('small') + hostelement.textContent = graph.split('/')[1] + ' / ' + graphelement.prepend(hostelement) + } + graphselect.appendChild(graphelement) + graphelement.addEventListener('click', function () { + addGraph(graphs[graph]) + }) }) - }) - graphfilter.focus() - } - hostselect.addEventListener('change', updateGraphList, false) - categoryselect.addEventListener('change', updateGraphList, false) - graphfilter.addEventListener('input', updateGraphList, false) - document.getElementsByClassName('addgraph')[0].getElementsByClassName('btn')[0].addEventListener('click', function () { - setTimeout(function () { graphfilter.focus() - }, 100) - }, false) - updateGraphList() -} + } + hostselect.addEventListener('change', updateGraphList, false) + categoryselect.addEventListener('change', updateGraphList, false) + graphfilter.addEventListener('input', updateGraphList, false) + document.getElementsByClassName('addgraph')[0].getElementsByClassName('btn')[0].addEventListener('click', function () { + setTimeout(function () { + graphfilter.focus() + }, 100) + }, false) + updateGraphList() + } -// load all graphs -var request = new XMLHttpRequest() -request.overrideMimeType('application/json') -request.open('GET', 'graphs', true) -request.onreadystatechange = function () { - if (request.readyState === 4 && request.status === 200) { - var graphs = JSON.parse(request.responseText) - updateSelect(graphs) - document.getElementsByClassName('addgraph')[0].style.display = 'flex' - document.getElementsByClassName('loadingrow')[0].style.display = 'none' + // load all graphs + var request = new XMLHttpRequest() + request.overrideMimeType('application/json') + request.open('GET', 'graphs', true) + request.onreadystatechange = function () { + if (request.readyState === 4 && request.status === 200) { + var graphs = JSON.parse(request.responseText) + updateSelect(graphs) + document.getElementsByClassName('addgraph')[0].style.display = 'flex' + document.getElementsByClassName('loadingrow')[0].style.display = 'none' + } } -} -request.send(null) + request.send(null) +}) |