- data is fetched from a binary RRD file and converted to JavaScript object
- charts are generated with re-usable chart object to reduce duplication of code
Last active
September 2, 2018 19:54
-
-
Save tdack/23e2a301eed5ee605caf to your computer and use it in GitHub Desktop.
Re-usable D3 chart to graph RRD data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* BinaryFile over XMLHttpRequest | |
* Part of the javascriptRRD package | |
* Copyright (c) 2009 Frank Wuerthwein, [email protected] | |
* MIT License [http://www.opensource.org/licenses/mit-license.php] | |
* | |
* Original repository: http://javascriptrrd.sourceforge.net/ | |
* | |
* Based on: | |
* Binary Ajax 0.1.5 | |
* Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/ | |
* MIT License [http://www.opensource.org/licenses/mit-license.php] | |
*/ | |
// ============================================================ | |
// Exception class | |
function InvalidBinaryFile(msg) { | |
this.message=msg; | |
this.name="Invalid BinaryFile"; | |
} | |
// pretty print | |
InvalidBinaryFile.prototype.toString = function() { | |
return this.name + ': "' + this.message + '"'; | |
} | |
// ===================================================================== | |
// BinaryFile class | |
// Allows access to element inside a binary stream | |
function BinaryFile(strData, iDataOffset, iDataLength) { | |
var data = strData; | |
var dataOffset = iDataOffset || 0; | |
var dataLength = 0; | |
// added | |
var doubleMantExpHi=Math.pow(2,-28); | |
var doubleMantExpLo=Math.pow(2,-52); | |
var doubleMantExpFast=Math.pow(2,-20); | |
var switch_endian = false; | |
var LastModified; | |
this.getLastModified = function(){ | |
return LastModified; | |
} | |
this.setLastModified = function(iLastModified){ | |
this.LastModified = new Date(iLastModified); | |
} | |
this.getRawData = function() { | |
return data; | |
} | |
if (typeof strData == "string") { | |
dataLength = iDataLength || data.length; | |
this.getByteAt = function(iOffset) { | |
return data.charCodeAt(iOffset + dataOffset) & 0xFF; | |
} | |
} else if (typeof strData == "unknown") { | |
dataLength = iDataLength || IEBinary_getLength(data); | |
this.getByteAt = function(iOffset) { | |
return IEBinary_getByteAt(data, iOffset + dataOffset); | |
} | |
} else { | |
throw new InvalidBinaryFile("Unsupported type " + (typeof strData)); | |
} | |
this.getEndianByteAt = function(iOffset,width,delta) { | |
if (this.switch_endian) | |
return this.getByteAt(iOffset+width-delta-1); | |
else | |
return this.getByteAt(iOffset+delta); | |
} | |
this.getLength = function() { | |
return dataLength; | |
} | |
this.getSByteAt = function(iOffset) { | |
var iByte = this.getByteAt(iOffset); | |
if (iByte > 127) | |
return iByte - 256; | |
else | |
return iByte; | |
} | |
this.getShortAt = function(iOffset) { | |
var iShort = (this.getEndianByteAt(iOffset,2,1) << 8) + this.getEndianByteAt(iOffset,2,0) | |
if (iShort < 0) iShort += 65536; | |
return iShort; | |
} | |
this.getSShortAt = function(iOffset) { | |
var iUShort = this.getShortAt(iOffset); | |
if (iUShort > 32767) | |
return iUShort - 65536; | |
else | |
return iUShort; | |
} | |
this.getLongAt = function(iOffset) { | |
var iByte1 = this.getEndianByteAt(iOffset,4,0), | |
iByte2 = this.getEndianByteAt(iOffset,4,1), | |
iByte3 = this.getEndianByteAt(iOffset,4,2), | |
iByte4 = this.getEndianByteAt(iOffset,4,3); | |
var iLong = (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1; | |
if (iLong < 0) iLong += 4294967296; | |
return iLong; | |
} | |
this.getSLongAt = function(iOffset) { | |
var iULong = this.getLongAt(iOffset); | |
if (iULong > 2147483647) | |
return iULong - 4294967296; | |
else | |
return iULong; | |
} | |
this.getStringAt = function(iOffset, iLength) { | |
var aStr = []; | |
for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) { | |
aStr[j] = String.fromCharCode(this.getByteAt(i)); | |
} | |
return aStr.join(""); | |
} | |
// Added | |
this.getCStringAt = function(iOffset, iMaxLength) { | |
var aStr = []; | |
for (var i=iOffset,j=0;(i<iOffset+iMaxLength) && (this.getByteAt(i)>0);i++,j++) { | |
aStr[j] = String.fromCharCode(this.getByteAt(i)); | |
} | |
return aStr.join(""); | |
} | |
// Added | |
this.getDoubleAt = function(iOffset) { | |
var iByte1 = this.getEndianByteAt(iOffset,8,0), | |
iByte2 = this.getEndianByteAt(iOffset,8,1), | |
iByte3 = this.getEndianByteAt(iOffset,8,2), | |
iByte4 = this.getEndianByteAt(iOffset,8,3), | |
iByte5 = this.getEndianByteAt(iOffset,8,4), | |
iByte6 = this.getEndianByteAt(iOffset,8,5), | |
iByte7 = this.getEndianByteAt(iOffset,8,6), | |
iByte8 = this.getEndianByteAt(iOffset,8,7); | |
var iSign=iByte8 >> 7; | |
var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4); | |
var iMantHi=((((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5) << 8) + iByte4; | |
var iMantLo=((((iByte3) << 8) + iByte2) << 8) + iByte1; | |
if (iExpRaw==0) return 0.0; | |
if (iExpRaw==0x7ff) return undefined; | |
var iExp=(iExpRaw & 0x7FF)-1023; | |
var dDouble = ((iSign==1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMantLo*doubleMantExpLo + iMantHi*doubleMantExpHi); | |
return dDouble; | |
} | |
// added | |
// Extracts only 4 bytes out of 8, loosing in precision (20 bit mantissa) | |
this.getFastDoubleAt = function(iOffset) { | |
var iByte5 = this.getEndianByteAt(iOffset,8,4), | |
iByte6 = this.getEndianByteAt(iOffset,8,5), | |
iByte7 = this.getEndianByteAt(iOffset,8,6), | |
iByte8 = this.getEndianByteAt(iOffset,8,7); | |
var iSign=iByte8 >> 7; | |
var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4); | |
var iMant=((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5; | |
if (iExpRaw==0) return 0.0; | |
if (iExpRaw==0x7ff) return undefined; | |
var iExp=(iExpRaw & 0x7FF)-1023; | |
var dDouble = ((iSign==1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMant*doubleMantExpFast); | |
return dDouble; | |
} | |
this.getCharAt = function(iOffset) { | |
return String.fromCharCode(this.getByteAt(iOffset)); | |
} | |
} | |
document.write( | |
"<script type='text/vbscript'>\r\n" | |
+ "Function IEBinary_getByteAt(strBinary, iOffset)\r\n" | |
+ " IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n" | |
+ "End Function\r\n" | |
+ "Function IEBinary_getLength(strBinary)\r\n" | |
+ " IEBinary_getLength = LenB(strBinary)\r\n" | |
+ "End Function\r\n" | |
+ "</script>\r\n" | |
); | |
// =============================================================== | |
// Load a binary file from the specified URL | |
// Will return an object of type BinaryFile | |
function FetchBinaryURL(url) { | |
var request = new XMLHttpRequest(); | |
request.open("GET", url,false); | |
try { | |
request.overrideMimeType('application/octet-stream'); | |
} catch (err) { | |
// ignore any error, just to make both FF and IE work | |
} | |
request.send(null); | |
var response=this.responseText; | |
try { | |
// for older IE versions, the value in responseText is not usable | |
if (IEBinary_getLength(this.responseBody)>0) { | |
// will get here only for older verson of IE | |
response=this.responseBody; | |
} | |
} catch (err) { | |
// not IE, do nothing | |
} | |
var bf=new BinaryFile(response); | |
bf.setLastModified(this.getResponseHeader("Last-Modified")) | |
return bf; | |
} | |
// =============================================================== | |
// Asyncronously load a binary file from the specified URL | |
// | |
// callback must be a function with one or two arguments: | |
// - bf = an object of type BinaryFile | |
// - optional argument object (used only if callback_arg not undefined) | |
function FetchBinaryURLAsync(url, callback, callback_arg) { | |
var callback_wrapper = function() { | |
if(this.readyState == 4) { | |
var response=this.responseText; | |
try { | |
// for older IE versions, the value in responseText is not usable | |
if (IEBinary_getLength(this.responseBody)>0) { | |
// will get here only for older verson of IE | |
response=this.responseBody; | |
} | |
} catch (err) { | |
// not IE, do nothing | |
} | |
console.log(response); | |
var bf=new BinaryFile(response); | |
console.log(bf); | |
bf.setLastModified(this.getResponseHeader("Last-Modified")) | |
if (callback_arg!=null) { | |
callback(bf,callback_arg); | |
} else { | |
callback(bf); | |
} | |
} | |
} | |
var request = new XMLHttpRequest(); | |
request.onreadystatechange = callback_wrapper; | |
request.open("GET", url,true); | |
try { | |
request.overrideMimeType('application/octet-stream'); | |
} catch (err) { | |
// ignore any error, just to make both FF and IE work | |
} | |
request.send(null); | |
return request | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function multiLineChart() { | |
var margin = { | |
top: 20, | |
right: 85, | |
bottom: 60, | |
left: 40 | |
}, | |
width = 900, | |
height = 500, | |
title = "Graph", | |
options = { | |
showVoronoi: false, // add a voronoi overlay for tool tips | |
showLegend: false, // display a legend | |
legend: "name", // field to use for legend, or array of values | |
valueSeries: "values", // fieled to use to get values for each line | |
valueUnits: "", // units to display on tool tips | |
rollOverDay: null, // display a marker on this day every month | |
limitLines: [], // display an horizontal line | |
invertDS: "" // invert data sources ending in this value | |
}, | |
xValue = function(d) { return d.date; }, | |
yValue = function(d) { return d.value; }, | |
xScale = d3.time.scale(), | |
yScale = d3.scale.linear(), | |
colours = d3.scale.category10(), | |
xTicks = 10, | |
yTicks = 6, | |
valueFormat = d3.format(".2f"), | |
dateFormat = d3.time.format("%H:%M"); | |
var toType = function(obj) { | |
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() | |
} | |
function chart(selection) { | |
var | |
xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(xTicks), | |
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(yTicks).tickFormat(valueFormat), | |
xGrid = d3.svg.axis().scale(xScale).orient("bottom").ticks(xTicks).tickSize(height - (margin.bottom + margin.top), 0).tickFormat(""), | |
yGrid = d3.svg.axis().scale(yScale).orient("left").ticks(yTicks).tickSize(-width + (margin.right + margin.left), 0).tickFormat(""), | |
line = d3.svg.line().interpolate("monotone").defined(function(d) { return yValue(d) != null; }).x(X).y(Y), | |
voronoi = d3.geom.voronoi().x(X).y(Y) | |
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]), | |
rollOverTicks; | |
selection.each(function(data){ | |
// Update the x-scale | |
xScale | |
.domain([ d3.min(data, function(s){ return d3.min(s[options.valueSeries], function(d){ return +xValue(d);} ) }), | |
d3.max(data, function(s){ return d3.max(s[options.valueSeries], function(d){ return +xValue(d);} ) })]) | |
.range([0, width - margin.left - margin.right]); | |
var xDomain = xScale.domain(); | |
// Invert values in data set if required | |
if (options.invertDS.length) { | |
data.forEach(function(d){ | |
var re = new RegExp(options.invertDS + '$'); | |
if (re.test(d.name)) { | |
d.values.forEach(function(v){ | |
v.value *= -1; | |
}); | |
} | |
}); | |
} | |
// Update the y-scale | |
yScale | |
.domain([ d3.min(data, function(s){ return d3.min(s[options.valueSeries], function(d){ return +yValue(d);} ) }), | |
d3.max(data, function(s){ return d3.max(s[options.valueSeries], function(d){ return +yValue(d);} ) })]).nice() | |
.range([height - margin.top - margin.bottom, 0]); | |
// Select the svg element if it exists | |
var svg = d3.select(this).selectAll("svg") | |
.data([data]); | |
// Otherwise create the elements | |
var gEnter = svg.enter().append("svg").append("g"); | |
gEnter.append("g") // Group for shading weekends | |
.attr("class", "grid grid--weekends"); | |
gEnter.append("g") // Grid | |
.attr("class", "grid grid--x"); | |
gEnter.append("g") | |
.attr("class", "grid grid--y"); | |
gEnter.append("g") // Axis | |
.attr("class", "axis axis--x"); | |
gEnter.append("g") | |
.attr("class", "axis axis--y"); | |
gEnter.append("g") // | |
.attr("class", "title"); | |
if (options.showLegend){ | |
gEnter.append("g") | |
.attr("class", "legend"); | |
} | |
gEnter.append("g") // Group to hold the lines | |
.attr("class", "line-group"); | |
// Marker at roll over date every month | |
if (options.rollOverDay != null) { | |
gEnter.append("g") | |
.attr("class", "grid grid--rollover"); | |
rollOverTicks = d3.svg.axis() | |
.scale(xScale) | |
.orient("bottom") | |
.ticks(xTicks) | |
.tickSize(height - (margin.bottom + margin.top), 0) | |
.tickFormat("") | |
.tickValues( | |
d3.time.days(xDomain[0], xDomain[1]) | |
.filter(function(d){ return (d.getDate() == options.rollOverDay); }) | |
); | |
} | |
if (options.limitLines.length > 0) { | |
gEnter.append("g") | |
.attr("class", "grid grid--limit-lines"); | |
} | |
if (options.showVoronoi){ | |
gEnter.append("g") | |
.attr("class", "voronoi") | |
.attr("id", "voronoi"); | |
} | |
var focus = gEnter.append("g") | |
.attr("transform", "translate(-100,-100)") | |
.attr("class", "focus"); | |
focus.append("circle") | |
.attr("r", 3.5); | |
focus.append("line") | |
.attr("class", "x") | |
.style("stroke", "green") | |
.style("stroke-dasharray", "3,3") | |
.style("opacity", 0.75) | |
.attr("y1", 0) | |
.attr("y2", height - (margin.top + margin.bottom)); | |
focus.append("line") | |
.attr("class", "y") | |
.style("stroke", "blue") | |
.style("stroke-dasharray", "3,3") | |
.style("opacity", 0.75) | |
.attr("x1", 0 ) | |
.attr("x2", width - (margin.left + margin.right)); | |
focus.append("text") | |
.attr("x", 5) | |
.attr("y", 10) | |
.attr("class", "text--date"); | |
focus.append("text") | |
.attr("x", -5) | |
.attr("y", -5) | |
.attr("class", "text--value"); | |
// Update outer dimensions | |
svg .attr("width", width) | |
.attr("height", height); | |
// Update inner dimensions | |
var g = svg.select("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// Update the x axis | |
g.select(".axis.axis--x") | |
.transition() | |
.duration(750) | |
.attr("transform", "translate(0," + yScale.range()[0] + ")") | |
.call(xAxis); | |
// Update the y axis | |
g.select(".axis.axis--y") | |
.transition() | |
.duration(750) | |
.call(yAxis); | |
// Update grid | |
g.select(".grid.grid--x") | |
.transition() | |
.duration(750) | |
.call(xGrid); | |
g.select(".grid.grid--y") | |
.transition() | |
.duration(750) | |
.call(yGrid); | |
if (options.rollOverDay != null) { | |
g.select(".grid.grid--rollover") | |
.transition() | |
.duration(750) | |
.call(rollOverTicks) | |
} | |
// Shade grid for weekends | |
var gWeekends = g.select(".grid.grid--weekends"); | |
var dataWeekends = d3.time.saturdays(xDomain[0], xDomain[1]); | |
if (dataWeekends.length == 0) { | |
if (xDomain[0].getDay() == 6) { | |
dataWeekends = [xDomain[0]]; | |
} else if (xDomain[0].getDay() == 0) { | |
dataWeekends = [d3.time.hour.offset(d3.time.day(xDomain[0]),-24)]; | |
} else { | |
dataWeekends = []; | |
} | |
} | |
var rectWeekends = gWeekends.selectAll("rect") | |
.data(dataWeekends); | |
// UPDATE | |
rectWeekends | |
.transition() | |
.duration(750) | |
.attr("x", function(d) { return xScale(d) < 0 ? 0 : xScale(d); }) | |
.attr("width", function(d) { | |
var x = xScale(d) < 0 ? 0 : xScale(d); | |
var w = xScale(d3.time.hour.offset(d, 48)) - x; | |
return (x + w) < (width - (margin.left + margin.right)) ? w : (width - (margin.left + margin.right)) - x; | |
}) | |
.attr("y", 0) | |
.attr("height", height - (margin.bottom + margin.top)); | |
// APPEND | |
rectWeekends | |
.enter() | |
.append("rect") | |
.style("fill-opacity", 1e-6) | |
.transition() | |
.duration(750) | |
.attr("x", function(d) { return xScale(d) < 0 ? 0 : xScale(d); }) | |
.attr("width", function(d) { | |
var x = xScale(d) < 0 ? 0 : xScale(d); | |
var w = xScale(d3.time.hour.offset(d, 48)) - x; | |
return (x + w) < (width - (margin.left + margin.right)) ? w : (width - (margin.left + margin.right)) - x; | |
}) | |
.attr("y", 0) | |
.attr("height", height - (margin.bottom + margin.top)) | |
.style("fill-opacity", 0.25); | |
//REMOVE | |
rectWeekends.exit() | |
.transition() | |
.duration(750) | |
.style("fill-opacity", 1e-6) | |
.remove(); | |
// Draw the line(s) | |
// DATA JOIN | |
var gLines = svg.select(".line-group"); | |
var linePaths = gLines.selectAll("path").data(data); | |
// UPDATE | |
linePaths | |
.transition() | |
.duration(1000) | |
.attr("d", function(d) { | |
d.line = this; | |
return line(d[options.valueSeries]); | |
}); | |
// NEW VALUES | |
linePaths | |
.enter() | |
.append("path") | |
.transition() | |
.duration(1000) | |
.attr("class", "line") | |
.attr("style", function(d,i){ return "stroke: " + colours(i);} ) | |
.attr("d", function(d) { d.line = this; return line(d[options.valueSeries]); }) | |
.style("fill-opacity", 1); | |
//REMOVE | |
linePaths.exit() | |
.style("fill-opacity", 1e-6) | |
.transition() | |
.duration(1000) | |
.remove(); | |
var titleText = svg.select(".title") | |
.selectAll("text") | |
.data([title]); | |
titleText.enter() | |
.append("text") | |
.attr({ | |
"x": (width- margin.right) / 2, | |
"y": -(margin.top / 2), | |
"text-anchor": "middle", | |
"font-size": "14px" | |
}) | |
.text(function(d) { | |
return d; | |
}); | |
titleText.exit() | |
.remove(); | |
if (options.limitLines.length > 0) { | |
var gLimitLines = svg.select(".grid.grid--limit-lines").selectAll("line").data(data); | |
gLimitLines | |
.transition() | |
.duration(1000) | |
.attr({ "x1": 0, | |
"y1": function(d, i) {return yScale(options.limitLines[i]); }, | |
"x2": width - (margin.right + margin.left), | |
"y2": function(d, i) {return yScale(options.limitLines[i]); } | |
}); | |
gLimitLines.enter() | |
.append("line") | |
.style("stroke", function(d, i) { return colours(i); }) | |
.attr({ "x1": 0, | |
"y1": function(d, i) {return yScale(options.limitLines[i]); }, | |
"x2": width - (margin.right + margin.left), | |
"y2": function(d, i) {return yScale(options.limitLines[i]); } | |
}); | |
gLimitLines.exit() | |
.remove(); | |
} | |
if (options.showLegend) { | |
// JOIN | |
var gLegend = svg.select(".legend").selectAll("g").data(data); | |
// APPEND | |
var gLegendEnter = gLegend.enter().append("g"); | |
gLegendEnter | |
.append("rect") | |
.attr("x", width-margin.right-margin.left + 15) | |
.attr("y", function(d, i){ return i * 20;}) | |
.attr("width", 10) | |
.attr("height", 10) | |
.style("fill", function(d, i) { return colours(i); }) | |
gLegendEnter | |
.append("text") | |
.attr("x", width-margin.right-margin.left + 27) | |
.attr("y", function(d, i){ return i * 20 + 9;}) | |
.attr("class", "legend-text") | |
.text(function(d,i) { | |
// if options.legend is a String use that as the legend field, | |
// otherwise assume options.legend is an array of Strings | |
return ( toType(options.legend) == "String") ? d[options.legend] : options.legend[i]; | |
}); | |
// REMOVE | |
gLegend.exit() | |
.remove(); | |
} | |
if (options.showVoronoi){ | |
// update voronoi overlay | |
var v = svg.select("#voronoi") | |
// DATA JOIN | |
var v_paths = v.selectAll("path") | |
.data(voronoi(d3.nest() | |
.key(function(d) { | |
return xScale(xValue(d)) + "," + yScale(yValue(d)); }) | |
.rollup(function(v) { | |
return v[0]; }) | |
.entries(d3.merge(data.map(function(d) { | |
return d[options.valueSeries]; }))) | |
.map(function(d) { | |
return d[options.valueSeries]; }))); | |
// Update | |
v_paths.attr("d", function(d) { | |
return "M" + d.join("L") + "Z"; }) | |
.datum(function(d) { return d.point; }) | |
.on("mouseover", mouseover) | |
.on("mouseout", mouseout); | |
// Add new values | |
v_paths.enter() | |
.append("path") | |
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) | |
.datum(function(d) { return d.point; }) | |
.on("mouseover", mouseover) | |
.on("mouseout", mouseout); | |
// Remove | |
v_paths.exit().remove(); | |
var focus = svg.select("g.focus"); | |
function mouseover(d) { | |
focus.attr("transform", "translate(" + xScale(xValue(d)) + "," + yScale(yValue(d)) + ")"); | |
focus.select(".text--date").text(dateFormat(new Date(xValue(d)))); | |
focus.select(".text--value").text(valueFormat(yValue(d)) + options.valueUnits); | |
focus.select(".x").attr("y2", height - (margin.top + margin.bottom) - yScale(yValue(d))); | |
focus.select(".y").attr("x2", -xScale(xValue(d))); | |
} | |
function mouseout(d) { | |
focus.attr("transform", "translate(-100,-100)"); | |
} | |
} | |
}) | |
} | |
// The x-accessor for the path generator; xScale ∘ xValue. | |
function X(d) { | |
return xScale(xValue(d)); | |
} | |
// The x-accessor for the path generator; yScale ∘ yValue. | |
function Y(d) { | |
return yScale(yValue(d)); | |
} | |
chart.margin = function(_) { | |
if (!arguments.length) return margin; | |
margin = _; | |
return chart; | |
}; | |
chart.options = function(_) { | |
if (!arguments.length) return options; | |
options = _; | |
return chart; | |
}; | |
chart.width = function(_) { | |
if (!arguments.length) return width; | |
width = _; | |
return chart; | |
}; | |
chart.height = function(_) { | |
if (!arguments.length) return height; | |
height = _; | |
return chart; | |
}; | |
chart.x = function(_) { | |
if (!arguments.length) return xValue; | |
xValue = _; | |
return chart; | |
}; | |
chart.y = function(_) { | |
if (!arguments.length) return yValue; | |
yValue = _; | |
return chart; | |
}; | |
chart.valueFormat = function(_) { | |
if (!arguments.length) return valueFormat; | |
valueFormat = _; | |
return chart; | |
}; | |
chart.legend = function(_) { | |
if (!arguments.length) return options.legend; | |
options.legend = _; | |
return chart; | |
}; | |
chart.valueSeries = function(_) { | |
if (!arguments.length) return options.valueSeries; | |
options.valueSeries = _; | |
return chart; | |
}; | |
chart.showLegend = function(_) { | |
if (!arguments.length) return options.showLegend; | |
options.showLegend = _; | |
return chart; | |
}; | |
chart.showVoronoi = function(_) { | |
if (!arguments.length) return options.showVoronoi; | |
options.showVoronoi = _; | |
return chart; | |
}; | |
chart.rollOverDay = function(_) { | |
if (!arguments.length) return options.rollOverDay; | |
options.rollOverDay = _; | |
return chart; | |
}; | |
chart.valueUnits = function(_) { | |
if (!arguments.length) return options.valueUnits; | |
options.valueUnits = _; | |
return chart; | |
}; | |
chart.limitLines = function(_) { | |
if (!arguments.length) return options.limitLines; | |
options.limitLines = _; | |
return chart; | |
}; | |
chart.title = function(_) { | |
if (!arguments.length) return title; | |
title = _; | |
return chart; | |
}; | |
chart.invertDS = function(_) { | |
if (!arguments.length) return options.invertDS; | |
options.invertDS = _; | |
return chart; | |
} | |
return chart; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/default.min.css"> | |
<style> | |
@import url("style.css"); | |
#form { | |
/*position: absolute; | |
top: 15px; | |
left: 550px;*/ | |
} | |
.grid--y .tick, .grid--x .tick { | |
stroke: lightgrey; | |
stroke-opacity: 0.7; | |
shape-rendering: crispEdges; | |
} | |
.grid.grid--y path, .grid.grid--x path { | |
stroke-width: 0; | |
shape-rendering: crispEdges; | |
} | |
.grid.grid--weekends rect { | |
fill: #aabbcc; | |
fill-opacity: 0.25; | |
shape-rendering: crispEdges; | |
} | |
.grid.grid--rollover line { | |
stroke: red; | |
stroke-width: 1.5px; | |
stroke-dasharray: 7 5; | |
stroke-opacity: 0.35; | |
shape-rendering: crispEdges; | |
} | |
</style> | |
<h1>Internet Usage</h1> | |
<div> | |
<label id="form"> | |
Select period: | |
<select id="period"></select> | |
</label> | |
</div> | |
<div id="graphs"> | |
</div> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script> | |
<script src="binaryXHR.js"></script> | |
<script src="rrdFile.js"></script> | |
<script src="rrdFilter.js"></script> | |
<script src="rrdAsync.js"></script> | |
<script src="rrd2D3.js"></script> | |
<script src="chart.js"></script> | |
<script id="code"> | |
// Retrieve data from RRD | |
var rrd_obj = new rrdAsync("usage.rrd",null, init); | |
var rrd_data; // populated in init() once RRD file has been fetched. | |
function init(){ | |
rrd_data = rrd_obj.rrd_data; | |
var rra = 1; | |
var DS_Names = rrd_data.getDSNames(); | |
// populate with RRA info | |
var nrRRAs=rrd_data.getNrRRAs(); | |
var RRAs = []; | |
for (var i=0; i<nrRRAs; i++) { | |
var rra=rrd_data.getRRAInfo(i); | |
var step=rra.getStep(); | |
var rows=rra.getNrRows(); | |
var period=step*rows; | |
var rra_label=rfs_format_time(period) + " ("+rfs_format_time(step)+" steps)"; | |
RRAs.push({text: rra_label, value: i}); | |
} | |
// Populate period select control | |
var select = d3.select("#period"); | |
var options = select.selectAll("option") | |
.data(RRAs) | |
.enter() | |
.append("option") | |
.attr('value', function(d){ return d.value; }) | |
.text(function(d){ return d.text;}); | |
select.property("value", 2); | |
select.on("change", function(){ | |
updateCharts(); | |
}) | |
var charts = [ { id: "usage", title: "Internet Usage", DS_Names: ["jack_usage", "harry_usage"] }, | |
{ id: "jack", title: "Jack Bandwidth Usage", DS_Names: ["jack_in", "jack_out"] }, | |
{ id: "harry", title: "Harry Bandwidth Usage", DS_Names: ["harry_in", "harry_out"] } | |
]; | |
var graphs = d3.select("#graphs").selectAll("div").data(charts); | |
graphs | |
.enter() | |
.append("div") | |
.attr("id", function(d) { return d.id; }); | |
charts.forEach(function(d){ | |
// Get the dataset | |
var dataset = rrdRRA2D3Obj(rrd_data, 2, d.DS_Names, true); | |
// Create the chart | |
var chart = new multiLineChart() | |
.x(function(d) { return d.date; }) | |
.y(function(d) { return d.value; }) | |
.valueFormat(d3.format(".3s")) | |
.width(960) | |
.height(500) | |
.legendField("name") | |
.valueSeries("values") | |
.title(d.title) | |
.showLegend(true) | |
.showVoronoi(true) | |
.rollOverDay(8); | |
var graph = d3.select("#" + d.id ) | |
.data([dataset]) | |
.call(chart); | |
d.chart = chart; | |
}); | |
// Refresh data every 5 minutes | |
/* | |
setInterval(function(){ | |
rrd_obj.customization_callback = updateCharts; | |
rrd_obj.reload(); | |
}, 5 * 60 * 1000); | |
*/ | |
function updateCharts() { | |
rrd_data = rrd_obj.rrd_data; | |
var RRA = select.property("value"); | |
charts.forEach(function(d){ | |
// Get the dataset | |
var dataset = rrdRRA2D3Obj(rrd_data, RRA, d.DS_Names, true); | |
// Update the chart | |
var graph = d3.select("#" + d.id ) | |
.data([dataset]) | |
.call(d.chart); | |
}) | |
} | |
} | |
</script> | |
<script> | |
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | |
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | |
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | |
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); | |
ga('create', 'UA-28825538-1', 'auto'); | |
ga('send', 'pageview'); | |
</script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (Number.prototype.toKMBT == null) | |
Number.prototype.toKMBT = function() { | |
y = this; | |
var abs_y = Math.abs(y); | |
if (abs_y >= 1000000000000) { return (y / 1000000000000).toFixed(2) + "T" } | |
else if (abs_y >= 1000000000) { return (y / 1000000000).toFixed(2) + "B" } | |
else if (abs_y >= 1000000) { return (y / 1000000).toFixed(2) + "M" } | |
else if (abs_y >= 1000) { return (y / 1000).toFixed(2) + "K" } | |
else if (abs_y < 1 && y > 0) { return y.toFixed(2) } | |
else if (abs_y === 0) { return ' ' } | |
else { return y } | |
}; | |
if (Number.prototype.toBase1024KMGTP == null) | |
Number.prototype.toBase1024KMGTP = function() { | |
y = this; | |
var abs_y = Math.abs(y); | |
if (abs_y >= 1125899906842624) { return (y / 1125899906842624).toFixed(2) + "P" } | |
else if (abs_y >= 1099511627776){ return (y / 1099511627776).toFixed(2) + "T" } | |
else if (abs_y >= 1073741824) { return (y / 1073741824).toFixed(2) + "G" } | |
else if (abs_y >= 1048576) { return (y / 1048576).toFixed(2) + "M" } | |
else if (abs_y >= 1024) { return (y / 1024).toFixed(2) + "K" } | |
else if (abs_y < 1 && y > 0) { return y.toFixed(2) } | |
else if (abs_y === 0) { return ' ' } | |
else { return y } | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Retrieve data from RRD | |
var rrd_obj = new rrdAsync("shed_temps.rrd",null, init); | |
var rrd_data; // populated in init() once RRD file has been fetched. | |
var margin = {top: 20, right: 70, bottom: 100, left: 40}, | |
width = 960 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom; | |
var colours = d3.scale.category10(); | |
function init() { | |
// RRD file has been fetched, so data is ready to be used | |
rrd_data = rrd_obj.rrd_data; | |
populateRRASelect("#period"); | |
drawGraph(rrdRRA2D3Obj(rrd_data,1,rrd_data.getDSNames(),true), rrd_data.getDSNames()); | |
d3.select("#listing") | |
.append("pre") | |
.append("code") | |
.attr("class", "js") | |
.text(d3.select("#code").html()); | |
hljs.highlightBlock(d3.select("#listing pre code").node()); | |
} | |
function populateRRASelect(res_id) { | |
// populate with RRA info | |
var nrRRAs=rrd_data.getNrRRAs(); | |
var RRAs = []; | |
for (var i=0; i<nrRRAs; i++) { | |
var rra=rrd_data.getRRAInfo(i); | |
var step=rra.getStep(); | |
var rows=rra.getNrRows(); | |
var period=step*rows; | |
var rra_label=rfs_format_time(period) + " ("+rfs_format_time(step)+" steps)"; | |
RRAs.push({text: rra_label, value: i}); | |
} | |
var select = d3.select(res_id) | |
.on("change", periodChange), | |
options = select | |
.selectAll("option") | |
.data(RRAs) | |
.enter() | |
.append("option") | |
.attr('value', function(d){ return d.value; }) | |
.text(function(d){ return d.text;}); | |
select.property("value", 1); | |
} | |
function periodChange(){ | |
// Update graph with new data | |
var RRA = this.value ? this.value : 1; | |
var dataset=rrdRRA2D3Obj(rrd_data,RRA,rrd_data.getDSNames(),true); | |
var x = d3.time.scale() | |
.range([0, width]) | |
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.date;} ) }), | |
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.date;} ) })]); | |
var y = d3.scale.linear() | |
.range([height, 0]) | |
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.value;} ) }), | |
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.value;} ) })]).nice(); | |
var voronoi = d3.geom.voronoi() | |
.x(function(d) { return x(d.date); }) | |
.y(function(d) { return y(d.value); }) | |
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]); | |
var line = d3.svg.line() | |
.defined(function(d) { return d.value != null; }) | |
.interpolate('cardinal') | |
.x(function(d) { return x(d.date); }) | |
.y(function(d) { return y(d.value); }); | |
var svg = d3.select("#temp-graph") | |
// DATA JOIN | |
var lines = svg.selectAll("path.line").data(dataset) | |
// Update | |
lines.transition() | |
.duration(1000) | |
.attr("d", function(d) { d.line = this; return line(d.values); }); | |
// Add new values | |
lines.enter() | |
.append("path") | |
.attr("class", "line") | |
.attr("style", function(d,i){ return "stroke: " + colours(i);} ) | |
.style("fill-opacity", 1e-6) | |
.transition() | |
.duration(1000) | |
.attr("d", function(d) { d.line = this; return line(d.values); }) | |
.style("fill-opacity", 1); | |
// Remove | |
lines.exit() | |
.transition() | |
.duration(1000) | |
.style("fill-opacity", 1e-6) | |
.remove(); | |
// Update X axis | |
svg.select(".axis.axis--x") | |
.transition() | |
.duration(1000) | |
.call(d3.svg.axis() | |
.scale(x) | |
.orient("bottom")); | |
// Update Y axis | |
svg.select(".axis.axis--y") | |
.transition() | |
.duration(1000) | |
.call(d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.ticks(10, ".2f")); | |
// Update stats | |
formatFloat = d3.format(".2f"); | |
var stats = d3.select("#stats tbody") | |
.selectAll('tr') | |
.data(dataset) | |
.selectAll("td") | |
.data(function(l){ return [ l.name, | |
formatFloat(d3.min(l.values, function(d){ return d.value; })), | |
formatFloat(d3.max(l.values, function(d){ return d.value; })), | |
formatFloat(d3.mean(l.values, function(d){return d.value; }))]; | |
}) | |
.text(function(d){ return d;}); | |
// update voronoi overlay | |
var v = svg.select("#voronoi") | |
// DATA JOIN | |
var v_paths = v.selectAll("path") | |
.data(voronoi(d3.nest() | |
.key(function(d) { return x(d.date) + "," + y(d.value); }) | |
.rollup(function(v) { return v[0]; }) | |
.entries(d3.merge(dataset.map(function(d) { return d.values; }))) | |
.map(function(d) { return d.values; }))); | |
// Update | |
v_paths.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) | |
.datum(function(d) { return d.point; }) | |
.on("mouseover", mouseover) | |
.on("mouseout", mouseout); | |
// Add new values | |
v_paths.enter() | |
.append("path") | |
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) | |
.datum(function(d) { return d.point; }) | |
.on("mouseover", mouseover) | |
.on("mouseout", mouseout); | |
// Remove | |
v_paths.exit().remove(); | |
var focus = svg.select("g.focus"); | |
function mouseover(d) { | |
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")"); | |
focus.select("text").text(formatFloat(d.value) + "°C"); | |
} | |
function mouseout(d) { | |
focus.attr("transform", "translate(-100,-100)"); | |
} | |
} | |
function drawGraph(dataset, names) { | |
var x = d3.time.scale() | |
.range([0, width]) | |
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.date;} ) }), | |
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.date;} ) })]); | |
var y = d3.scale.linear() | |
.range([height, 0]) | |
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.value;} ) }), | |
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.value;} ) })]).nice(); | |
var color = d3.scale.category20(); | |
var voronoi = d3.geom.voronoi() | |
.x(function(d) { return x(d.date); }) | |
.y(function(d) { return y(d.value); }) | |
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]); | |
var line = d3.svg.line() | |
.defined(function(d) { return d.value != null; }) | |
//.interpolate('cardinal') | |
.x(function(d) { return x(d.date); }) | |
.y(function(d) { return y(d.value); }); | |
var svg = d3.select("#temp-graph") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// Draw X axis | |
svg.append("g") | |
.attr("class", "axis axis--x") | |
.attr("transform", "translate(0," + height + ")") | |
.call(d3.svg.axis() | |
.scale(x) | |
.orient("bottom")) | |
.append("text") | |
.attr("x", -margin.left * 0.75) | |
.attr("y", -height-10) | |
.text("° C"); | |
// Draw Y axis | |
svg.append("g") | |
.attr("class", "axis axis--y") | |
.call(d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.ticks(10, ".2f")) | |
// Plot the lines - first time, so have to append <g> | |
svg.append("g") | |
.attr("class", "line-group") | |
.selectAll("path") | |
.data(dataset) | |
.enter() | |
.append("path") | |
.attr("class", "line") | |
.attr("style", function(d,i){ return "stroke: " + colours(i);} ) | |
.attr("d", function(d) { d.line = this; return line(d.values); }); | |
// add legend | |
var legend = svg.append("g") | |
.attr("class", "legend") | |
.attr("height", 100) | |
.attr("width", 120); | |
legend.selectAll('rect') | |
.data(dataset) | |
.enter() | |
.append("rect") | |
.attr("x", width + 15) | |
.attr("y", function(d, i){ return i * 20;}) | |
.attr("width", 10) | |
.attr("height", 10) | |
.style("fill", function(d, i) { return colours(i); }); | |
legend.selectAll('text') | |
.data(dataset) | |
.enter() | |
.append("text") | |
.attr("x", width + 27) | |
.attr("y", function(d, i){ return i * 20 + 9;}) | |
.attr("class", "legend-text") | |
.text(function(d) { return d.name; }); | |
var formatFloat = d3.format(".2f"); | |
// Display some simple stats | |
var stats = d3.select("#stats tbody") | |
.selectAll('tr') | |
.data(dataset) | |
.enter() | |
.append("tr") | |
.style("background-color", function(d,i){ return colours(i);}) | |
.selectAll("td") | |
.data(function(l){ return [ l.name, | |
formatFloat(d3.min(l.values, function(d){ return d.value; })), | |
formatFloat(d3.max(l.values, function(d){ return d.value; })), | |
formatFloat(d3.mean(l.values, function(d){return d.value; })) ]; | |
}) | |
.enter() | |
.append("td") | |
.text(function(d){ return d;}) | |
// Element to hold voronoi overlay | |
var voronoiGroup = svg.append("g") | |
.attr("class", "voronoi") | |
.attr("id", "voronoi"); | |
// Generate & add voronoi paths | |
voronoiGroup.selectAll("path") | |
.data(voronoi(d3.nest() | |
.key(function(d) { return x(d.date) + "," + y(d.value); }) | |
.rollup(function(v) { return v[0]; }) | |
.entries(d3.merge(dataset.map(function(d) { return d.values; }))) | |
.map(function(d) { return d.values; }))) | |
.enter().append("path") | |
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) | |
.datum(function(d, i, j) { console.log(d, i, j); return d.point; }) | |
.on("mouseover", mouseover) | |
.on("mouseout", mouseout); | |
var focus = svg.append("g") | |
.attr("transform", "translate(-100,-100)") | |
.attr("class", "focus"); | |
focus.append("circle") | |
.attr("r", 3.5); | |
focus.append("text") | |
.attr("y", -10); | |
function mouseover(d, i, j) { | |
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")"); | |
focus.select("text").text(formatFloat(d.value) + "°C"); | |
} | |
function mouseout(d) { | |
focus.attr("transform", "translate(-100,-100)"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// return an array of objects containing D3elements, one per DS | |
// | |
// data = [ | |
// { | |
// name: <DS name>, | |
// values: [{ date: <timestamp>, value: 1}, { date: <timestamp>, value: 2}] | |
// }, | |
// { | |
// name: <DS name>, | |
// values: [{ date: <timestamp>, value: 1}, { date: <timestamp>, value: 2}] | |
// } | |
// ]; | |
function rrdRRA2D3Obj(rrd_file,rra_idx,ds_list,want_rounding) { | |
var rra=rrd_file.getRRA(rra_idx); | |
var rra_rows=rra.getNrRows(); | |
var last_update=rrd_file.getLastUpdate(); | |
var step=rra.getStep(); | |
if (want_rounding!=false) { | |
// round last_update to step | |
// so that all elements are sync | |
last_update-=(last_update%step); | |
} | |
var first_el=last_update-(rra_rows-1)*step; | |
var out_el = []; | |
var ds_list_len = ds_list.length; | |
for (var ds_list_idx=0; ds_list_idx<ds_list_len; ++ds_list_idx) { | |
var ds_id=ds_list[ds_list_idx]; | |
var ds=rrd_file.getDS(ds_id); | |
var ds_name=ds.getName(); | |
var ds_idx=ds.getIdx(); | |
var timestamp=first_el; | |
var ds_series=[]; | |
for (var i=0;i<rra_rows;i++) { | |
var el=rra.getEl(i,ds_idx); | |
if (el != undefined) { | |
if (Math.abs(+el) > 213000000000000) { // remove erroneous spikes from RRD data | |
el = 0; | |
} | |
// Convert timestamp to JS ticks | |
ds_series.push( { "date": timestamp * 1000, "value": +el } ); | |
} else { | |
// Convert timestamp to JS ticks, return null for missing values | |
ds_series.push( { "date": timestamp * 1000, "value": null } ); | |
} | |
timestamp+=step; | |
} // end for | |
var ds_name=ds.getName(); | |
out_el.push({"name": ds_name, | |
"values": ds_series}); | |
} //end for ds_list_idx | |
return out_el; | |
} | |
function rfs_format_time(s) { | |
if (s<120) { | |
return s+"s"; | |
} else { | |
var s60=s%60; | |
var m=(s-s60)/60; | |
if ((m<10) && (s60>9)) { | |
return m+":"+s60+" min"; | |
} if (m<120) { | |
return m+" min"; | |
} else { | |
var m60=m%60; | |
var h=(m-m60)/60; | |
if ((h<12) && (m60>9)) { | |
return h+":"+m60+" hrs"; | |
} if (h<48) { | |
return h+" hrs"; | |
} else { | |
var h24=h%24; | |
var d=(h-h24)/24; | |
if ((d<7) && (h24>0)) { | |
return d+" days "+h24+"h"; | |
} if (d<60) { | |
return d+" days"; | |
} else { | |
var d30=d%30; | |
var mt=(d-d30)/30; | |
return mt+" months"; | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Callback to load RRD asynchronously */ | |
function rrdAsyncCallback(bf,obj) { | |
var i_rrd_data=undefined; | |
if (bf.getLength()<1) { | |
alert("File "+obj.url+" is empty (possibly loading failed)!"); | |
return 1; | |
} | |
try { | |
i_rrd_data=new RRDFile(bf,obj.file_options); | |
} catch(err) { | |
alert("File "+obj.url+" is not a valid RRD archive!\n"+err); | |
} | |
if (i_rrd_data!=undefined) { | |
if (obj.rrd_data!=null) delete obj.rrd_data; | |
obj.rrd_data=i_rrd_data; | |
obj.callback(); | |
} | |
} | |
/* Use url==null if you do not know the url yet */ | |
function rrdAsync(url, | |
file_options, // see rrdFile.js::RRDFile for documentation | |
customization_callback // if defined, see above | |
) { | |
this.url=url; | |
this.file_options=file_options; | |
this.customization_callback=customization_callback; | |
this.rrd_obj=null; | |
this.rrd_data=null; | |
if (url!=null) { | |
this.reload(url); | |
} | |
} | |
rrdAsync.prototype.reload = function(url) { | |
if (arguments.length) { | |
this.url=url; | |
} | |
try { | |
FetchBinaryURLAsync(this.url,rrdAsyncCallback,this); | |
} catch (err) { | |
alert("Failed loading "+this.url+"\n"+err); | |
} | |
}; | |
rrdAsync.prototype.callback = function() { | |
if (this.rrd_flot_obj!=null) delete this.rrd_flot_obj; | |
if (this.customization_callback!=undefined) this.customization_callback(this); | |
var irrd_data=this.rrd_data; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Client library for access to RRD archive files | |
* Part of the javascriptRRD package | |
* Copyright (c) 2009-2010 Frank Wuerthwein, [email protected] | |
* Igor Sfiligoi, [email protected] | |
* | |
* Original repository: http://javascriptrrd.sourceforge.net/ | |
* | |
* MIT License [http://www.opensource.org/licenses/mit-license.php] | |
* | |
*/ | |
/* | |
* | |
* RRDTool has been developed and is maintained by | |
* Tobias Oether [http://oss.oetiker.ch/rrdtool/] | |
* | |
* This software can be used to read files produced by the RRDTool | |
* but has been developed independently. | |
* | |
* Limitations: | |
* | |
* This version of the module assumes RRD files created on linux | |
* with intel architecture and supports both 32 and 64 bit CPUs. | |
* All integers in RRD files are suppoes to fit in 32bit values. | |
* | |
* Only versions 3 and 4 of the RRD archive are supported. | |
* | |
* Only AVERAGE,MAXIMUM,MINIMUM and LAST consolidation functions are | |
* supported. For all others, the behaviour is at the moment undefined. | |
* | |
*/ | |
/* | |
* Dependencies: | |
* | |
* The data provided to this module require an object of a class | |
* that implements the following methods: | |
* getByteAt(idx) - Return a 8 bit unsigned integer at offset idx | |
* getLongAt(idx) - Return a 32 bit unsigned integer at offset idx | |
* getDoubleAt(idx) - Return a double float at offset idx | |
* getFastDoubleAt(idx) - Similar to getDoubleAt but with less precision | |
* getCStringAt(idx,maxsize) - Return a string of at most maxsize characters | |
* that was 0-terminated in the source | |
* | |
* The BinaryFile from binaryXHR.js implements this interface. | |
* | |
*/ | |
// ============================================================ | |
// Exception class | |
function InvalidRRD(msg) { | |
this.message=msg; | |
this.name="Invalid RRD"; | |
} | |
// pretty print | |
InvalidRRD.prototype.toString = function() { | |
return this.name + ': "' + this.message + '"'; | |
} | |
// ============================================================ | |
// RRD DS Info class | |
function RRDDS(rrd_data,rrd_data_idx,my_idx) { | |
this.rrd_data=rrd_data; | |
this.rrd_data_idx=rrd_data_idx; | |
this.my_idx=my_idx; | |
} | |
RRDDS.prototype.getIdx = function() { | |
return this.my_idx; | |
} | |
RRDDS.prototype.getName = function() { | |
return this.rrd_data.getCStringAt(this.rrd_data_idx,20); | |
} | |
RRDDS.prototype.getType = function() { | |
return this.rrd_data.getCStringAt(this.rrd_data_idx+20,20); | |
} | |
RRDDS.prototype.getMin = function() { | |
return this.rrd_data.getDoubleAt(this.rrd_data_idx+48); | |
} | |
RRDDS.prototype.getMax = function() { | |
return this.rrd_data.getDoubleAt(this.rrd_data_idx+56); | |
} | |
// ============================================================ | |
// RRD RRA Info class | |
function RRDRRAInfo(rrd_data,rra_def_idx, | |
int_align,row_cnt,pdp_step,my_idx) { | |
this.rrd_data=rrd_data; | |
this.rra_def_idx=rra_def_idx; | |
this.int_align=int_align; | |
this.row_cnt=row_cnt; | |
this.pdp_step=pdp_step; | |
this.my_idx=my_idx; | |
// char nam[20], uint row_cnt, uint pdp_cnt | |
this.rra_pdp_cnt_idx=rra_def_idx+Math.ceil(20/int_align)*int_align+int_align; | |
} | |
RRDRRAInfo.prototype.getIdx = function() { | |
return this.my_idx; | |
} | |
// Get number of rows | |
RRDRRAInfo.prototype.getNrRows = function() { | |
return this.row_cnt; | |
} | |
// Get number of slots used for consolidation | |
// Mostly for internal use | |
RRDRRAInfo.prototype.getPdpPerRow = function() { | |
return this.rrd_data.getLongAt(this.rra_pdp_cnt_idx); | |
} | |
// Get RRA step (expressed in seconds) | |
RRDRRAInfo.prototype.getStep = function() { | |
return this.pdp_step*this.getPdpPerRow(); | |
} | |
// Get consolidation function name | |
RRDRRAInfo.prototype.getCFName = function() { | |
return this.rrd_data.getCStringAt(this.rra_def_idx,20); | |
} | |
// ============================================================ | |
// RRD RRA handling class | |
function RRDRRA(rrd_data,rra_ptr_idx, | |
rra_info, | |
header_size,prev_row_cnts,ds_cnt) { | |
this.rrd_data=rrd_data; | |
this.rra_info=rra_info; | |
this.row_cnt=rra_info.row_cnt; | |
this.ds_cnt=ds_cnt; | |
var row_size=ds_cnt*8; | |
this.base_rrd_db_idx=header_size+prev_row_cnts*row_size; | |
// get imediately, since it will be needed often | |
this.cur_row=rrd_data.getLongAt(rra_ptr_idx); | |
// calculate idx relative to base_rrd_db_idx | |
// mostly used internally | |
this.calc_idx = function(row_idx,ds_idx) { | |
if ((row_idx>=0) && (row_idx<this.row_cnt)) { | |
if ((ds_idx>=0) && (ds_idx<ds_cnt)){ | |
// it is round robin, starting from cur_row+1 | |
var real_row_idx=row_idx+this.cur_row+1; | |
if (real_row_idx>=this.row_cnt) real_row_idx-=this.row_cnt; | |
return row_size*real_row_idx+ds_idx*8; | |
} else { | |
throw RangeError("DS idx ("+ row_idx +") out of range [0-" + ds_cnt +")."); | |
} | |
} else { | |
throw RangeError("Row idx ("+ row_idx +") out of range [0-" + this.row_cnt +")."); | |
} | |
} | |
} | |
RRDRRA.prototype.getIdx = function() { | |
return this.rra_info.getIdx(); | |
} | |
// Get number of rows/columns | |
RRDRRA.prototype.getNrRows = function() { | |
return this.row_cnt; | |
} | |
RRDRRA.prototype.getNrDSs = function() { | |
return this.ds_cnt; | |
} | |
// Get RRA step (expressed in seconds) | |
RRDRRA.prototype.getStep = function() { | |
return this.rra_info.getStep(); | |
} | |
// Get consolidation function name | |
RRDRRA.prototype.getCFName = function() { | |
return this.rra_info.getCFName(); | |
} | |
RRDRRA.prototype.getEl = function(row_idx,ds_idx) { | |
return this.rrd_data.getDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx)); | |
} | |
// Low precision version of getEl | |
// Uses getFastDoubleAt | |
RRDRRA.prototype.getElFast = function(row_idx,ds_idx) { | |
return this.rrd_data.getFastDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx)); | |
} | |
// ============================================================ | |
// RRD Header handling class | |
function RRDHeader(rrd_data) { | |
this.rrd_data=rrd_data; | |
this.validate_rrd(); | |
this.calc_idxs(); | |
} | |
// Internal, used for initialization | |
RRDHeader.prototype.validate_rrd = function() { | |
if (this.rrd_data.getLength()<1) throw new InvalidRRD("Empty file."); | |
if (this.rrd_data.getLength()<16) throw new InvalidRRD("File too short."); | |
if (this.rrd_data.getCStringAt(0,4)!=="RRD") throw new InvalidRRD("Wrong magic id."); | |
this.rrd_version=this.rrd_data.getCStringAt(4,5); | |
if ((this.rrd_version!=="0003")&&(this.rrd_version!=="0004")&&(this.rrd_version!=="0001")) { | |
throw new InvalidRRD("Unsupported RRD version "+this.rrd_version+"."); | |
} | |
this.float_width=8; | |
if (this.rrd_data.getLongAt(12)==0) { | |
// not a double here... likely 64 bit | |
this.float_align=8; | |
if (! (this.rrd_data.getDoubleAt(16)==8.642135e+130)) { | |
// uhm... wrong endian? | |
this.rrd_data.switch_endian=true; | |
} | |
if (this.rrd_data.getDoubleAt(16)==8.642135e+130) { | |
// now, is it all 64bit or only float 64 bit? | |
if (this.rrd_data.getLongAt(28)==0) { | |
// true 64 bit align | |
this.int_align=8; | |
this.int_width=8; | |
} else { | |
// integers are 32bit aligned | |
this.int_align=4; | |
this.int_width=4; | |
} | |
} else { | |
throw new InvalidRRD("Magic float not found at 16."); | |
} | |
} else { | |
/// should be 32 bit alignment | |
if (! (this.rrd_data.getDoubleAt(12)==8.642135e+130)) { | |
// uhm... wrong endian? | |
this.rrd_data.switch_endian=true; | |
} | |
if (this.rrd_data.getDoubleAt(12)==8.642135e+130) { | |
this.float_align=4; | |
this.int_align=4; | |
this.int_width=4; | |
} else { | |
throw new InvalidRRD("Magic float not found at 12."); | |
} | |
} | |
this.unival_width=this.float_width; | |
this.unival_align=this.float_align; | |
// process the header here, since I need it for validation | |
// char magic[4], char version[5], double magic_float | |
// long ds_cnt, long rra_cnt, long pdp_step, unival par[10] | |
this.ds_cnt_idx=Math.ceil((4+5)/this.float_align)*this.float_align+this.float_width; | |
this.rra_cnt_idx=this.ds_cnt_idx+this.int_width; | |
this.pdp_step_idx=this.rra_cnt_idx+this.int_width; | |
//always get only the low 32 bits, the high 32 on 64 bit archs should always be 0 | |
this.ds_cnt=this.rrd_data.getLongAt(this.ds_cnt_idx); | |
if (this.ds_cnt<1) { | |
throw new InvalidRRD("ds count less than 1."); | |
} | |
this.rra_cnt=this.rrd_data.getLongAt(this.rra_cnt_idx); | |
if (this.ds_cnt<1) { | |
throw new InvalidRRD("rra count less than 1."); | |
} | |
this.pdp_step=this.rrd_data.getLongAt(this.pdp_step_idx); | |
if (this.pdp_step<1) { | |
throw new InvalidRRD("pdp step less than 1."); | |
} | |
// best guess, assuming no weird align problems | |
this.top_header_size=Math.ceil((this.pdp_step_idx+this.int_width)/this.unival_align)*this.unival_align+10*this.unival_width; | |
var t=this.rrd_data.getLongAt(this.top_header_size); | |
if (t==0) { | |
throw new InvalidRRD("Could not find first DS name."); | |
} | |
} | |
// Internal, used for initialization | |
RRDHeader.prototype.calc_idxs = function() { | |
this.ds_def_idx=this.top_header_size; | |
// char ds_nam[20], char dst[20], unival par[10] | |
this.ds_el_size=Math.ceil((20+20)/this.unival_align)*this.unival_align+10*this.unival_width; | |
this.rra_def_idx=this.ds_def_idx+this.ds_el_size*this.ds_cnt; | |
// char cf_nam[20], uint row_cnt, uint pdp_cnt, unival par[10] | |
this.row_cnt_idx=Math.ceil(20/this.int_align)*this.int_align; | |
this.rra_def_el_size=Math.ceil((this.row_cnt_idx+2*this.int_width)/this.unival_align)*this.unival_align+10*this.unival_width; | |
this.live_head_idx=this.rra_def_idx+this.rra_def_el_size*this.rra_cnt; | |
// time_t last_up, int last_up_usec | |
this.live_head_size=2*this.int_width; | |
this.pdp_prep_idx=this.live_head_idx+this.live_head_size; | |
// char last_ds[30], unival scratch[10] | |
this.pdp_prep_el_size=Math.ceil(30/this.unival_align)*this.unival_align+10*this.unival_width; | |
this.cdp_prep_idx=this.pdp_prep_idx+this.pdp_prep_el_size*this.ds_cnt; | |
// unival scratch[10] | |
this.cdp_prep_el_size=10*this.unival_width; | |
this.rra_ptr_idx=this.cdp_prep_idx+this.cdp_prep_el_size*this.ds_cnt*this.rra_cnt; | |
// uint cur_row | |
this.rra_ptr_el_size=1*this.int_width; | |
this.header_size=this.rra_ptr_idx+this.rra_ptr_el_size*this.rra_cnt; | |
} | |
// Optional initialization | |
// Read and calculate row counts | |
RRDHeader.prototype.load_row_cnts = function() { | |
this.rra_def_row_cnts=[]; | |
this.rra_def_row_cnt_sums=[]; // how many rows before me | |
for (var i=0; i<this.rra_cnt; i++) { | |
this.rra_def_row_cnts[i]=this.rrd_data.getLongAt(this.rra_def_idx+i*this.rra_def_el_size+this.row_cnt_idx,false); | |
if (i==0) { | |
this.rra_def_row_cnt_sums[i]=0; | |
} else { | |
this.rra_def_row_cnt_sums[i]=this.rra_def_row_cnt_sums[i-1]+this.rra_def_row_cnts[i-1]; | |
} | |
} | |
} | |
// --------------------------- | |
// Start of user functions | |
RRDHeader.prototype.getMinStep = function() { | |
return this.pdp_step; | |
} | |
RRDHeader.prototype.getLastUpdate = function() { | |
return this.rrd_data.getLongAt(this.live_head_idx,false); | |
} | |
RRDHeader.prototype.getNrDSs = function() { | |
return this.ds_cnt; | |
} | |
RRDHeader.prototype.getDSNames = function() { | |
var ds_names=[] | |
for (var idx=0; idx<this.ds_cnt; idx++) { | |
var ds=this.getDSbyIdx(idx); | |
var ds_name=ds.getName() | |
ds_names.push(ds_name); | |
} | |
return ds_names; | |
} | |
RRDHeader.prototype.getDSbyIdx = function(idx) { | |
if ((idx>=0) && (idx<this.ds_cnt)) { | |
return new RRDDS(this.rrd_data,this.ds_def_idx+this.ds_el_size*idx,idx); | |
} else { | |
throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_cnt +")."); | |
} | |
} | |
RRDHeader.prototype.getDSbyName = function(name) { | |
for (var idx=0; idx<this.ds_cnt; idx++) { | |
var ds=this.getDSbyIdx(idx); | |
var ds_name=ds.getName() | |
if (ds_name==name) | |
return ds; | |
} | |
throw RangeError("DS name "+ name +" unknown."); | |
} | |
RRDHeader.prototype.getNrRRAs = function() { | |
return this.rra_cnt; | |
} | |
RRDHeader.prototype.getRRAInfo = function(idx) { | |
if ((idx>=0) && (idx<this.rra_cnt)) { | |
return new RRDRRAInfo(this.rrd_data, | |
this.rra_def_idx+idx*this.rra_def_el_size, | |
this.int_align,this.rra_def_row_cnts[idx],this.pdp_step, | |
idx); | |
} else { | |
throw RangeError("RRA idx ("+ idx +") out of range [0-" + this.rra_cnt +")."); | |
} | |
} | |
// ============================================================ | |
// RRDFile class | |
// Given a BinaryFile, gives access to the RRD archive fields | |
// | |
// Arguments: | |
// bf must be an object compatible with the BinaryFile interface | |
// file_options - currently no semantics... introduced for future expandability | |
function RRDFile(bf,file_options) { | |
this.file_options=file_options; | |
var rrd_data=bf | |
this.rrd_header=new RRDHeader(rrd_data); | |
this.rrd_header.load_row_cnts(); | |
// =================================== | |
// Start of user functions | |
this.getMinStep = function() { | |
return this.rrd_header.getMinStep(); | |
} | |
this.getLastUpdate = function() { | |
return this.rrd_header.getLastUpdate(); | |
} | |
this.getNrDSs = function() { | |
return this.rrd_header.getNrDSs(); | |
} | |
this.getDSNames = function() { | |
return this.rrd_header.getDSNames(); | |
} | |
this.getDS = function(id) { | |
if (typeof id == "number") { | |
return this.rrd_header.getDSbyIdx(id); | |
} else { | |
return this.rrd_header.getDSbyName(id); | |
} | |
} | |
this.getNrRRAs = function() { | |
return this.rrd_header.getNrRRAs(); | |
} | |
this.getRRAInfo = function(idx) { | |
return this.rrd_header.getRRAInfo(idx); | |
} | |
this.getRRA = function(idx) { | |
rra_info=this.rrd_header.getRRAInfo(idx); | |
return new RRDRRA(rrd_data, | |
this.rrd_header.rra_ptr_idx+idx*this.rrd_header.rra_ptr_el_size, | |
rra_info, | |
this.rrd_header.header_size, | |
this.rrd_header.rra_def_row_cnt_sums[idx], | |
this.rrd_header.ds_cnt); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Filter classes for rrdFile | |
* They implement the same interface, but changing the content | |
* | |
* Part of the javascriptRRD package | |
* Copyright (c) 2009 Frank Wuerthwein, [email protected] | |
* | |
* Original repository: http://javascriptrrd.sourceforge.net/ | |
* | |
* MIT License [http://www.opensource.org/licenses/mit-license.php] | |
* | |
*/ | |
/* | |
* All filter classes must implement the following interface: | |
* getMinStep() | |
* getLastUpdate() | |
* getNrRRAs() | |
* getRRAInfo(rra_idx) | |
* getFilterRRA(rra_idx) | |
* getName() | |
* | |
* Where getFilterRRA returns an object implementing the following interface: | |
* getIdx() | |
* getNrRows() | |
* getStep() | |
* getCFName() | |
* getEl(row_idx) | |
* getElFast(row_idx) | |
* | |
*/ | |
// ================================================================ | |
// Filter out a subset of DSs (identified either by idx or by name) | |
// Internal | |
function RRDRRAFilterDS(rrd_rra,ds_list) { | |
this.rrd_rra=rrd_rra; | |
this.ds_list=ds_list; | |
} | |
RRDRRAFilterDS.prototype.getIdx = function() {return this.rrd_rra.getIdx();} | |
RRDRRAFilterDS.prototype.getNrRows = function() {return this.rrd_rra.getNrRows();} | |
RRDRRAFilterDS.prototype.getNrDSs = function() {return this.ds_list.length;} | |
RRDRRAFilterDS.prototype.getStep = function() {return this.rrd_rra.getStep();} | |
RRDRRAFilterDS.prototype.getCFName = function() {return this.rrd_rra.getCFName();} | |
RRDRRAFilterDS.prototype.getEl = function(row_idx,ds_idx) { | |
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) { | |
var real_ds_idx=this.ds_list[ds_idx].real_ds_idx; | |
return this.rrd_rra.getEl(row_idx,real_ds_idx); | |
} else { | |
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +")."); | |
} | |
} | |
RRDRRAFilterDS.prototype.getElFast = function(row_idx,ds_idx) { | |
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) { | |
var real_ds_idx=this.ds_list[ds_idx].real_ds_idx; | |
return this.rrd_rra.getElFast(row_idx,real_ds_idx); | |
} else { | |
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +")."); | |
} | |
} | |
// -------------------------------------------------- | |
// Public | |
// NOTE: This class is deprecated, use RRDFilterOp instead | |
function RRDFilterDS(rrd_file,ds_id_list) { | |
this.rrd_file=rrd_file; | |
this.ds_list=[]; | |
for (var i=0; i<ds_id_list.length; i++) { | |
var org_ds=rrd_file.getDS(ds_id_list[i]); | |
// must create a new copy, as the index has changed | |
var new_ds=new RRDDS(org_ds.rrd_data,org_ds.rrd_data_idx,i); | |
// then extend it to include the real RRD index | |
new_ds.real_ds_idx=org_ds.my_idx; | |
this.ds_list.push(new_ds); | |
} | |
} | |
RRDFilterDS.prototype.getMinStep = function() {return this.rrd_file.getMinStep();} | |
RRDFilterDS.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();} | |
RRDFilterDS.prototype.getNrDSs = function() {return this.ds_list.length;} | |
RRDFilterDS.prototype.getDSNames = function() { | |
var ds_names=[]; | |
for (var i=0; i<this.ds_list.length; i++) { | |
ds_names.push(ds_list[i].getName()); | |
} | |
return ds_names; | |
} | |
RRDFilterDS.prototype.getDS = function(id) { | |
if (typeof id == "number") { | |
return this.getDSbyIdx(id); | |
} else { | |
return this.getDSbyName(id); | |
} | |
} | |
// INTERNAL: Do not call directly | |
RRDFilterDS.prototype.getDSbyIdx = function(idx) { | |
if ((idx>=0) && (idx<this.ds_list.length)) { | |
return this.ds_list[idx]; | |
} else { | |
throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_list.length +")."); | |
} | |
} | |
// INTERNAL: Do not call directly | |
RRDFilterDS.prototype.getDSbyName = function(name) { | |
for (var idx=0; idx<this.ds_list.length; idx++) { | |
var ds=this.ds_list[idx]; | |
var ds_name=ds.getName() | |
if (ds_name==name) | |
return ds; | |
} | |
throw RangeError("DS name "+ name +" unknown."); | |
} | |
RRDFilterDS.prototype.getNrRRAs = function() {return this.rrd_file.getNrRRAs();} | |
RRDFilterDS.prototype.getRRAInfo = function(idx) {return this.rrd_file.getRRAInfo(idx);} | |
RRDFilterDS.prototype.getRRA = function(idx) {return new RRDRRAFilterDS(this.rrd_file.getRRA(idx),this.ds_list);} | |
// ================================================================ | |
// Filter out by using a user provided filter object | |
// The object must implement the following interface | |
// getName() - Symbolic name give to this function | |
// getDSName() - list of DSs used in computing the result (names or indexes) | |
// computeResult(val_list) - val_list contains the values of the requested DSs (in the same order) | |
// If the element is a string or a number, it will just use that ds | |
// Example class that implements the interface: | |
// function DoNothing(ds_name) { //Leaves the DS alone. | |
// this.getName = function() {return ds_name;} | |
// this.getDSNames = function() {return [ds_name];} | |
// this.computeResult = function(val_list) {return val_list[0];} | |
// } | |
// function sumDS(ds1_name,ds2_name) { //Sums the two DSs. | |
// this.getName = function() {return ds1_name+"+"+ds2_name;} | |
// this.getDSNames = function() {return [ds1_name,ds2_name];} | |
// this.computeResult = function(val_list) {return val_list[0]+val_list[1];} | |
// } | |
// | |
// So to add a summed DS of your 1st and second DS: | |
// var ds0_name = rrd_data.getDS(0).getName(); | |
// var ds1_name = rrd_data.getDS(1).getName(); | |
// rrd_data = new RRDFilterOp(rrd_data, [new DoNothing(ds0_name), | |
// DoNothing(ds1_name), sumDS(ds0_name, ds1_name]); | |
// | |
// You get the same resoult with | |
// rrd_data = new RRDFilterOp(rrd_data, [ds0_name,1,new sumDS(ds0_name, ds1_name)]); | |
//////////////////////////////////////////////////////////////////// | |
// this implements the conceptual NoNothing above | |
function RRDFltOpIdent(ds_name) { | |
this.getName = function() {return ds_name;} | |
this.getDSNames = function() {return [ds_name];} | |
this.computeResult = function(val_list) {return val_list[0];} | |
} | |
// similar to the above, but extracts the name from the index | |
// requires two parametes, since it it need context | |
function RRDFltOpIdentId(rrd_data,id) { | |
this.ds_name=rrd_data.getDS(id).getName(); | |
this.getName = function() {return this.ds_name;} | |
this.getDSNames = function() {return [this.ds_name];} | |
this.computeResult = function(val_list) {return val_list[0];} | |
} | |
//Private | |
function RRDDSFilterOp(rrd_file,op_obj,my_idx) { | |
this.rrd_file=rrd_file; | |
this.op_obj=op_obj; | |
this.my_idx=my_idx; | |
var ds_names=op_obj.getDSNames(); | |
var ds_idx_list=[]; | |
for (var i=0; i<ds_names.length; i++) { | |
ds_idx_list.push(rrd_file.getDS(ds_names[i]).getIdx()); | |
} | |
this.ds_idx_list=ds_idx_list; | |
} | |
RRDDSFilterOp.prototype.getIdx = function() {return this.my_idx;} | |
RRDDSFilterOp.prototype.getName = function() {return this.op_obj.getName();} | |
RRDDSFilterOp.prototype.getType = function() {return "function";} | |
RRDDSFilterOp.prototype.getMin = function() {return undefined;} | |
RRDDSFilterOp.prototype.getMax = function() {return undefined;} | |
// These are new to RRDDSFilterOp | |
RRDDSFilterOp.prototype.getRealDSList = function() { return this.ds_idx_list;} | |
RRDDSFilterOp.prototype.computeResult = function(val_list) {return this.op_obj.computeResult(val_list);} | |
// ------ -------------------------------------------- | |
//Private | |
function RRDRRAFilterOp(rrd_rra,ds_list) { | |
this.rrd_rra=rrd_rra; | |
this.ds_list=ds_list; | |
} | |
RRDRRAFilterOp.prototype.getIdx = function() {return this.rrd_rra.getIdx();} | |
RRDRRAFilterOp.prototype.getNrRows = function() {return this.rrd_rra.getNrRows();} | |
RRDRRAFilterOp.prototype.getNrDSs = function() {return this.ds_list.length;} | |
RRDRRAFilterOp.prototype.getStep = function() {return this.rrd_rra.getStep();} | |
RRDRRAFilterOp.prototype.getCFName = function() {return this.rrd_rra.getCFName();} | |
RRDRRAFilterOp.prototype.getEl = function(row_idx,ds_idx) { | |
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) { | |
var ds_idx_list=this.ds_list[ds_idx].getRealDSList(); | |
var val_list=[]; | |
for (var i=0; i<ds_idx_list.length; i++) { | |
val_list.push(this.rrd_rra.getEl(row_idx,ds_idx_list[i])); | |
} | |
return this.ds_list[ds_idx].computeResult(val_list); | |
} else { | |
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +")."); | |
} | |
} | |
RRDRRAFilterOp.prototype.getElFast = function(row_idx,ds_idx) { | |
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) { | |
var ds_idx_list=this.ds_list[ds_idx].getRealDSList(); | |
var val_list=[]; | |
for (var i=0; i<ds_idx_list.length; i++) { | |
val_list.push(this.rrd_rra.getEl(row_idx,ds_idx_list[i])); | |
} | |
return this.ds_list[ds_idx].computeResult(val_list); | |
} else { | |
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +")."); | |
} | |
} | |
// -------------------------------------------------- | |
//Public | |
function RRDFilterOp(rrd_file,op_obj_list) { | |
this.rrd_file=rrd_file; | |
this.ds_list=[]; | |
for (i in op_obj_list) { | |
var el=op_obj_list[i]; | |
var outel=null; | |
if (typeof(el)=="string") {outel=new RRDFltOpIdent(el);} | |
else if (typeof(el)=="number") {outel=new RRDFltOpIdentId(this.rrd_file,el);} | |
else {outel=el;} | |
this.ds_list.push(new RRDDSFilterOp(rrd_file,outel,i)); | |
} | |
} | |
RRDFilterOp.prototype.getMinStep = function() {return this.rrd_file.getMinStep();} | |
RRDFilterOp.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();} | |
RRDFilterOp.prototype.getNrDSs = function() {return this.ds_list.length;} | |
RRDFilterOp.prototype.getDSNames = function() { | |
var ds_names=[]; | |
for (var i=0; i<this.ds_list.length; i++) { | |
ds_names.push(ds_list[i].getName()); | |
} | |
return ds_names; | |
} | |
RRDFilterOp.prototype.getDS = function(id) { | |
if (typeof id == "number") { | |
return this.getDSbyIdx(id); | |
} else { | |
return this.getDSbyName(id); | |
} | |
} | |
// INTERNAL: Do not call directly | |
RRDFilterOp.prototype.getDSbyIdx = function(idx) { | |
if ((idx>=0) && (idx<this.ds_list.length)) { | |
return this.ds_list[idx]; | |
} else { | |
throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_list.length +")."); | |
} | |
} | |
// INTERNAL: Do not call directly | |
RRDFilterOp.prototype.getDSbyName = function(name) { | |
for (var idx=0; idx<this.ds_list.length; idx++) { | |
var ds=this.ds_list[idx]; | |
var ds_name=ds.getName() | |
if (ds_name==name) | |
return ds; | |
} | |
throw RangeError("DS name "+ name +" unknown."); | |
} | |
RRDFilterOp.prototype.getNrRRAs = function() {return this.rrd_file.getNrRRAs();} | |
RRDFilterOp.prototype.getRRAInfo = function(idx) {return this.rrd_file.getRRAInfo(idx);} | |
RRDFilterOp.prototype.getRRA = function(idx) {return new RRDRRAFilterOp(this.rrd_file.getRRA(idx),this.ds_list);} | |
// ================================================================ | |
// NOTE: This function is archaic, and will likely be deprecated in future releases | |
// | |
// Shift RRAs in rra_list by the integer shift_int (in seconds). | |
// Only change is getLastUpdate - this takes care of everything. | |
// Example: To shift the first three 3 RRAs in the file by one hour, | |
// rrd_data = new RRAFilterShift(rra_data, 3600, [0,1,2]); | |
function RRAFilterShift(rrd_file, shift_int, rra_list) { | |
this.rrd_file = rrd_file; | |
this.shift_int = shift_int; | |
this.rra_list = rra_list; | |
this.shift_in_seconds = this.shift_int*3600; //number of steps needed to move 1 hour | |
} | |
RRAFilterShift.prototype.getMinStep = function() {return this.rrd_file.getMinStep();} | |
RRAFilterShift.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate()+this.shift_in_seconds;} | |
RRAFilterShift.prototype.getNrDSs = function() {return this.rrd_file.getNrDSs();} | |
RRAFilterShift.prototype.getDSNames = function() {return this.rrd_file.getDSNames();} | |
RRAFilterShift.prototype.getDS = function(id) {return this.rrd_file.getDS(id);} | |
RRAFilterShift.prototype.getNrRRAs = function() {return this.rra_list.length;} | |
RRAFilterShift.prototype.getRRAInfo = function(idx) {return this.rrd_file.getRRAInfo(idx);} | |
RRAFilterShift.prototype.getRRA = function(idx) {return this.rrd_file.getRRA(idx);} | |
// ================================================================ | |
// Filter RRAs by using a user provided filter object | |
// The object must implement the following interface | |
// getIdx() - Index of RRA to use | |
// getStep() - new step size (return null to use step size of RRA specified by getIdx() | |
// If a number is passed, it implies to just use the RRA as it is | |
// If an array is passed, it is assumed to be [rra_id,new_step_in_seconds] | |
// and a RRDRRAFltAvgOpNewStep object will be instantiated internally | |
/* Example classes that implements the interface: | |
* | |
* //This RRA Filter object leaves the original RRA unchanged. | |
* | |
* function RRADoNothing(rra_idx) { | |
* this.getIdx = function() {return rra_idx;} | |
* this.getStep = function() {return null;} | |
* } | |
* | |
* // This Filter creates a new RRA with a different step size | |
* // based on another RRA, whose data the new RRA averages. | |
* // rra_idx should be index of RRA with largest step size | |
* // that doesn't exceed new step size. | |
* | |
* function RRA_Avg(rra_idx,new_step_in_seconds) { | |
* this.getIdx = function() {return rra_idx;} | |
* this.getStep = function() {return new_step_in_seconds;} | |
* } | |
* //For example, if you have two RRAs, one with a 5 second step, | |
* //and another with a 60 second step, and you'd like a 30 second step, | |
* //rrd_data = new RRDRRAFilterAvg(rrd_data,[new RRADoNothing(0), new RRDDoNothing(1),new RRA_Avg(1,30)];) | |
*/ | |
// Users can use this one directly for simple use cases | |
// It is equivalent to RRADoNothing and RRA_Avg above | |
function RRDRRAFltAvgOpNewStep(rra_idx,new_step_in_seconds) { | |
this.getIdx = function() {return rra_idx;} | |
this.getStep = function() {return new_step_in_seconds;} | |
} | |
//Private Function | |
function RRAInfoFilterAvg(rrd_file, rra, op_obj, idx) { | |
this.rrd_file = rrd_file; | |
this.op_obj = op_obj; | |
this.base_rra = rrd_file.getRRA(this.op_obj.getIdx()); | |
this.rra = rra; | |
this.idx = idx; | |
var scaler = 1; | |
if (this.op_obj.getStep()!=null) {scaler = this.op_obj.getStep()/this.base_rra.getStep();} | |
this.scaler = scaler; | |
} | |
RRAInfoFilterAvg.prototype.getIdx = function() {return this.idx;} | |
RRAInfoFilterAvg.prototype.getNrRows = function() {return this.rra.getNrRows();} //draw info from RRAFilterAvg | |
RRAInfoFilterAvg.prototype.getStep = function() {return this.rra.getStep();} | |
RRAInfoFilterAvg.prototype.getCFName = function() {return this.rra.getCFName();} | |
RRAInfoFilterAvg.prototype.getPdpPerRow = function() {return this.rrd_file.getRRAInfo(this.op_obj.getIdx()).getPdpPerRow()*this.scaler;} | |
//--------------------------------------------------------------------------- | |
//Private Function | |
function RRAFilterAvg(rrd_file, op_obj) { | |
this.rrd_file = rrd_file; | |
this.op_obj = op_obj; | |
this.base_rra = rrd_file.getRRA(op_obj.getIdx()); | |
var scaler=1; | |
if (op_obj.getStep()!=null) {scaler = op_obj.getStep()/this.base_rra.getStep();} | |
this.scaler = Math.floor(scaler); | |
//document.write(this.scaler+","); | |
} | |
RRAFilterAvg.prototype.getIdx = function() {return this.op_obj.getIdx();} | |
RRAFilterAvg.prototype.getCFName = function() {return this.base_rra.getCFName();} | |
RRAFilterAvg.prototype.getNrRows = function() {return Math.floor(this.base_rra.getNrRows()/this.scaler);} | |
RRAFilterAvg.prototype.getNrDSs = function() {return this.base_rra.getNrDSs();} | |
RRAFilterAvg.prototype.getStep = function() { | |
if(this.op_obj.getStep()!=null) { | |
return this.op_obj.getStep(); | |
} else { return this.base_rra.getStep();} | |
} | |
RRAFilterAvg.prototype.getEl = function(row,ds) { | |
var sum=0; | |
for(var i=0;i<this.scaler;i++) { | |
sum += this.base_rra.getEl((this.scaler*row)+i,ds); | |
} | |
return sum/this.scaler; | |
} | |
RRAFilterAvg.prototype.getElFast = function(row,ds) { | |
var sum=0; | |
for(var i=0;i<this.scaler;i++) { | |
sum += this.base_rra.getElFast((this.scaler*row)+i,ds); | |
} | |
return sum/this.scaler; | |
} | |
//---------------------------------------------------------------------------- | |
//Public function - use this one for RRA averaging | |
function RRDRRAFilterAvg(rrd_file, op_obj_list) { | |
this.rrd_file = rrd_file; | |
this.op_obj_list = new Array(); | |
this.rra_list=[]; | |
for (var i in op_obj_list) { | |
var el=op_obj_list[i]; | |
var outel=null; | |
if (Object.prototype.toString.call(el)=="[object Number]") {outel=new RRDRRAFltAvgOpNewStep(el,null);} | |
else if (Object.prototype.toString.call(el)=="[object Array]") {outel=new RRDRRAFltAvgOpNewStep(el[0],el[1]);} | |
else {outel=el;} | |
this.op_obj_list.push(outel); | |
this.rra_list.push(new RRAFilterAvg(rrd_file,outel)); | |
} | |
} | |
RRDRRAFilterAvg.prototype.getMinStep = function() {return this.rrd_file.getMinStep();} //other RRA steps change, not min | |
RRDRRAFilterAvg.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();} | |
RRDRRAFilterAvg.prototype.getNrDSs = function() {return this.rrd_file.getNrDSs();} //DSs unchanged | |
RRDRRAFilterAvg.prototype.getDSNames = function() {return this.rrd_file.getDSNames();} | |
RRDRRAFilterAvg.prototype.getDS = function(id) {return this.rrd_file.getDS(id);} | |
RRDRRAFilterAvg.prototype.getNrRRAs = function() {return this.rra_list.length;} | |
RRDRRAFilterAvg.prototype.getRRAInfo = function(idx) { | |
if ((idx>=0) && (idx<this.rra_list.length)) { | |
return new RRAInfoFilterAvg(this.rrd_file, this.rra_list[idx],this.op_obj_list[idx],idx); | |
} else {return this.rrd_file.getRRAInfo(0);} | |
} | |
RRDRRAFilterAvg.prototype.getRRA = function(idx) { | |
if ((idx>=0) && (idx<this.rra_list.length)) { | |
return this.rra_list[idx]; | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html { | |
min-width: 1040px; | |
} | |
body { | |
font-family: "Helvetica Neue", Helvetica, sans-serif; | |
margin: 1em auto 4em auto; | |
position: relative; | |
tab-size: 2; | |
width: 960px; | |
} | |
h1 { | |
font-size: 64px; | |
font-weight: 300; | |
letter-spacing: -2px; | |
margin: .3em 0 .1em 0; | |
} | |
h2 { | |
margin-top: 2em; | |
} | |
h1, h2 { | |
text-rendering: optimizeLegibility; | |
} | |
h2 a { | |
color: #ccc; | |
left: -20px; | |
position: absolute; | |
width: 740px; | |
} | |
svg { | |
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif; | |
} | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #3c3c3c; | |
shape-rendering: crispEdges; | |
} | |
.line { | |
fill: none; | |
stroke-linejoin: round; | |
stroke-linecap: round; | |
stroke-width: 2px; | |
} | |
.voronoi path { | |
fill: none; | |
pointer-events: all; | |
} | |
.voronoi--show path { | |
stroke: red; | |
stroke-opacity: .2; | |
} | |
.legend-text { | |
text-transform: capitalize; | |
white-space: pre; | |
} | |
.focus text { | |
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff; | |
} | |
.focus text.text--date { | |
text-anchor: start; | |
} | |
.focus text.text--value { | |
text-anchor: end; | |
} | |
#stats { | |
width: 100%; | |
border: 2px solid black; | |
border-collapse: collapse; | |
margin: 5% 10% 10% 5%; | |
} | |
#stats td { | |
padding: 5px; | |
border: 1px solid black; | |
text-align: right; | |
} | |
#stats td:first-child { | |
text-align: left; | |
text-transform: capitalize; | |
} | |
#stats tr:hover td { | |
background-color: aliceblue; | |
} | |
pre, code, textarea { | |
font-family: "Menlo", monospace; | |
line-height: normal; | |
} | |
textarea { | |
font-size: 100%; | |
} | |
pre { | |
border-left: solid 2px #ccc; | |
padding-left: 18px; | |
margin: 2em 0 2em -20px; | |
width: 960px; | |
overflow-x: auto; | |
} | |
.html .value, | |
.javascript .string, | |
.javascript .regexp { | |
color: #756bb1; | |
} | |
.html .tag, | |
.css .tag, | |
.javascript .keyword { | |
color: #3182bd; | |
} | |
aside, | |
.comment { | |
color: #636363; | |
} | |
.html .doctype, | |
.javascript .number { | |
color: #31a354; | |
} | |
.html .attribute, | |
.css .attribute, | |
.javascript .class, | |
.javascript .special { | |
color: #e6550d; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment