lib/visage-app/public/javascripts/graph.js in visage-app-1.0.0 vs lib/visage-app/public/javascripts/graph.js in visage-app-2.0.0

- old
+ new

@@ -119,18 +119,16 @@ var label = value, unit = ''; break } - var rounded = label.round(precision) - - return rounded + unit + return label.format({decimals: precision, suffix: unit}) } function formatDate(d) { - var datetime = new Date(d * 1000) - return datetime.format("%Y-%m-%d %H:%M:%S UTC%T") + var datetime = new Date(d) + return datetime.format("%Y-%m-%d %H:%M:%S UTC%z") } function formatPluginName(name) { if (name.test(/^curl_json/)) { name = name.split('-')[1].replace(/(-|_)/, ' '); @@ -138,48 +136,46 @@ return name } /* - * visageBase() + * VisageBase() * * Base class for fetching data and setting graph options. * Should be used by other classes to build specialised graphing behaviour. * */ -var visageBase = new Class({ +var VisageBase = new Class({ Implements: [ Options, Events ], options: { secureJSON: false, httpMethod: 'get', live: false }, initialize: function(element, host, plugin, options) { - this.parentElement = element - this.options.host = host - this.options.plugin = plugin - this.setOptions(options) + this.parentElement = element; + this.options.host = host; + this.options.plugin = plugin; + this.query = window.location.search.slice(1).parseQueryString(); + this.options = Object.merge(this.options, this.query); - var data = new Hash() - if($chk(this.options.start)) { - data.set('start', this.options.start) - } - if($chk(this.options.finish)) { - data.set('finish', this.options.finish) - } + this.setOptions(options); - this.requestData = data; + this.requestData = new Object(); + this.requestData.start = this.options.start; + this.requestData.finish = this.options.finish; + this.getData(); // calls graphData }, dataURL: function() { - var url = ['data', this.options.host, this.options.plugin] + var url = ['data', this.options.host, this.options.plugin]; // if the data exists on another host (useful for embedding) - if ($defined(this.options.baseurl)) { + if (this.options.baseurl) { url.unshift(this.options.baseurl.replace(/\/$/, '')) } // for specific plugin instances - if ($chk(this.options.pluginInstance)) { + if (this.options.pluginInstance) { url.push(this.options.pluginInstance) } // if no url is specified if (!url[0].match(/http\:\/\//)) { url[0] = '/' + url[0] @@ -200,34 +196,34 @@ }.bind(this) }); this.request.send(); }, - graphName: function() { + title: function() { if ($chk(this.options.name)) { - var name = this.options.name + var title = this.options.name } else { - var name = [ formatPluginName(this.options.plugin), - 'on', - this.options.host ].join(' ') + var title = [ formatPluginName(this.options.plugin), + 'on', + this.options.host ].join(' ') } - return name + return title }, }); /* - * visageGraph() + * VisageGraph() * * General purpose graph for rendering data from a single plugin * with multiple plugin instances. * - * Builds upon visageBase(). + * Builds upon VisageBase(). * */ -var visageGraph = new Class({ - Extends: visageBase, +var VisageGraph = new Class({ + Extends: VisageBase, Implements: Chain, // assemble data to graph, then draw it graphData: function(data) { this.response = data this.buildDataStructures() @@ -264,24 +260,25 @@ var series = this.series = [] var host = this.options.host var plugin = this.options.plugin var data = data ? data : this.response - $each(data[host][plugin], function(instance, iname) { - $each(instance, function(metric, mname) { + $each(data[host][plugin], function(instance, instanceName) { + $each(instance, function(metric, metricName) { var start = metric.start, finish = metric.finish, interval = (finish - start) / metric.data.length; var data = metric.data.map(function(value, index) { - var x = start + index * interval, + var x = (start + index * interval) * 1000, y = value; + return [ x, y ]; }); var set = { - name: [ host, plugin, iname, mname ], + name: [ host, plugin, instanceName, metricName ], data: data, }; series.push(set) }, this); @@ -316,11 +313,11 @@ return {'min': min, 'max': max}; }, drawChart: function() { var series = this.series, - title = this.graphName(), + title = this.title(), element = this.parentElement, ytitle = formatPluginName(this.options.plugin), min, max; @@ -329,129 +326,145 @@ meta = this.getSeriesMinMax(series); var min = meta.min, max = meta.max; this.chart = new Highcharts.Chart({ + series: series, chart: { - renderTo: element, - defaultSeriesType: 'line', - marginRight: 200, - marginBottom: 25, - zoomType: 'xy', - height: 300, + renderTo: element, + type: 'line', + marginRight: 0, + marginBottom: 60, + zoomType: 'xy', + height: 300, events: { load: function(e) { setInterval(function() { if (this.options.live) { - var data = { 'start': this.lastFinish, - 'finish': this.lastFinish + 10, + var data = { 'start': this.lastFinish / 1000, + 'finish': this.lastFinish / 1000 + 10, 'live': true }; this.requestData = data; this.getData() } }.bind(this), 10000); }.bind(this) } }, title: { - text: title, - style: { - fontSize: '20px', - fontWeight: 'bold', - color: "#333333" - } + text: title, + style: { + 'fontSize': '18px', + 'fontWeight': 'bold', + 'color': '#333333', + 'font-family': 'Bitstream Vera Sans, Helvetica Neue, sans-serif', + } }, +/* + colors: [ +'#204a87', '#4e9a06', '#cc0000', '#5c3566', '#f57900', '#e9b96e', '#ad7fa8', '#888a85', '#8ae234', '#75507b', '#c17d11', '#729fcf', '#73d216', '#ef2929', '#edd400', '#8f5902', '#555753', '#fce94f', '#2e3436', '#babdb6', '#3465a4', '#a40000', '#c4a000', '#ce5c00', '#d3d7cf', '#fcaf3e', '#eeeeec', + ], +*/ xAxis: { - type: 'datetime', - labels: { - y: 20, - formatter: function() { - var d = new Date(this.value * 1000) - return d.format("%H:%M") - } - }, title: { text: null + }, + lineColor: "#aaa", + tickColor: "#aaa", + type: 'datetime', + dateTimeLabelFormats: { + second: '%H:%M:%S', + minute: '%H:%M', + hour: '%H:%M', + day: '%d/%m', + week: '%d/%m', + month: '%m/%Y', + year: '%Y' } + }, yAxis: { - title: { - text: ytitle - }, - startOnTick: false, - minPadding: 0.065, - max: max, - endOnTick: true, - labels: { + title: { + text: null + }, + startOnTick: false, + minPadding: 0.065, + endOnTick: false, + gridLineColor: "#dddddd", + labels: { formatter: function() { - var precision = min - max < 1000 ? 2 : 0, + var precision = 1, value = formatValue(this.value, { 'precision': precision, 'min': min, 'max': max }); return value } - } + } }, plotOptions: { - series: { - shadow: false, - marker: { - enabled: false, - stacking: 'normal', - states: { - hover: { - enabled: true + series: { + shadow: false, + lineWidth: 1, + marker: { + enabled: false, + states: { + hover: { + enabled: true, + radius: 4, + }, + }, + }, + states: { + hover: { + enabled: true, + lineWidth: 1, + }, } - } } - } }, tooltip: { - formatter: function() { - var tip; - tip = '<strong>' - tip += formatSeriesLabel(this.series.name).trim() - tip += '</strong>' + ' -> ' - tip += '<span style="font-family: monospace; font-size: 14px;">' - tip += formatValue(this.y, { 'precision': 2, 'min': min, 'max': max }) - tip += '<span style="font-size: 9px; color: #777">' - tip += ' (' + this.y + ')' - tip += '</span>' - tip += '</span>' - tip += '<br/>' - tip += '<span style="font-family: monospace">' - tip += formatDate(this.x) - tip += '</span>' + formatter: function() { + var tip; + tip = '<strong>' + tip += formatSeriesLabel(this.series.name).trim() + tip += '</strong>' + ' -> ' + tip += '<span style="font-family: monospace; font-size: 14px;">' + tip += formatValue(this.y, { 'precision': 2, 'min': min, 'max': max }) + tip += '<span style="font-size: 9px; color: #777">' + tip += ' (' + this.y + ')' + tip += '</span>' + tip += '</span>' + tip += '<br/>' + tip += '<span style="font-family: monospace">' + tip += formatDate(this.x) + tip += '</span>' - return tip - } + return tip + } }, legend: { - layout: 'vertical', - align: 'right', + layout: 'horizontal', + align: 'center', verticalAlign: 'top', - x: -10, - y: 60, + y: 275, borderWidth: 0, - itemWidth: 186, labelFormatter: function() { return formatSeriesLabel(this.name) }, itemStyle: { cursor: 'pointer', color: '#333333' }, itemHoverStyle: { - color: '#777777' + color: '#888' } }, - series: series, credits: { - enabled: false + enabled: false } }); this.buildDateSelector(); }, @@ -460,57 +473,75 @@ * container * \ * - form * \ * - select - * | \ - * | - option - * | | - * | - option - * | - * - submit + * \ + * - option + * | + * - option */ var currentDate = new Date; var currentUnixTime = parseInt(currentDate.getTime() / 1000); var container = $(this.parentElement); - var form = new Element('form', { + var form = this.form = new Element('form', { 'method': 'get', + 'styles': { + 'text-align': 'right', + }, 'events': { - 'submit': function(e, foo) { - e.stop(); - e.target.getElement('select').getSelected().each(function(option) { - value = parseInt(option.value.split('=')[1]) - data = { 'start': value } - }); - this.requestData = data; - + 'submit': function(e) { + this.requestData = this.form.getElement('select').getSelected()[0].value.parseQueryString() /* Draw everything again. */ this.getData(); }.bind(this) } }); - var select = new Element('select', { 'class': 'date timescale' }); - var timescales = new Hash({ 'hour': 1, '2 hours': 2, '6 hours': 6, '12 hours': 12, - 'day': 24, '2 days': 48, '3 days': 72, - 'week': 168, '2 weeks': 336, 'month': 672 }); + /* Select dropdown */ + var select = this.select = new Element('select', { + 'class': 'date timescale', + 'styles': { + 'margin-bottom': '3px', + 'border': '1px solid #aaa', + }, + 'events': { + 'change': function(e) { + e.target.form.fireEvent('submit', e) + } + } + }); + + /* Timescales available in the dropdown */ + var timescales = new Hash({ '1 hour': 1, + '2 hours': 2, + '6 hours': 6, + '12 hours': 12, + '24 hours': 24, + '3 days': 72, + '7 days': 168, + '2 weeks': 336, + '1 month': 774, + '3 month': 2322, + '6 months': 4368, + '1 year': 8760, + '2 years': 17520 }); + timescales.each(function(hour, label) { var current = this.currentTimePeriod == 'last {label}'.substitute({'label': label }); - var value = "start={start}".substitute({'start': currentUnixTime - (hour * 3600)}); - var html = 'last {label}'.substitute({'label': label }); + var value = "start={start}".substitute({'start': currentUnixTime - (hour * 3600)}); + var html = 'last {label}'.substitute({'label': label }); var option = new Element('option', { - html: html, - value: value, - selected: (current ? 'selected' : '') + 'html': html, + 'value': value, + 'selected': (current ? 'selected' : ''), }); select.grab(option) }); - var submit = new Element('input', { 'type': 'submit', 'value': 'show' }); - var liveToggler = new Element('input', { 'type': 'checkbox', 'id': this.parentElement + '-live', 'name': 'live', 'checked': this.options.live, @@ -518,33 +549,34 @@ 'click': function() { this.options.live = !this.options.live }.bind(this) }, 'styles': { - 'margin-left': '4px', + 'margin-right': '4px', 'cursor': 'pointer' } }); var liveLabel = new Element('label', { 'for': this.parentElement + '-live', 'html': 'Live', 'styles': { 'font-family': 'sans-serif', 'font-size': '11px', - 'margin-left': '8px', + 'margin-right': '8px', 'cursor': 'pointer' } }); var exportLink = new Element('a', { 'href': this.dataURL(), 'html': 'Export data', 'styles': { - 'font-family': 'sans-serif', - 'font-size': '11px', - 'margin-left': '8px', + 'font-family': 'sans-serif', + 'font-size': '11px', + 'margin-right': '8px', + 'color': '#2F5A92', }, 'events': { 'mouseover': function(e) { e.stop(); var url = e.target.get('href'), @@ -559,20 +591,25 @@ e.target.set('href', url) }.bind(this) } }); - form.grab(select) - form.grab(submit) + form.grab(exportLink) form.grab(liveToggler) form.grab(liveLabel) - form.grab(exportLink) + form.grab(select) container.grab(form, 'top') }, + setTimePeriodTo: function(selected) { + var option = this.select.getElements('option').filter(function(opt) { + return opt.text == selected.text + })[0]; + option.set('selected', 'selected'); - + this.form.fireEvent('submit') + } }); // buildEmbedder: function() { // var pre = new Element('textarea', { // 'id': 'embedder', @@ -608,10 +645,10 @@ // }, // embedCode: function() { // baseurl = "{protocol}//{host}".substitute({'host': window.location.host, 'protocol': window.location.protocol}); // code = "<script src='{baseurl}/javascripts/visage.js' type='text/javascript'></script>".substitute({'baseurl': baseurl}); // code += "<div id='graph'></div>" -// code += "<script type='text/javascript'>window.addEvent('domready', function() { var graph = new visageGraph('graph', '{host}', '{plugin}', ".substitute({'host': this.options.host, 'plugin': this.options.plugin}); +// code += "<script type='text/javascript'>window.addEvent('domready', function() { var graph = new VisageGraph('graph', '{host}', '{plugin}', ".substitute({'host': this.options.host, 'plugin': this.options.plugin}); // code += "{" // code += "width: 900, height: 220, gridWidth: 800, gridHeight: 200, baseurl: '{baseurl}'".substitute({'baseurl': baseurl}); // code += "}); });</script>" // return code.replace('<', '&lt;').replace('>', '&gt;') // },