// import MochiKit
// import MochiKit.Base
// import MochiKit.Iter
// import MochiKit.DOM
// import PlotKit.Base
// import PlotKit.Layout
// import PlotKit.Canvas
// import PlotKit.SweetCanvas
// import Mantissa
Mantissa.StatGraph.Pie = Divmod.Class.subclass();
/* Mantissa.StatGraph.Pie:
I represent a pie chart drawn with PlotKit.
*/
Mantissa.StatGraph.Pie.methods(
function __init__(self, canvas) {
/** canvas: a canvas DOM node. */
self.canvas = canvas;
canvas.height = 900;
canvas.width = 900;
self.layout = new PlotKit.Layout("pie");
self.graph = new PlotKit.SweetCanvasRenderer(self.canvas, self.layout, {'axisLabelWidth':160});
},
function draw(self, slices) {
/** Actually render the piechart.
slices: a pair of lists: labels for slices (strings), and slice values (floats).
*/
self.layout.options.xTicks = MochiKit.Base.map(function(L, val) { return {"label": L, v: val};}, slices[0],
MochiKit.Iter.range(slices[0].length));
self.layout.addDataset("data", MochiKit.Base.zip(MochiKit.Iter.range(slices[1].length), slices[1]));
self.layout.evaluate();
self.graph.clear();
self.graph.render();
});
Mantissa.StatGraph.GraphData = Divmod.Class.subclass();
/** Mantissa.StatGraph.GraphData:
I represent a line chart drawn with PlotKit.
*/
Mantissa.StatGraph.GraphData.methods(
function __init__(self, canvas) {
/** canvas: a canvas DOM node. */
self.plots = {}
self.xs = null;
self.canvas = canvas;
var xticks = [];
self.layout = new PlotKit.Layout("line", {xTicks: xticks});
self.graph = new PlotKit.SweetCanvasRenderer(self.canvas, self.layout, {});
},
function addPlot(self, name, xs, ys) {
/** Add a line to be plotted on this graph.
name: An identifier for this data set.
xs: A list of X-axis labels (strings). If adding more than one
plot, this list needs to be equal to the current X axis.
ys: A list of Y values for this plot (floats).
*/
if (self.xs == null) {
self.xs = xs;
} else if (!MochiKit.Base.arrayEqual(xs, self.xs)) {
throw TypeError(name + "has different timescale from existing plots");
}
self.plots[name] = ys;
},
function _updateXTicks(self) {
var allXTicks = MochiKit.Base.map(function(L, val) { return {"label": L, v: val};}, self.xs,
MochiKit.Iter.range(self.xs.length));
// XXX find a better way to do this maybe?
var len = allXTicks.length;
self.layout.options.xTicks.length = 0;
if (len > 5) {
for (var i = 0; i < len; i += Math.floor(len/4)) {
self.layout.options.xTicks.push(allXTicks[i]);
}
} else {
self.layout.options.xTicks = allXTicks;
}
},
function draw(self) {
/** Draw this graph on its canvas. */
var doPlots = function(name, plot) {
self.layout.addDataset(name, MochiKit.Base.map(null, MochiKit.Iter.range(self.xs.length),
plot));
};
MochiKit.Iter.exhaust(MochiKit.Iter.applymap(doPlots, MochiKit.Base.items(self.plots)));
self._updateXTicks();
self.layout.evaluate();
self.graph.clear();
self.graph.render();
});
Mantissa.StatGraph.StatGraph = Nevow.Athena.Widget.subclass("Mantissa.StatGraph.StatGraph");
/** Mantissa.StatGraph.StatGraph: I control the display of statistics graphs on
the Stats page of the Mantissa admin console, and receive updates from
stats collectors on the server.
*/
Mantissa.StatGraph.StatGraph.methods(
function __init__(self, node) {
Mantissa.StatGraph.StatGraph.upcall(self, '__init__', node);
self.graphs = {};
self.pieMode = "pie";
self.pie = null;
self.callRemote('getGraphNames').addCallback(
function (names) {
var graphSelector = MochiKit.DOM.SELECT({}, MochiKit.Iter.chain([MochiKit.DOM.OPTION({},""),
MochiKit.DOM.OPTION({"value": "pie"},
"Query time piechart")],
MochiKit.Base.map(
function (name) {
return MochiKit.DOM.OPTION(
{"value": name[0]},
name[1])
}, names)));
graphSelector.onchange = function () {
var name = graphSelector[graphSelector.selectedIndex].value;
var desc = graphSelector[graphSelector.selectedIndex].text;
if (name == "pie") {
self.callRemote('buildPie').addCallback(function(slices) {self.drawPie(slices)});
} else if (name != "") {
self.callRemote('addStat',name).addCallback(
function (data) { node.appendChild(self.newGraph(name, desc, data));});
}};
self.node.appendChild(graphSelector);
});
},
function drawPie(self, slices) {
/** Draw a pie chart with a selector for the time period it represents,
and a toggle to show a table of the same data instead.
*/
var g = new Mantissa.StatGraph.Pie(self._newCanvas("Pie!"));
self.pie = g;
var p = g.canvas.parentNode;
var details = MochiKit.DOM.A({"onclick": function () { self.togglePieOrTable(self.pie, p)},
"class": "sublink", "style": "cursor: pointer"},
"Toggle Table/Pie");
g.canvas.parentNode.insertBefore(details, g.canvas);
var periodSelector = MochiKit.DOM.SELECT({}, MochiKit.Base.map(
function(x) {return MochiKit.DOM.OPTION({"value":x[1]}, x[0])},
[["60 minutes", 60], ["30 minutes", 30], ["15 minutes", 15]]));
periodSelector.onchange = function () {
self.callRemote('setPiePeriod',
periodSelector[periodSelector.selectedIndex].value);
self.callRemote('buildPie').addCallback(
function (slices) {self.slices = slices;
if (self.pieMode == "pie") {
g.draw(slices);
} else {
p.removeChild(p.lastChild);
self.table = self.makeSliceTable();
p.appendChild(self.table);
}})};
g.canvas.parentNode.insertBefore(periodSelector, g.canvas);
self.slices = slices;
g.draw(slices);
},
function newGraph(self, name, desc, data) {
/** Add a new line graph to the page.
name: An identifier for this graph.
desc: A title to display above the graph.
data: A pair of lists, containing X-axis labels and Y-axis data points.
*/
var canvas = self._newCanvas(desc);
var graphdata = Mantissa.StatGraph.GraphData(canvas);
graphdata.addPlot(name, data[0], data[1]);
graphdata.draw();
self.graphs[name] = graphdata;
},
function makeSliceTable(self) {
var pairs = MochiKit.Base.map(function (pair) { return [MochiKit.DOM.TD({}, pair[0]),
MochiKit.DOM.TD({}, pair[1])] },
MochiKit.Base.zip(self.slices[0], self.slices[1]));
var trs = MochiKit.Base.map(MochiKit.Base.partial(MochiKit.DOM.TR, null), pairs);
var tbody = MochiKit.DOM.TBODY({}, trs);
var t= MochiKit.DOM.TABLE({}, [MochiKit.DOM.THEAD({}, MochiKit.DOM.TR({},
MochiKit.DOM.TD({}, "Source"),
MochiKit.DOM.TD({}, "Time"))),
tbody]);
return t;
},
function togglePieOrTable(self, g, p) {
if (self.pieMode == "pie") {
g.graph.clear();
p.removeChild(g.canvas);
t = self.makeSliceTable();
p.appendChild(t);
self.table = t;
self.pieMode = "table";
self.pie = null;
} else {
p.removeChild(p.lastChild); // that's the table, right?
var canvas = document.createElement('canvas');
p.appendChild(canvas);
self.pie = new Mantissa.StatGraph.Pie(canvas);
self.pie.draw(self.slices);
self.pieMode = "pie";
self.table = null;
}
},
function _newCanvas(self, title) {
var container = document.createElement('div');
var t = document.createElement('div');
var container2 = document.createElement('div');
var canvas = document.createElement("canvas");
t.appendChild(document.createTextNode(title));
container.appendChild(t);
container.appendChild(container2);
container2.appendChild(canvas);
container2.style.width = "500px";
t.style.textAlign = "center";
t.style.width = "500px";
canvas.width = 500;
canvas.height = 200;
self.node.appendChild(container);
return canvas;
},
function update(self, time, updates) {
/** Add a new data point to the displayed graphs.
time: The time associated with this datum.
updates: A mapping of graph identifiers to new data points.
*/
var ps = MochiKit.Iter.chain.apply(MochiKit.Iter.chain,
MochiKit.Base.map(function (g) {return MochiKit.Base.items(g[1].plots)},
MochiKit.Base.items(self.graphs)));
MochiKit.Iter.exhaust(MochiKit.Iter.applymap(function (plotName, ys) {
ys.push(updates[plotName]);
if (ys.length > 60) {
ys.shift();
}
}, ps));
MochiKit.Base.map(function (x) {
var g = x[1];
g.xs.push(time);
if (g.xs.length > 60) {
g.xs.shift();
};
g.draw();
}, MochiKit.Base.items(self.graphs));
},
function updatePie(self, slices) {
/** Update the pie chart (if displayed)
with these new slices.
*/
if (self.pie == null) {
return;
}
self.slices = slices;
if (self.pieMode == "pie") {
self.pie.draw(slices);
} else {
var oldtable = self.table;
self.table = self.makeSliceTable();
oldtable.parentNode.replaceChild(self.table, oldtable);
}
});
syntax highlighted by Code2HTML, v. 0.9.1