| Index: static/js/plotkit/Canvas.js | 
| diff --git a/static/js/plotkit/Canvas.js b/static/js/plotkit/Canvas.js | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..c4b61cce8d82c4b6f2e79a281fcf9117daf10d17 | 
| --- /dev/null | 
| +++ b/static/js/plotkit/Canvas.js | 
| @@ -0,0 +1,682 @@ | 
| +/* | 
| +    ======= | 
| +    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. | 
| + | 
| +    ======= | 
| +    Provides HTML Canvas Renderer. This is supported under: | 
| + | 
| +    - Safari 2.0 | 
| +    - Mozilla Firefox 1.5 | 
| +    - Opera 9.0 preview 2 | 
| +    - IE 6 (via VML Emulation) | 
| + | 
| +    It uses DIVs for labels. | 
| +*/ | 
| +// -------------------------------------------------------------------- | 
| +// Check required components | 
| +// -------------------------------------------------------------------- | 
| + | 
| +try { | 
| +    if ((typeof(PlotKit.Base) == 'undefined') || | 
| +        (typeof(PlotKit.Layout) == 'undefined')) | 
| +    { | 
| +        throw ""; | 
| +    } | 
| +} | 
| +catch (e) { | 
| +    throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}" | 
| +} | 
| + | 
| + | 
| +// ------------------------------------------------------------------------ | 
| +//  Defines the renderer class | 
| +// ------------------------------------------------------------------------ | 
| + | 
| +if (typeof(PlotKit.CanvasRenderer) == 'undefined') { | 
| +    PlotKit.CanvasRenderer = {}; | 
| +} | 
| + | 
| +PlotKit.CanvasRenderer.NAME = "PlotKit.CanvasRenderer"; | 
| +PlotKit.CanvasRenderer.VERSION = PlotKit.VERSION; | 
| + | 
| +PlotKit.CanvasRenderer.__repr__ = function() { | 
| +    return "[" + this.NAME + " " + this.VERSION + "]"; | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.toString = function() { | 
| +    return this.__repr__(); | 
| +} | 
| + | 
| +PlotKit.CanvasRenderer = function(element, layout, options) { | 
| +    if (arguments.length  > 0) | 
| +        this.__init__(element, layout, options); | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype.__init__ = function(element, layout, options) { | 
| +    var isNil = MochiKit.Base.isUndefinedOrNull; | 
| +    var Color = MochiKit.Color.Color; | 
| + | 
| +    // default options | 
| +    this.options = { | 
| +        "drawBackground": true, | 
| +        "backgroundColor": Color.whiteColor(), | 
| +        "padding": {left: 30, right: 30, top: 5, bottom: 10}, | 
| +        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[0]), | 
| +        "strokeColor": Color.whiteColor(), | 
| +        "strokeColorTransform": "asStrokeColor", | 
| +        "strokeWidth": 0.5, | 
| +        "shouldFill": true, | 
| +        "shouldStroke": true, | 
| +        "drawXAxis": true, | 
| +        "drawYAxis": true, | 
| +        "axisLineColor": Color.blackColor(), | 
| +        "axisLineWidth": 0.5, | 
| +        "axisTickSize": 3, | 
| +        "axisLabelColor": Color.blackColor(), | 
| +        "axisLabelFont": "Arial", | 
| +        "axisLabelFontSize": 9, | 
| +		"axisLabelWidth": 50, | 
| +		"pieRadius": 0.4, | 
| +        "enableEvents": true | 
| +    }; | 
| +    MochiKit.Base.update(this.options, options ? options : {}); | 
| + | 
| +    this.layout = layout; | 
| +    this.element = MochiKit.DOM.getElement(element); | 
| +    this.container = this.element.parentNode; | 
| + | 
| +    // Stuff relating to Canvas on IE support | 
| +    this.isIE = PlotKit.Base.excanvasSupported(); | 
| + | 
| +    if (this.isIE && !isNil(G_vmlCanvasManager)) { | 
| +        this.IEDelay = 0.5; | 
| +        this.maxTries = 5; | 
| +        this.renderDelay = null; | 
| +        this.clearDelay = null; | 
| +        this.element = G_vmlCanvasManager.initElement(this.element); | 
| +    } | 
| + | 
| +    this.height = this.element.height; | 
| +    this.width = this.element.width; | 
| + | 
| +    // --- check whether everything is ok before we return | 
| + | 
| +    if (isNil(this.element)) | 
| +        throw "CanvasRenderer() - passed canvas is not found"; | 
| + | 
| +    if (!this.isIE && !(PlotKit.CanvasRenderer.isSupported(this.element))) | 
| +        throw "CanvasRenderer() - Canvas is not supported."; | 
| + | 
| +    if (isNil(this.container) || (this.container.nodeName.toLowerCase() != "div")) | 
| +        throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>"; | 
| + | 
| +    // internal state | 
| +    this.xlabels = new Array(); | 
| +    this.ylabels = new Array(); | 
| +    this.isFirstRender = true; | 
| + | 
| +    this.area = { | 
| +        x: this.options.padding.left, | 
| +        y: this.options.padding.top, | 
| +        w: this.width - this.options.padding.left - this.options.padding.right, | 
| +        h: this.height - this.options.padding.top - this.options.padding.bottom | 
| +    }; | 
| + | 
| +    MochiKit.DOM.updateNodeAttributes(this.container, | 
| +    {"style":{ "position": "relative", "width": this.width + "px"}}); | 
| + | 
| +    // load event system if we have Signals | 
| +    /* Disabled until we have a proper implementation | 
| +    try { | 
| +        this.event_isinside = null; | 
| +        if (MochiKit.Signal && this.options.enableEvents) { | 
| +            this._initialiseEvents(); | 
| +        } | 
| +    } | 
| +    catch (e) { | 
| +        // still experimental | 
| +    } | 
| +    */ | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype.render = function() { | 
| +    if (this.isIE) { | 
| +        // VML takes a while to start up, so we just poll every this.IEDelay | 
| +        try { | 
| +            if (this.renderDelay) { | 
| +                this.renderDelay.cancel(); | 
| +                this.renderDelay = null; | 
| +            } | 
| +            var context = this.element.getContext("2d"); | 
| +        } | 
| +        catch (e) { | 
| +            this.isFirstRender = false; | 
| +            if (this.maxTries-- > 0) { | 
| +                this.renderDelay = MochiKit.Async.wait(this.IEDelay); | 
| +                this.renderDelay.addCallback(bind(this.render, this)); | 
| +            } | 
| +            return; | 
| +        } | 
| +    } | 
| + | 
| +    if (this.options.drawBackground) | 
| +        this._renderBackground(); | 
| + | 
| +    if (this.layout.style == "bar") { | 
| +        this._renderBarChart(); | 
| +		this._renderBarAxis(); | 
| +	} | 
| +    else if (this.layout.style == "pie") { | 
| +        this._renderPieChart(); | 
| +		this._renderPieAxis(); | 
| +	} | 
| +    else if (this.layout.style == "line") { | 
| +        this._renderLineChart(); | 
| +		this._renderLineAxis(); | 
| +	} | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderBarChartWrap = function(data, plotFunc) { | 
| +    var context = this.element.getContext("2d"); | 
| +    var colorCount = this.options.colorScheme.length; | 
| +    var colorScheme = this.options.colorScheme; | 
| +    var setNames = MochiKit.Base.keys(this.layout.datasets); | 
| +    var setCount = setNames.length; | 
| + | 
| +    for (var i = 0; i < setCount; i++) { | 
| +        var setName = setNames[i]; | 
| +        var color = colorScheme[i%colorCount]; | 
| +        context.save(); | 
| +        context.fillStyle = color.toRGBString(); | 
| +        if (this.options.strokeColor) | 
| +            context.strokeStyle = this.options.strokeColor.toRGBString(); | 
| +        else if (this.options.strokeColorTransform) | 
| +            context.strokeStyle = color[this.options.strokeColorTransform]().toRGBString(); | 
| + | 
| +        context.lineWidth = this.options.strokeWidth; | 
| +        var forEachFunc = function(obj) { | 
| +            if (obj.name == setName) | 
| +                plotFunc(context, obj); | 
| +        }; | 
| + | 
| +        MochiKit.Iter.forEach(data, bind(forEachFunc, this)); | 
| +        context.restore(); | 
| +    } | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderBarChart = function() { | 
| +    var bind = MochiKit.Base.bind; | 
| + | 
| +    var drawRect = function(context, bar) { | 
| +        var x = this.area.w * bar.x + this.area.x; | 
| +        var y = this.area.h * bar.y + this.area.y; | 
| +        var w = this.area.w * bar.w; | 
| +        var h = this.area.h * bar.h; | 
| +        if ((w < 1) || (h < 1)) | 
| +            return; | 
| +        if (this.options.shouldFill) | 
| +            context.fillRect(x, y, w, h); | 
| +        if (this.options.shouldStroke) | 
| +            context.strokeRect(x, y, w, h); | 
| +    }; | 
| +    this._renderBarChartWrap(this.layout.bars, bind(drawRect, this)); | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderLineChart = function() { | 
| +    var context = this.element.getContext("2d"); | 
| +    var colorCount = this.options.colorScheme.length; | 
| +    var colorScheme = this.options.colorScheme; | 
| +    var setNames = MochiKit.Base.keys(this.layout.datasets); | 
| +    var setCount = setNames.length; | 
| +    var bind = MochiKit.Base.bind; | 
| +    var partial = MochiKit.Base.partial; | 
| + | 
| +    for (var i = 0; i < setCount; i++) { | 
| +        var setName = setNames[i]; | 
| +        var color = colorScheme[i%colorCount]; | 
| +        var strokeX = this.options.strokeColorTransform; | 
| + | 
| +        // setup graphics context | 
| +        context.save(); | 
| +        context.fillStyle = color.toRGBString(); | 
| +        if (this.options.strokeColor) | 
| +            context.strokeStyle = this.options.strokeColor.toRGBString(); | 
| +        else if (this.options.strokeColorTransform) | 
| +            context.strokeStyle = color[strokeX]().toRGBString(); | 
| + | 
| +        context.lineWidth = this.options.strokeWidth; | 
| + | 
| +        // create paths | 
| +        var makePath = function(ctx) { | 
| +            ctx.beginPath(); | 
| +            ctx.moveTo(this.area.x, this.area.y + this.area.h); | 
| +            var addPoint = function(ctx_, point) { | 
| +                if (point.name == setName) | 
| +                    ctx_.lineTo(this.area.w * point.x + this.area.x, | 
| +                                this.area.h * point.y + this.area.y); | 
| +            }; | 
| +            MochiKit.Iter.forEach(this.layout.points, partial(addPoint, ctx), this); | 
| +            ctx.lineTo(this.area.w + this.area.x, | 
| +                           this.area.h + this.area.y); | 
| +            ctx.lineTo(this.area.x, this.area.y + this.area.h); | 
| +            ctx.closePath(); | 
| +        }; | 
| + | 
| +        if (this.options.shouldFill) { | 
| +            bind(makePath, this)(context); | 
| +            context.fill(); | 
| +        } | 
| +        if (this.options.shouldStroke) { | 
| +            bind(makePath, this)(context); | 
| +            context.stroke(); | 
| +        } | 
| + | 
| +        context.restore(); | 
| +    } | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderPieChart = function() { | 
| +    var context = this.element.getContext("2d"); | 
| +    var colorCount = this.options.colorScheme.length; | 
| +    var slices = this.layout.slices; | 
| + | 
| +    var centerx = this.area.x + this.area.w * 0.5; | 
| +    var centery = this.area.y + this.area.h * 0.5; | 
| +    var radius = Math.min(this.area.w * this.options.pieRadius, | 
| +                          this.area.h * this.options.pieRadius); | 
| + | 
| +    if (this.isIE) { | 
| +        centerx = parseInt(centerx); | 
| +        centery = parseInt(centery); | 
| +        radius = parseInt(radius); | 
| +    } | 
| + | 
| + | 
| +	// NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1 | 
| +	// so we have to subtract 90 degrees to make it start at y = 1, x = 0 | 
| + | 
| +    for (var i = 0; i < slices.length; i++) { | 
| +        var color = this.options.colorScheme[i%colorCount]; | 
| +        context.save(); | 
| +        context.fillStyle = color.toRGBString(); | 
| + | 
| +        var makePath = function() { | 
| +            context.beginPath(); | 
| +            context.moveTo(centerx, centery); | 
| +            context.arc(centerx, centery, radius, | 
| +                        slices[i].startAngle - Math.PI/2, | 
| +                        slices[i].endAngle - Math.PI/2, | 
| +                        false); | 
| +            context.lineTo(centerx, centery); | 
| +            context.closePath(); | 
| +        }; | 
| + | 
| +        if (Math.abs(slices[i].startAngle - slices[i].endAngle) > 0.001) { | 
| +            if (this.options.shouldFill) { | 
| +                makePath(); | 
| +                context.fill(); | 
| +            } | 
| + | 
| +            if (this.options.shouldStroke) { | 
| +                makePath(); | 
| +                context.lineWidth = this.options.strokeWidth; | 
| +                if (this.options.strokeColor) | 
| +                    context.strokeStyle = this.options.strokeColor.toRGBString(); | 
| +                else if (this.options.strokeColorTransform) | 
| +                    context.strokeStyle = color[this.options.strokeColorTransform]().toRGBString(); | 
| +                context.stroke(); | 
| +            } | 
| +        } | 
| +        context.restore(); | 
| +    } | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderBarAxis = function() { | 
| +	this._renderAxis(); | 
| +} | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderLineAxis = function() { | 
| +	this._renderAxis(); | 
| +}; | 
| + | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderAxis = function() { | 
| +    if (!this.options.drawXAxis && !this.options.drawYAxis) | 
| +        return; | 
| + | 
| +    var context = this.element.getContext("2d"); | 
| + | 
| +    var labelStyle = {"style": | 
| +         {"position": "absolute", | 
| +          "fontSize": this.options.axisLabelFontSize + "px", | 
| +          "zIndex": 10, | 
| +          "color": this.options.axisLabelColor.toRGBString(), | 
| +          "width": this.options.axisLabelWidth + "px", | 
| +          "overflow": "hidden" | 
| +         } | 
| +    }; | 
| + | 
| +    // axis lines | 
| +    context.save(); | 
| +    context.strokeStyle = this.options.axisLineColor.toRGBString(); | 
| +    context.lineWidth = this.options.axisLineWidth; | 
| + | 
| + | 
| +    if (this.options.drawYAxis) { | 
| +        if (this.layout.yticks) { | 
| +            var drawTick = function(tick) { | 
| +                if (typeof(tick) == "function") return; | 
| +                var x = this.area.x; | 
| +                var y = this.area.y + tick[0] * this.area.h; | 
| +                context.beginPath(); | 
| +                context.moveTo(x, y); | 
| +                context.lineTo(x - this.options.axisTickSize, y); | 
| +                context.closePath(); | 
| +                context.stroke(); | 
| + | 
| +                var label = DIV(labelStyle, tick[1]); | 
| +                label.style.top = (y - this.options.axisLabelFontSize) + "px"; | 
| +                label.style.left = (x - this.options.padding.left - this.options.axisTickSize) + "px"; | 
| +                label.style.textAlign = "right"; | 
| +                label.style.width = (this.options.padding.left - this.options.axisTickSize * 2) + "px"; | 
| +                MochiKit.DOM.appendChildNodes(this.container, label); | 
| +                this.ylabels.push(label); | 
| +            }; | 
| + | 
| +            MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this)); | 
| +        } | 
| + | 
| +        context.beginPath(); | 
| +        context.moveTo(this.area.x, this.area.y); | 
| +        context.lineTo(this.area.x, this.area.y + this.area.h); | 
| +        context.closePath(); | 
| +        context.stroke(); | 
| +    } | 
| + | 
| +    if (this.options.drawXAxis) { | 
| +        if (this.layout.xticks) { | 
| +            var drawTick = function(tick) { | 
| +                if (typeof(dataset) == "function") return; | 
| + | 
| +                var x = this.area.x + tick[0] * this.area.w; | 
| +                var y = this.area.y + this.area.h; | 
| +                context.beginPath(); | 
| +                context.moveTo(x, y); | 
| +                context.lineTo(x, y + this.options.axisTickSize); | 
| +                context.closePath(); | 
| +                context.stroke(); | 
| + | 
| +                var label = DIV(labelStyle, tick[1]); | 
| +                label.style.top = (y + this.options.axisTickSize) + "px"; | 
| +                label.style.left = (x - this.options.axisLabelWidth/2) + "px"; | 
| +                label.style.textAlign = "center"; | 
| +                label.style.width = this.options.axisLabelWidth + "px"; | 
| +                MochiKit.DOM.appendChildNodes(this.container, label); | 
| +                this.xlabels.push(label); | 
| +            }; | 
| + | 
| +            MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this)); | 
| +        } | 
| + | 
| +        context.beginPath(); | 
| +        context.moveTo(this.area.x, this.area.y + this.area.h); | 
| +        context.lineTo(this.area.x + this.area.w, this.area.y + this.area.h); | 
| +        context.closePath(); | 
| +        context.stroke(); | 
| +    } | 
| + | 
| +    context.restore(); | 
| + | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderPieAxis = function() { | 
| +    if (!this.options.drawXAxis) | 
| +        return; | 
| + | 
| +	if (this.layout.xticks) { | 
| +		// make a lookup dict for x->slice values | 
| +		var lookup = new Array(); | 
| +		for (var i = 0; i < this.layout.slices.length; i++) { | 
| +			lookup[this.layout.slices[i].xval] = this.layout.slices[i]; | 
| +		} | 
| + | 
| +		var centerx = this.area.x + this.area.w * 0.5; | 
| +	    var centery = this.area.y + this.area.h * 0.5; | 
| +	    var radius = Math.min(this.area.w * this.options.pieRadius, | 
| +	                          this.area.h * this.options.pieRadius); | 
| +		var labelWidth = this.options.axisLabelWidth; | 
| + | 
| +		for (var i = 0; i < this.layout.xticks.length; i++) { | 
| +			var slice = lookup[this.layout.xticks[i][0]]; | 
| +			if (MochiKit.Base.isUndefinedOrNull(slice)) | 
| +				continue; | 
| + | 
| + | 
| +			var angle = (slice.startAngle + slice.endAngle)/2; | 
| +			// normalize the angle | 
| +			var normalisedAngle = angle; | 
| +			if (normalisedAngle > Math.PI * 2) | 
| +				normalisedAngle = normalisedAngle - Math.PI * 2; | 
| +			else if (normalisedAngle < 0) | 
| +				normalisedAngle = normalisedAngle + Math.PI * 2; | 
| + | 
| +			var labelx = centerx + Math.sin(normalisedAngle) * (radius + 10); | 
| +	        var labely = centery - Math.cos(normalisedAngle) * (radius + 10); | 
| + | 
| +			var attrib = {"position": "absolute", | 
| +	                      "zIndex": 11, | 
| +	                      "width": labelWidth + "px", | 
| +	                      "fontSize": this.options.axisLabelFontSize + "px", | 
| +	                      "overflow": "hidden", | 
| +						  "color": this.options.axisLabelColor.toHexString() | 
| +						}; | 
| + | 
| +			if (normalisedAngle <= Math.PI * 0.5) { | 
| +	            // text on top and align left | 
| +	            attrib["textAlign"] = "left"; | 
| +	            attrib["verticalAlign"] = "top"; | 
| +	            attrib["left"] = labelx + "px"; | 
| +	            attrib["top"] = (labely - this.options.axisLabelFontSize) + "px"; | 
| +	        } | 
| +	        else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) { | 
| +	            // text on bottom and align left | 
| +	            attrib["textAlign"] = "left"; | 
| +	            attrib["verticalAlign"] = "bottom"; | 
| +	            attrib["left"] = labelx + "px"; | 
| +	            attrib["top"] = labely + "px"; | 
| + | 
| +	        } | 
| +	        else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) { | 
| +	            // text on bottom and align right | 
| +	            attrib["textAlign"] = "right"; | 
| +	            attrib["verticalAlign"] = "bottom"; | 
| +	            attrib["left"] = (labelx  - labelWidth) + "px"; | 
| +	            attrib["top"] = labely + "px"; | 
| +	        } | 
| +	        else { | 
| +	            // text on top and align right | 
| +	            attrib["textAlign"] = "right"; | 
| +	            attrib["verticalAlign"] = "bottom"; | 
| +	            attrib["left"] = (labelx  - labelWidth) + "px"; | 
| +	            attrib["top"] = (labely - this.options.axisLabelFontSize) + "px"; | 
| +	        } | 
| + | 
| +			var label = DIV({'style': attrib}, this.layout.xticks[i][1]); | 
| +			this.xlabels.push(label); | 
| +			MochiKit.DOM.appendChildNodes(this.container, label); | 
| +	  } | 
| + | 
| +	} | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._renderBackground = function() { | 
| +    var context = this.element.getContext("2d"); | 
| +    context.save(); | 
| +    context.fillStyle = this.options.backgroundColor.toRGBString(); | 
| +    context.fillRect(0, 0, this.width, this.height); | 
| +    context.restore(); | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype.clear = function() { | 
| +    if (this.isIE) { | 
| +        // VML takes a while to start up, so we just poll every this.IEDelay | 
| +        try { | 
| +            if (this.clearDelay) { | 
| +                this.clearDelay.cancel(); | 
| +                this.clearDelay = null; | 
| +            } | 
| +            var context = this.element.getContext("2d"); | 
| +        } | 
| +        catch (e) { | 
| +            this.isFirstRender = false; | 
| +            this.clearDelay = MochiKit.Async.wait(this.IEDelay); | 
| +            this.clearDelay.addCallback(bind(this.clear, this)); | 
| +            return; | 
| +        } | 
| +    } | 
| + | 
| +    var context = this.element.getContext("2d"); | 
| +    context.clearRect(0, 0, this.width, this.height); | 
| + | 
| +    MochiKit.Iter.forEach(this.xlabels, MochiKit.DOM.removeElement); | 
| +    MochiKit.Iter.forEach(this.ylabels, MochiKit.DOM.removeElement); | 
| +    this.xlabels = new Array(); | 
| +    this.ylabels = new Array(); | 
| +}; | 
| + | 
| +// ---------------------------------------------------------------- | 
| +//  Everything below here is experimental and undocumented. | 
| +// ---------------------------------------------------------------- | 
| + | 
| +PlotKit.CanvasRenderer.prototype._initialiseEvents = function() { | 
| +    var connect = MochiKit.Signal.connect; | 
| +    var bind = MochiKit.Base.bind; | 
| +    //MochiKit.Signal.registerSignals(this, ['onmouseover', 'onclick', 'onmouseout', 'onmousemove']); | 
| +    //connect(this.element, 'onmouseover', bind(this.onmouseover, this)); | 
| +    //connect(this.element, 'onmouseout', bind(this.onmouseout, this)); | 
| +    //connect(this.element, 'onmousemove', bind(this.onmousemove, this)); | 
| +    connect(this.element, 'onclick', bind(this.onclick, this)); | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._resolveObject = function(e) { | 
| +    // does not work in firefox | 
| +	//var x = (e.event().offsetX - this.area.x) / this.area.w; | 
| +	//var y = (e.event().offsetY - this.area.y) / this.area.h; | 
| + | 
| +    var x = (e.mouse().page.x - PlotKit.Base.findPosX(this.element) - this.area.x) / this.area.w; | 
| +    var y = (e.mouse().page.y - PlotKit.Base.findPosY(this.element) - this.area.y) / this.area.h; | 
| + | 
| +    //log(x, y); | 
| + | 
| +    var isHit = this.layout.hitTest(x, y); | 
| +    if (isHit) | 
| +        return isHit; | 
| +    return null; | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype._createEventObject = function(layoutObj, e) { | 
| +    if (layoutObj == null) { | 
| +        return null; | 
| +    } | 
| + | 
| +    e.chart = layoutObj | 
| +    return e; | 
| +}; | 
| + | 
| + | 
| +PlotKit.CanvasRenderer.prototype.onclick = function(e) { | 
| +    var layoutObject = this._resolveObject(e); | 
| +    var eventObject = this._createEventObject(layoutObject, e); | 
| +    if (eventObject != null) | 
| +        MochiKit.Signal.signal(this, "onclick", eventObject); | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype.onmouseover = function(e) { | 
| +    var layoutObject = this._resolveObject(e); | 
| +    var eventObject = this._createEventObject(layoutObject, e); | 
| +    if (eventObject != null) | 
| +        signal(this, "onmouseover", eventObject); | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype.onmouseout = function(e) { | 
| +    var layoutObject = this._resolveObject(e); | 
| +    var eventObject = this._createEventObject(layoutObject, e); | 
| +    if (eventObject == null) | 
| +        signal(this, "onmouseout", e); | 
| +    else | 
| +        signal(this, "onmouseout", eventObject); | 
| + | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.prototype.onmousemove = function(e) { | 
| +    var layoutObject = this._resolveObject(e); | 
| +    var eventObject = this._createEventObject(layoutObject, e); | 
| + | 
| +    if ((layoutObject == null) && (this.event_isinside == null)) { | 
| +        // TODO: should we emit an event anyway? | 
| +        return; | 
| +    } | 
| + | 
| +    if ((layoutObject != null) && (this.event_isinside == null)) | 
| +        signal(this, "onmouseover", eventObject); | 
| + | 
| +    if ((layoutObject == null) && (this.event_isinside != null)) | 
| +        signal(this, "onmouseout", eventObject); | 
| + | 
| +    if ((layoutObject != null) && (this.event_isinside != null)) | 
| +        signal(this, "onmousemove", eventObject); | 
| + | 
| +    this.event_isinside = layoutObject; | 
| +    //log("move", x, y); | 
| +}; | 
| + | 
| +PlotKit.CanvasRenderer.isSupported = function(canvasName) { | 
| +    var canvas = null; | 
| +    try { | 
| +        if (MochiKit.Base.isUndefinedOrNull(canvasName)) | 
| +            canvas = MochiKit.DOM.CANVAS({}); | 
| +        else | 
| +            canvas = MochiKit.DOM.getElement(canvasName); | 
| +        var context = canvas.getContext("2d"); | 
| +    } | 
| +    catch (e) { | 
| +        var ie = navigator.appVersion.match(/MSIE (\d\.\d)/); | 
| +        var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1); | 
| +        if ((!ie) || (ie[1] < 6) || (opera)) | 
| +            return false; | 
| +        return true; | 
| +    } | 
| +    return true; | 
| +}; | 
| + | 
| +// Namespace Iniitialisation | 
| + | 
| +PlotKit.Canvas = {} | 
| +PlotKit.Canvas.CanvasRenderer = PlotKit.CanvasRenderer; | 
| + | 
| +PlotKit.Canvas.EXPORT = [ | 
| +    "CanvasRenderer" | 
| +]; | 
| + | 
| +PlotKit.Canvas.EXPORT_OK = [ | 
| +    "CanvasRenderer" | 
| +]; | 
| + | 
| +PlotKit.Canvas.__new__ = function() { | 
| +    var m = MochiKit.Base; | 
| + | 
| +    m.nameFunctions(this); | 
| + | 
| +    this.EXPORT_TAGS = { | 
| +        ":common": this.EXPORT, | 
| +        ":all": m.concat(this.EXPORT, this.EXPORT_OK) | 
| +    }; | 
| +}; | 
| + | 
| +PlotKit.Canvas.__new__(); | 
| +MochiKit.Base._exportSymbols(this, PlotKit.Canvas); | 
|  |