Index: static/js/plotkit/Layout.js |
diff --git a/static/js/plotkit/Layout.js b/static/js/plotkit/Layout.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..776813d0e74de2b3d15fa8f2f612c94ef05d16ae |
--- /dev/null |
+++ b/static/js/plotkit/Layout.js |
@@ -0,0 +1,753 @@ |
+/* |
+ ======= |
+ PlotKit is a collection of Javascript classes that allows |
+ you to quickly visualise data using different types of charts. |
+ |
+ http://www.liquidx.net/plotkit/ |
+ |
+ See LICENSE file for copyright information. |
+ |
+ Handles laying out data on to a virtual canvas square canvas between 0.0 |
+ and 1.0. If you want to add new chart/plot types such as point plots, |
+ you need to add them here. |
+*/ |
+ |
+try { |
+ if (typeof(PlotKit.Base) == 'undefined') |
+ { |
+ throw "" |
+ } |
+} |
+catch (e) { |
+ throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base" |
+} |
+ |
+// -------------------------------------------------------------------- |
+// Start of Layout definition |
+// -------------------------------------------------------------------- |
+ |
+if (typeof(PlotKit.Layout) == 'undefined') { |
+ PlotKit.Layout = {}; |
+} |
+ |
+PlotKit.Layout.NAME = "PlotKit.Layout"; |
+PlotKit.Layout.VERSION = PlotKit.VERSION; |
+ |
+PlotKit.Layout.__repr__ = function() { |
+ return "[" + this.NAME + " " + this.VERSION + "]"; |
+}; |
+ |
+PlotKit.Layout.toString = function() { |
+ return this.__repr__(); |
+} |
+ |
+PlotKit.Layout.valid_styles = ["bar", "line", "pie", "point"]; |
+ |
+// -------------------------------------------------------------------- |
+// Start of Layout definition |
+// -------------------------------------------------------------------- |
+ |
+PlotKit.Layout = function(style, options) { |
+ |
+ this.options = { |
+ "barWidthFillFraction": 0.75, |
+ "barOrientation": "vertical", |
+ "xOriginIsZero": true, |
+ "yOriginIsZero": true, |
+ "xAxis": null, // [xmin, xmax] |
+ "yAxis": null, // [ymin, ymax] |
+ "xTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.) |
+ "yTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.) |
+ "xNumberOfTicks": 10, |
+ "yNumberOfTicks": 5, |
+ "xTickPrecision": 1, |
+ "yTickPrecision": 1, |
+ "pieRadius": 0.4 |
+ }; |
+ |
+ // valid external options : TODO: input verification |
+ this.style = style; |
+ MochiKit.Base.update(this.options, options ? options : {}); |
+ |
+ // externally visible states |
+ // overriden if xAxis and yAxis are set in options |
+ if (!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)) { |
+ this.minxval = this.options.xAxis[0]; |
+ this.maxxval = this.options.xAxis[1]; |
+ this.xscale = this.maxxval - this.minxval; |
+ } |
+ else { |
+ this.minxval = 0; |
+ this.maxxval = null; |
+ this.xscale = null; // val -> pos factor (eg, xval * xscale = xpos) |
+ } |
+ |
+ if (!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)) { |
+ this.minyval = this.options.yAxis[0]; |
+ this.maxyval = this.options.yAxis[1]; |
+ this.yscale = this.maxyval - this.minyval; |
+ } |
+ else { |
+ this.minyval = 0; |
+ this.maxyval = null; |
+ this.yscale = null; |
+ } |
+ |
+ this.bars = new Array(); // array of bars to plot for bar charts |
+ this.points = new Array(); // array of points to plot for line plots |
+ this.slices = new Array(); // array of slices to draw for pie charts |
+ |
+ this.xticks = new Array(); |
+ this.yticks = new Array(); |
+ |
+ // internal states |
+ this.datasets = new Array(); |
+ this.minxdelta = 0; |
+ this.xrange = 1; |
+ this.yrange = 1; |
+ |
+ this.hitTestCache = {x2maxy: null}; |
+ |
+}; |
+ |
+// -------------------------------------------------------------------- |
+// Dataset Manipulation |
+// -------------------------------------------------------------------- |
+ |
+ |
+PlotKit.Layout.prototype.addDataset = function(setname, set_xy) { |
+ this.datasets[setname] = set_xy; |
+}; |
+ |
+PlotKit.Layout.prototype.removeDataset = function(setname, set_xy) { |
+ delete this.datasets[setname]; |
+}; |
+ |
+PlotKit.Layout.prototype.addDatasetFromTable = function(name, tableElement, xcol, ycol, lcol) { |
+ var isNil = MochiKit.Base.isUndefinedOrNull; |
+ var scrapeText = MochiKit.DOM.scrapeText; |
+ var strip = MochiKit.Format.strip; |
+ |
+ if (isNil(xcol)) |
+ xcol = 0; |
+ if (isNil(ycol)) |
+ ycol = 1; |
+ if (isNil(lcol)) |
+ lcol = -1; |
+ |
+ var rows = tableElement.tBodies[0].rows; |
+ var data = new Array(); |
+ var labels = new Array(); |
+ |
+ if (!isNil(rows)) { |
+ for (var i = 0; i < rows.length; i++) { |
+ data.push([parseFloat(strip(scrapeText(rows[i].cells[xcol]))), |
+ parseFloat(strip(scrapeText(rows[i].cells[ycol])))]); |
+ if (lcol >= 0){ |
+ labels.push({v: parseFloat(strip(scrapeText(rows[i].cells[xcol]))), |
+ label: strip(scrapeText(rows[i].cells[lcol]))}); |
+ } |
+ } |
+ this.addDataset(name, data); |
+ if (lcol >= 0) { |
+ this.options.xTicks = labels; |
+ } |
+ return true; |
+ } |
+ return false; |
+}; |
+ |
+// -------------------------------------------------------------------- |
+// Evaluates the layout for the current data and style. |
+// -------------------------------------------------------------------- |
+ |
+PlotKit.Layout.prototype.evaluate = function() { |
+ this._evaluateLimits(); |
+ this._evaluateScales(); |
+ if (this.style == "bar") { |
+ if (this.options.barOrientation == "horizontal") { |
+ this._evaluateHorizBarCharts(); |
+ } |
+ else { |
+ this._evaluateBarCharts(); |
+ } |
+ this._evaluateBarTicks(); |
+ } |
+ else if (this.style == "line") { |
+ this._evaluateLineCharts(); |
+ this._evaluateLineTicks(); |
+ } |
+ else if (this.style == "pie") { |
+ this._evaluatePieCharts(); |
+ this._evaluatePieTicks(); |
+ } |
+}; |
+ |
+ |
+ |
+// Given the fractional x, y positions, report the corresponding |
+// x, y values. |
+PlotKit.Layout.prototype.hitTest = function(x, y) { |
+ // TODO: make this more efficient with better datastructures |
+ // for this.bars, this.points and this.slices |
+ |
+ var f = MochiKit.Format.twoDigitFloat; |
+ |
+ if ((this.style == "bar") && this.bars && (this.bars.length > 0)) { |
+ for (var i = 0; i < this.bars.length; i++) { |
+ var bar = this.bars[i]; |
+ if ((x >= bar.x) && (x <= bar.x + bar.w) |
+ && (y >= bar.y) && (y - bar.y <= bar.h)) |
+ return bar; |
+ } |
+ } |
+ |
+ else if (this.style == "line") { |
+ if (this.hitTestCache.x2maxy == null) { |
+ this._regenerateHitTestCache(); |
+ } |
+ |
+ // 1. find the xvalues that equal or closest to the give x |
+ var xval = x / this.xscale; |
+ var xvalues = this.hitTestCache.xvalues; |
+ var xbefore = null; |
+ var xafter = null; |
+ |
+ for (var i = 1; i < xvalues.length; i++) { |
+ if (xvalues[i] > xval) { |
+ xbefore = xvalues[i-1]; |
+ xafter = xvalues[i]; |
+ break; |
+ } |
+ } |
+ |
+ if ((xbefore != null)) { |
+ var ybefore = this.hitTestCache.x2maxy[xbefore]; |
+ var yafter = this.hitTestCache.x2maxy[xafter]; |
+ var yval = (1.0 - y)/this.yscale; |
+ |
+ // interpolate whether we will fall inside or outside |
+ var gradient = (yafter - ybefore) / (xafter - xbefore); |
+ var projmaxy = ybefore + gradient * (xval - xbefore); |
+ if (projmaxy >= yval) { |
+ // inside the highest curve (roughly) |
+ var obj = {xval: xval, yval: yval, |
+ xafter: xafter, yafter: yafter, |
+ xbefore: xbefore, ybefore: ybefore, |
+ yprojected: projmaxy |
+ }; |
+ return obj; |
+ } |
+ } |
+ } |
+ |
+ else if (this.style == "pie") { |
+ var dist = Math.sqrt((y-0.5)*(y-0.5) + (x-0.5)*(x-0.5)); |
+ if (dist > this.options.pieRadius) |
+ return null; |
+ |
+ // TODO: actually doesn't work if we don't know how the Canvas |
+ // lays it out, need to fix! |
+ var angle = Math.atan2(y - 0.5, x - 0.5) - Math.PI/2; |
+ for (var i = 0; i < this.slices.length; i++) { |
+ var slice = this.slices[i]; |
+ if (slice.startAngle < angle && slice.endAngle >= angle) |
+ return slice; |
+ } |
+ } |
+ |
+ return null; |
+}; |
+ |
+// Reports valid position rectangle for X value (only valid for bar charts) |
+PlotKit.Layout.prototype.rectForX = function(x) { |
+ return null; |
+}; |
+ |
+// Reports valid angles through which X value encloses (only valid for pie charts) |
+PlotKit.Layout.prototype.angleRangeForX = function(x) { |
+ return null; |
+}; |
+ |
+// -------------------------------------------------------------------- |
+// START Internal Functions |
+// -------------------------------------------------------------------- |
+ |
+PlotKit.Layout.prototype._evaluateLimits = function() { |
+ // take all values from all datasets and find max and min |
+ var map = PlotKit.Base.map; |
+ var items = PlotKit.Base.items; |
+ var itemgetter = MochiKit.Base.itemgetter; |
+ var collapse = PlotKit.Base.collapse; |
+ var listMin = MochiKit.Base.listMin; |
+ var listMax = MochiKit.Base.listMax; |
+ var isNil = MochiKit.Base.isUndefinedOrNull; |
+ |
+ |
+ var all = collapse(map(itemgetter(1), items(this.datasets))); |
+ if (isNil(this.options.xAxis)) { |
+ if (this.options.xOriginIsZero) |
+ this.minxval = 0; |
+ else |
+ this.minxval = listMin(map(parseFloat, map(itemgetter(0), all))); |
+ |
+ this.maxxval = listMax(map(parseFloat, map(itemgetter(0), all))); |
+ } |
+ else { |
+ this.minxval = this.options.xAxis[0]; |
+ this.maxxval = this.options.xAxis[1]; |
+ this.xscale = this.maxval - this.minxval; |
+ } |
+ |
+ if (isNil(this.options.yAxis)) { |
+ if (this.options.yOriginIsZero) |
+ this.minyval = 0; |
+ else |
+ this.minyval = listMin(map(parseFloat, map(itemgetter(1), all))); |
+ |
+ this.maxyval = listMax(map(parseFloat, map(itemgetter(1), all))); |
+ } |
+ else { |
+ this.minyval = this.options.yAxis[0]; |
+ this.maxyval = this.options.yAxis[1]; |
+ this.yscale = this.maxyval - this.minyval; |
+ } |
+ |
+}; |
+ |
+PlotKit.Layout.prototype._evaluateScales = function() { |
+ var isNil = MochiKit.Base.isUndefinedOrNull; |
+ |
+ this.xrange = this.maxxval - this.minxval; |
+ if (this.xrange == 0) |
+ this.xscale = 1.0; |
+ else |
+ this.xscale = 1/this.xrange; |
+ |
+ this.yrange = this.maxyval - this.minyval; |
+ if (this.yrange == 0) |
+ this.yscale = 1.0; |
+ else |
+ this.yscale = 1/this.yrange; |
+}; |
+ |
+PlotKit.Layout.prototype._uniqueXValues = function() { |
+ var collapse = PlotKit.Base.collapse; |
+ var map = PlotKit.Base.map; |
+ var uniq = PlotKit.Base.uniq; |
+ var getter = MochiKit.Base.itemgetter; |
+ var items = PlotKit.Base.items; |
+ |
+ var xvalues = map(parseFloat, map(getter(0), collapse(map(getter(1), items(this.datasets))))); |
+ xvalues.sort(MochiKit.Base.compare); |
+ return uniq(xvalues); |
+}; |
+ |
+// Create the bars |
+PlotKit.Layout.prototype._evaluateBarCharts = function() { |
+ var items = PlotKit.Base.items; |
+ |
+ var setCount = items(this.datasets).length; |
+ |
+ // work out how far separated values are |
+ var xdelta = 10000000; |
+ var xvalues = this._uniqueXValues(); |
+ for (var i = 1; i < xvalues.length; i++) { |
+ xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta); |
+ } |
+ |
+ var barWidth = 0; |
+ var barWidthForSet = 0; |
+ var barMargin = 0; |
+ if (xvalues.length == 1) { |
+ // note we have to do something smarter if we only plot one value |
+ xdelta = 1.0; |
+ this.xscale = 1.0; |
+ this.minxval = xvalues[0]; |
+ barWidth = 1.0 * this.options.barWidthFillFraction; |
+ barWidthForSet = barWidth/setCount; |
+ barMargin = (1.0 - this.options.barWidthFillFraction)/2; |
+ } |
+ else { |
+ // readjust xscale to fix with bar charts |
+ if (this.xrange == 1) { |
+ this.xscale = 0.5; |
+ } |
+ else if (this.xrange == 2) { |
+ this.xscale = 1/3.0; |
+ } |
+ else { |
+ this.xscale = (1.0 - xdelta/this.xrange)/this.xrange; |
+ } |
+ barWidth = xdelta * this.xscale * this.options.barWidthFillFraction; |
+ barWidthForSet = barWidth / setCount; |
+ barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2; |
+ } |
+ |
+ this.minxdelta = xdelta; // need this for tick positions |
+ |
+ // add all the rects |
+ this.bars = new Array(); |
+ var i = 0; |
+ for (var setName in this.datasets) { |
+ var dataset = this.datasets[setName]; |
+ if (PlotKit.Base.isFuncLike(dataset)) continue; |
+ for (var j = 0; j < dataset.length; j++) { |
+ var item = dataset[j]; |
+ var rect = { |
+ x: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin, |
+ y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale), |
+ w: barWidthForSet, |
+ h: ((parseFloat(item[1]) - this.minyval) * this.yscale), |
+ xval: parseFloat(item[0]), |
+ yval: parseFloat(item[1]), |
+ name: setName |
+ }; |
+ if ((rect.x >= 0.0) && (rect.x <= 1.0) && |
+ (rect.y >= 0.0) && (rect.y <= 1.0)) { |
+ this.bars.push(rect); |
+ } |
+ } |
+ i++; |
+ } |
+}; |
+ |
+// Create the horizontal bars |
+PlotKit.Layout.prototype._evaluateHorizBarCharts = function() { |
+ var items = PlotKit.Base.items; |
+ |
+ var setCount = items(this.datasets).length; |
+ |
+ // work out how far separated values are |
+ var xdelta = 10000000; |
+ var xvalues = this._uniqueXValues(); |
+ for (var i = 1; i < xvalues.length; i++) { |
+ xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta); |
+ } |
+ |
+ var barWidth = 0; |
+ var barWidthForSet = 0; |
+ var barMargin = 0; |
+ |
+ // work out how far each far each bar is separated |
+ if (xvalues.length == 1) { |
+ // do something smarter if we only plot one value |
+ xdelta = 1.0; |
+ this.xscale = 1.0; |
+ this.minxval = xvalues[0]; |
+ barWidth = 1.0 * this.options.barWidthFillFraction; |
+ barWidthForSet = barWidth/setCount; |
+ barMargin = (1.0 - this.options.barWidthFillFraction)/2; |
+ } |
+ else { |
+ // readjust yscale to fix with bar charts |
+ this.xscale = (1.0 - xdelta/this.xrange)/this.xrange; |
+ barWidth = xdelta * this.xscale * this.options.barWidthFillFraction; |
+ barWidthForSet = barWidth / setCount; |
+ barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2; |
+ } |
+ |
+ this.minxdelta = xdelta; // need this for tick positions |
+ |
+ // add all the rects |
+ this.bars = new Array(); |
+ var i = 0; |
+ for (var setName in this.datasets) { |
+ var dataset = this.datasets[setName]; |
+ if (PlotKit.Base.isFuncLike(dataset)) continue; |
+ for (var j = 0; j < dataset.length; j++) { |
+ var item = dataset[j]; |
+ var rect = { |
+ y: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin, |
+ x: 0.0, |
+ h: barWidthForSet, |
+ w: ((parseFloat(item[1]) - this.minyval) * this.yscale), |
+ xval: parseFloat(item[0]), |
+ yval: parseFloat(item[1]), |
+ name: setName |
+ }; |
+ |
+ // limit the x, y values so they do not overdraw |
+ if (rect.y <= 0.0) { |
+ rect.y = 0.0; |
+ } |
+ if (rect.y >= 1.0) { |
+ rect.y = 1.0; |
+ } |
+ if ((rect.x >= 0.0) && (rect.x <= 1.0)) { |
+ this.bars.push(rect); |
+ } |
+ } |
+ i++; |
+ } |
+}; |
+ |
+ |
+// Create the line charts |
+PlotKit.Layout.prototype._evaluateLineCharts = function() { |
+ var items = PlotKit.Base.items; |
+ |
+ var setCount = items(this.datasets).length; |
+ |
+ // add all the rects |
+ this.points = new Array(); |
+ var i = 0; |
+ for (var setName in this.datasets) { |
+ var dataset = this.datasets[setName]; |
+ if (PlotKit.Base.isFuncLike(dataset)) continue; |
+ dataset.sort(function(a, b) { return compare(parseFloat(a[0]), parseFloat(b[0])); }); |
+ for (var j = 0; j < dataset.length; j++) { |
+ var item = dataset[j]; |
+ var point = { |
+ x: ((parseFloat(item[0]) - this.minxval) * this.xscale), |
+ y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale), |
+ xval: parseFloat(item[0]), |
+ yval: parseFloat(item[1]), |
+ name: setName |
+ }; |
+ |
+ // limit the x, y values so they do not overdraw |
+ if (point.y <= 0.0) { |
+ point.y = 0.0; |
+ } |
+ if (point.y >= 1.0) { |
+ point.y = 1.0; |
+ } |
+ if ((point.x >= 0.0) && (point.x <= 1.0)) { |
+ this.points.push(point); |
+ } |
+ } |
+ i++; |
+ } |
+}; |
+ |
+// Create the pie charts |
+PlotKit.Layout.prototype._evaluatePieCharts = function() { |
+ var items = PlotKit.Base.items; |
+ var sum = MochiKit.Iter.sum; |
+ var getter = MochiKit.Base.itemgetter; |
+ |
+ var setCount = items(this.datasets).length; |
+ |
+ // we plot the y values of the first dataset |
+ var dataset = items(this.datasets)[0][1]; |
+ var total = sum(map(getter(1), dataset)); |
+ |
+ this.slices = new Array(); |
+ var currentAngle = 0.0; |
+ for (var i = 0; i < dataset.length; i++) { |
+ var fraction = dataset[i][1] / total; |
+ var startAngle = currentAngle * Math.PI * 2; |
+ var endAngle = (currentAngle + fraction) * Math.PI * 2; |
+ |
+ var slice = {fraction: fraction, |
+ xval: dataset[i][0], |
+ yval: dataset[i][1], |
+ startAngle: startAngle, |
+ endAngle: endAngle |
+ }; |
+ if (dataset[i][1] != 0) { |
+ this.slices.push(slice); |
+ } |
+ currentAngle += fraction; |
+ } |
+}; |
+ |
+PlotKit.Layout.prototype._evaluateLineTicksForXAxis = function() { |
+ var isNil = MochiKit.Base.isUndefinedOrNull; |
+ |
+ if (this.options.xTicks) { |
+ // we use use specified ticks with optional labels |
+ |
+ this.xticks = new Array(); |
+ var makeTicks = function(tick) { |
+ var label = tick.label; |
+ if (isNil(label)) |
+ label = tick.v.toString(); |
+ var pos = this.xscale * (tick.v - this.minxval); |
+ if ((pos >= 0.0) && (pos <= 1.0)) { |
+ this.xticks.push([pos, label]); |
+ } |
+ }; |
+ MochiKit.Iter.forEach(this.options.xTicks, bind(makeTicks, this)); |
+ } |
+ else if (this.options.xNumberOfTicks) { |
+ // we use defined number of ticks as hint to auto generate |
+ var xvalues = this._uniqueXValues(); |
+ var roughSeparation = this.xrange / this.options.xNumberOfTicks; |
+ var tickCount = 0; |
+ |
+ this.xticks = new Array(); |
+ for (var i = 0; i <= xvalues.length; i++) { |
+ if ((xvalues[i] - this.minxval) >= (tickCount * roughSeparation)) { |
+ var pos = this.xscale * (xvalues[i] - this.minxval); |
+ if ((pos > 1.0) || (pos < 0.0)) |
+ continue; |
+ this.xticks.push([pos, xvalues[i]]); |
+ tickCount++; |
+ } |
+ if (tickCount > this.options.xNumberOfTicks) |
+ break; |
+ } |
+ } |
+}; |
+ |
+PlotKit.Layout.prototype._evaluateLineTicksForYAxis = function() { |
+ var isNil = MochiKit.Base.isUndefinedOrNull; |
+ |
+ |
+ if (this.options.yTicks) { |
+ this.yticks = new Array(); |
+ var makeTicks = function(tick) { |
+ var label = tick.label; |
+ if (isNil(label)) |
+ label = tick.v.toString(); |
+ var pos = 1.0 - (this.yscale * (tick.v - this.minyval)); |
+ if ((pos >= 0.0) && (pos <= 1.0)) { |
+ this.yticks.push([pos, label]); |
+ } |
+ }; |
+ MochiKit.Iter.forEach(this.options.yTicks, bind(makeTicks, this)); |
+ } |
+ else if (this.options.yNumberOfTicks) { |
+ // We use the optionally defined number of ticks as a guide |
+ this.yticks = new Array(); |
+ |
+ // if we get this separation right, we'll have good looking graphs |
+ var roundInt = PlotKit.Base.roundInterval; |
+ var prec = this.options.yTickPrecision; |
+ var roughSeparation = roundInt(this.yrange, |
+ this.options.yNumberOfTicks, prec); |
+ |
+ // round off each value of the y-axis to the precision |
+ // eg. 1.3333 at precision 1 -> 1.3 |
+ for (var i = 0; i <= this.options.yNumberOfTicks; i++) { |
+ var yval = this.minyval + (i * roughSeparation); |
+ var pos = 1.0 - ((yval - this.minyval) * this.yscale); |
+ if ((pos > 1.0) || (pos < 0.0)) |
+ continue; |
+ this.yticks.push([pos, MochiKit.Format.roundToFixed(yval, prec)]); |
+ } |
+ } |
+}; |
+ |
+PlotKit.Layout.prototype._evaluateLineTicks = function() { |
+ this._evaluateLineTicksForXAxis(); |
+ this._evaluateLineTicksForYAxis(); |
+}; |
+ |
+PlotKit.Layout.prototype._evaluateBarTicks = function() { |
+ this._evaluateLineTicks(); |
+ var centerInBar = function(tick) { |
+ return [tick[0] + (this.minxdelta * this.xscale)/2, tick[1]]; |
+ }; |
+ this.xticks = MochiKit.Base.map(bind(centerInBar, this), this.xticks); |
+ |
+ if (this.options.barOrientation == "horizontal") { |
+ // swap scales |
+ var tempticks = this.xticks; |
+ this.xticks = this.yticks; |
+ this.yticks = tempticks; |
+ |
+ // we need to invert the "yaxis" (which is now the xaxis when drawn) |
+ var invert = function(tick) { |
+ return [1.0 - tick[0], tick[1]]; |
+ } |
+ this.xticks = MochiKit.Base.map(invert, this.xticks); |
+ } |
+}; |
+ |
+PlotKit.Layout.prototype._evaluatePieTicks = function() { |
+ var isNil = MochiKit.Base.isUndefinedOrNull; |
+ var formatter = MochiKit.Format.numberFormatter("#%"); |
+ |
+ this.xticks = new Array(); |
+ if (this.options.xTicks) { |
+ // make a lookup dict for x->slice values |
+ var lookup = new Array(); |
+ for (var i = 0; i < this.slices.length; i++) { |
+ lookup[this.slices[i].xval] = this.slices[i]; |
+ } |
+ |
+ for (var i =0; i < this.options.xTicks.length; i++) { |
+ var tick = this.options.xTicks[i]; |
+ var slice = lookup[tick.v]; |
+ var label = tick.label; |
+ if (slice) { |
+ if (isNil(label)) |
+ label = tick.v.toString(); |
+ label += " (" + formatter(slice.fraction) + ")"; |
+ this.xticks.push([tick.v, label]); |
+ } |
+ } |
+ } |
+ else { |
+ // we make our own labels from all the slices |
+ for (var i =0; i < this.slices.length; i++) { |
+ var slice = this.slices[i]; |
+ var label = slice.xval + " (" + formatter(slice.fraction) + ")"; |
+ this.xticks.push([slice.xval, label]); |
+ } |
+ } |
+}; |
+ |
+PlotKit.Layout.prototype._regenerateHitTestCache = function() { |
+ this.hitTestCache.xvalues = this._uniqueXValues(); |
+ this.hitTestCache.xlookup = new Array(); |
+ this.hitTestCache.x2maxy = new Array(); |
+ |
+ var listMax = MochiKit.Base.listMax; |
+ var itemgetter = MochiKit.Base.itemgetter; |
+ var map = MochiKit.Base.map; |
+ |
+ // generate a lookup table for x values to y values |
+ var setNames = keys(this.datasets); |
+ for (var i = 0; i < setNames.length; i++) { |
+ var dataset = this.datasets[setNames[i]]; |
+ for (var j = 0; j < dataset.length; j++) { |
+ var xval = dataset[j][0]; |
+ var yval = dataset[j][1]; |
+ if (this.hitTestCache.xlookup[xval]) |
+ this.hitTestCache.xlookup[xval].push([yval, setNames[i]]); |
+ else |
+ this.hitTestCache.xlookup[xval] = [[yval, setNames[i]]]; |
+ } |
+ } |
+ |
+ for (var x in this.hitTestCache.xlookup) { |
+ var yvals = this.hitTestCache.xlookup[x]; |
+ this.hitTestCache.x2maxy[x] = listMax(map(itemgetter(0), yvals)); |
+ } |
+ |
+ |
+}; |
+ |
+// -------------------------------------------------------------------- |
+// END Internal Functions |
+// -------------------------------------------------------------------- |
+ |
+ |
+// Namespace Iniitialisation |
+ |
+PlotKit.LayoutModule = {}; |
+PlotKit.LayoutModule.Layout = PlotKit.Layout; |
+ |
+PlotKit.LayoutModule.EXPORT = [ |
+ "Layout" |
+]; |
+ |
+PlotKit.LayoutModule.EXPORT_OK = []; |
+ |
+PlotKit.LayoutModule.__new__ = function() { |
+ var m = MochiKit.Base; |
+ |
+ m.nameFunctions(this); |
+ |
+ this.EXPORT_TAGS = { |
+ ":common": this.EXPORT, |
+ ":all": m.concat(this.EXPORT, this.EXPORT_OK) |
+ }; |
+}; |
+ |
+PlotKit.LayoutModule.__new__(); |
+MochiKit.Base._exportSymbols(this, PlotKit.LayoutModule); |