define(["c3", "d3"], function (c3, d3) { var charts = function (node, config) { this.node = node this.chartConfig = config this.chart = null this.zoomConfig = { "levels": [ {"label": "8h", "from": 8 * 3600, "interval": 15 * 60} /*{"label": "24h", "from": "26h", "interval": "1h"}, {"label": "1m", "from": "1mon", "interval": "1d"}, {"label": "1y", "from": 31536000, "interval": 2628000}*/ ] } this.zoomLevel = 0 this.c3Config = { "size": { "height": 240 }, padding: { bottom: 30 }, "legend": { "item": { "onclick": function (id) { this.api.hide() this.api.show(id) } } }, "tooltip": { "format": { "value": this.c3FormatToolTip.bind(this) } }, "axis": { "x": { "type": "timeseries", "tick": { "format": this.c3FormatXAxis.bind(this), "rotate": -45 } }, "y": { "min": 0, "padding": { "bottom": 0 } } } } this.cache = [] this.init() } charts.prototype = { init: function () { // Workaround for endless loop bug if (this.c3Config.axis.x.tick && this.c3Config.axis.x.tick.format && typeof this.c3Config.axis.x.tick.format === "function") { if (this.c3Config.axis.x.tick.format && !this.c3Config.axis.x.tick._format) this.c3Config.axis.x.tick._format = this.c3Config.axis.x.tick.format this.c3Config.axis.x.tick.format = function (val) { return this.c3Config.axis.x.tick._format(val) }.bind(this) } // Configure metrics this.c3Config.data = { "keys": { "x": "time", "value": this.chartConfig.metrics.map(function (metric) { return metric.id }) }, "colors": this.chartConfig.metrics.reduce(function (collector, metric) { collector[metric.id] = metric.color return collector }, {}), "names": this.chartConfig.metrics.reduce(function (collector, metric) { collector[metric.id] = metric.label return collector }, {}), "hide": this.chartConfig.metrics.map(function (metric) { return metric.id }).filter(function (id) { return id !== this.chartConfig.defaultMetric }.bind(this)) } }, render: function () { var div = document.createElement("div") div.classList.add("chart") var h4 = document.createElement("h4") h4.textContent = this.chartConfig.name div.appendChild(h4) // Render chart this.load(function (data) { div.appendChild(this.renderChart(data)) // Render zoom controls if (this.zoomConfig.levels.length > 0) div.appendChild(this.renderZoomControls()) }.bind(this)) return div }, renderChart: function (data) { this.c3Config.data.json = data this.chart = c3.generate(this.c3Config) return this.chart.element }, updateChart: function (data) { this.c3Config.data.json = data this.chart.load(this.c3Config.data) }, renderZoomControls: function () { // Draw zoom controls var zoomDiv = document.createElement("div") zoomDiv.classList.add("zoom-buttons") var zoomButtons = [] this.zoomConfig.levels.forEach(function (v, level) { var btn = document.createElement("button") btn.classList.add("zoom-button") btn.setAttribute("data-zoom-level", level) if (level === this.zoomLevel) btn.classList.add("active") btn.onclick = function () { if (level !== this.zoomLevel) { zoomButtons.forEach(function (v, k) { if (level !== k) v.classList.remove("active") else v.classList.add("active") }) this.setZoomLevel(level) } }.bind(this) btn.textContent = v.label zoomButtons[level] = btn zoomDiv.appendChild(btn) }.bind(this)) return zoomDiv }, setZoomLevel: function (level) { if (level !== this.zoomLevel) { this.zoomLevel = level this.load(this.updateChart.bind(this)) } }, load: function (callback) { if (this.cache[this.zoomLevel]) callback(this.cache[this.zoomLevel]) else { var urls = [] var id = this.node.nodeinfo.node_id var unixStamp = Math.floor(Date.now() / 1000) var zoomConfig = this.zoomConfig.levels[this.zoomLevel] this.chartConfig.metrics.forEach(function(metric) { var parameters = [ "start=" + (unixStamp - zoomConfig.from), "end=" + unixStamp, "step=" + zoomConfig.interval, "query=" + metric.query.replace("{{NODE_ID}}", id) ] var url = this.chartConfig.url + "?" + parameters.join("&") urls.push(url) }.bind(this)) Promise.all(urls.map(getJSON)).then(function (data) { this.cache[this.zoomLevel] = this.parse(data) callback(this.cache[this.zoomLevel]) }.bind(this)) } }, parse: function (results) { var data = [] results[0].data.result[0].values.forEach(function (tp) { var time = {"time": new Date(tp[0] * 1000)} results.forEach(function(result) { var metric = this.chartConfig.metrics[results.indexOf(result)] var index = results[0].data.result[0].values.indexOf(tp) time[metric.id] = this.formatValue(metric.id, result.data.result[0].values[index][1]) }.bind(this)) data.push(time) }.bind(this)) return data }, c3FormatToolTip: function (d, ratio, id) { switch (id) { case "uptime": return d.toFixed(1) + " Tage" default: return d } }, c3FormatXAxis: function (d) { var pad = function (number, pad) { var N = Math.pow(10, pad) return number < N ? ("" + (N + number)).slice(1) : "" + number } switch (this.zoomLevel) { case 0: // 8h case 1: // 24h return pad(d.getHours(), 2) + ":" + pad(d.getMinutes(), 2) case 2: // 1m return pad(d.getDate(), 2) + "." + pad(d.getMonth() + 1, 2) case 3: // 1y return ["Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"][d.getMonth()] default: break } }, formatValue: function (id, value) { switch (id) { case "loadavg": return (d3.format(".2r")(value)) case "clientcount": return (Math.ceil(value)) case "uptime": return (value / 86400) default: return value } } } return charts })