hopglass/lib/infobox/charts.js

256 lines
6.9 KiB
JavaScript
Raw Normal View History

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
})