/*     PlotKit Canvas    ==============        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.        Copyright    ---------    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>    For use under the BSD license. <http://www.liquidx.net/plotkit>    */// --------------------------------------------------------------------// 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);
