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