5 Provides HTML Canvas Renderer. This is supported under:
10 - IE 6 (via VML Emulation)
12 It uses DIVs for labels.
16 Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
17 For use under the BSD license. <http://www.liquidx.net/plotkit>
20 // --------------------------------------------------------------------
21 // Check required components
22 // --------------------------------------------------------------------
25 if (typeof(PlotKit
.Base
) == 'undefined')
31 throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}"
35 // ------------------------------------------------------------------------
36 // Defines the renderer class
37 // ------------------------------------------------------------------------
39 if (typeof(PlotKit
.CanvasRenderer
) == 'undefined') {
40 PlotKit
.CanvasRenderer
= {};
43 PlotKit
.CanvasRenderer
.NAME
= "PlotKit.CanvasRenderer";
44 PlotKit
.CanvasRenderer
.VERSION
= PlotKit
.VERSION
;
46 PlotKit
.CanvasRenderer
.__repr__
= function() {
47 return "[" + this.NAME
+ " " + this.VERSION
+ "]";
50 PlotKit
.CanvasRenderer
.toString
= function() {
51 return this.__repr__();
54 PlotKit
.CanvasRenderer
= function(element
, layout
, options
) {
55 if (arguments
.length
> 0)
56 this.__init__(element
, layout
, options
);
59 PlotKit
.CanvasRenderer
.prototype.__init__
= function(element
, layout
, options
) {
60 var isNil
= MochiKit
.Base
.isUndefinedOrNull
;
61 var Color
= MochiKit
.Color
.Color
;
68 "axisLineColor": Color
.blackColor(),
71 "axisLabelColor": Color
.blackColor(),
72 "axisLabelFont": "Arial",
73 "axisLabelFontSize": 9,
76 MochiKit
.Base
.update(this.options
, options
? options
: {});
79 this.element
= MochiKit
.DOM
.getElement(element
);
80 this.container
= this.element
.parentNode
;
82 // Stuff relating to Canvas on IE support
83 this.isIE
= PlotKit
.Base
.excanvasSupported();
85 if (this.isIE
&& !isNil(G_vmlCanvasManager
)) {
88 this.renderDelay
= null;
89 this.clearDelay
= null;
90 this.element
= G_vmlCanvasManager
.initElement(this.element
);
93 this.height
= this.element
.height
;
94 this.width
= this.element
.width
;
96 // --- check whether everything is ok before we return
98 if (isNil(this.element
))
99 throw "CanvasRenderer() - passed canvas is not found";
101 if (!this.isIE
&& !(PlotKit
.CanvasRenderer
.isSupported(this.element
)))
102 throw "CanvasRenderer() - Canvas is not supported.";
104 if (isNil(this.container
) || (this.container
.nodeName
.toLowerCase() != "div"))
105 throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>";
108 this.xlabels
= new Array();
109 this.ylabels
= new Array();
110 this.isFirstRender
= true;
113 x
: this.options
.yAxisLabelWidth
+ 2 * this.options
.axisTickSize
,
116 this.area
.w
= this.width
- this.area
.x
- this.options
.rightGap
;
117 this.area
.h
= this.height
- this.options
.axisLabelFontSize
-
118 2 * this.options
.axisTickSize
;
120 MochiKit
.DOM
.updateNodeAttributes(this.container
,
121 {"style":{ "position": "relative", "width": this.width
+ "px"}});
125 PlotKit
.CanvasRenderer
.prototype._renderLineAxis
= function() {
130 PlotKit
.CanvasRenderer
.prototype._renderAxis
= function() {
131 if (!this.options
.drawXAxis
&& !this.options
.drawYAxis
)
134 var context
= this.element
.getContext("2d");
136 var labelStyle
= {"style":
137 {"position": "absolute",
138 "fontSize": this.options
.axisLabelFontSize
+ "px",
140 "color": this.options
.axisLabelColor
.toRGBString(),
141 "width": this.options
.axisLabelWidth
+ "px",
148 context
.strokeStyle
= this.options
.axisLineColor
.toRGBString();
149 context
.lineWidth
= this.options
.axisLineWidth
;
152 if (this.options
.drawYAxis
) {
153 if (this.layout
.yticks
) {
154 var drawTick
= function(tick
) {
155 if (typeof(tick
) == "function") return;
157 var y
= this.area
.y
+ tick
[0] * this.area
.h
;
159 context
.moveTo(x
, y
);
160 context
.lineTo(x
- this.options
.axisTickSize
, y
);
164 var label
= DIV(labelStyle
, tick
[1]);
165 var top
= (y
- this.options
.axisLabelFontSize
/ 2);
166 if (top
< 0) top
= 0;
168 if (top
+ this.options
.axisLabelFontSize
+ 3 > this.height
) {
169 label
.style
.bottom
= "0px";
171 label
.style
.top
= top
+ "px";
173 label
.style
.left
= "0px";
174 label
.style
.textAlign
= "right";
175 label
.style
.width
= this.options
.yAxisLabelWidth
+ "px";
176 MochiKit
.DOM
.appendChildNodes(this.container
, label
);
177 this.ylabels
.push(label
);
180 MochiKit
.Iter
.forEach(this.layout
.yticks
, bind(drawTick
, this));
182 // The lowest tick on the y-axis often overlaps with the leftmost
183 // tick on the x-axis. Shift the bottom tick up a little bit to
184 // compensate if necessary.
185 var bottomTick
= this.ylabels
[0];
186 var fontSize
= this.options
.axisLabelFontSize
;
187 var bottom
= parseInt(bottomTick
.style
.top
) + fontSize
;
188 if (bottom
> this.height
- fontSize
) {
189 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
) -
190 fontSize
/ 2) + "px";
195 context
.moveTo(this.area
.x
, this.area
.y
);
196 context
.lineTo(this.area
.x
, this.area
.y
+ this.area
.h
);
201 if (this.options
.drawXAxis
) {
202 if (this.layout
.xticks
) {
203 var drawTick
= function(tick
) {
204 if (typeof(dataset
) == "function") return;
206 var x
= this.area
.x
+ tick
[0] * this.area
.w
;
207 var y
= this.area
.y
+ this.area
.h
;
209 context
.moveTo(x
, y
);
210 context
.lineTo(x
, y
+ this.options
.axisTickSize
);
214 var label
= DIV(labelStyle
, tick
[1]);
215 label
.style
.textAlign
= "center";
216 label
.style
.bottom
= "0px";
218 var left
= (x
- this.options
.axisLabelWidth
/2);
219 if (left
+ this.options
.axisLabelWidth
> this.width
) {
220 left
= this.width
- this.options
.xAxisLabelWidth
;
221 label
.style
.textAlign
= "right";
225 label
.style
.textAlign
= "left";
228 label
.style
.left
= left
+ "px";
229 label
.style
.width
= this.options
.xAxisLabelWidth
+ "px";
230 MochiKit
.DOM
.appendChildNodes(this.container
, label
);
231 this.xlabels
.push(label
);
234 MochiKit
.Iter
.forEach(this.layout
.xticks
, bind(drawTick
, this));
238 context
.moveTo(this.area
.x
, this.area
.y
+ this.area
.h
);
239 context
.lineTo(this.area
.x
+ this.area
.w
, this.area
.y
+ this.area
.h
);
248 PlotKit
.CanvasRenderer
.prototype.clear
= function() {
250 // VML takes a while to start up, so we just poll every this.IEDelay
252 if (this.clearDelay
) {
253 this.clearDelay
.cancel();
254 this.clearDelay
= null;
256 var context
= this.element
.getContext("2d");
259 this.isFirstRender
= false;
260 this.clearDelay
= MochiKit
.Async
.wait(this.IEDelay
);
261 this.clearDelay
.addCallback(bind(this.clear
, this));
266 var context
= this.element
.getContext("2d");
267 context
.clearRect(0, 0, this.width
, this.height
);
269 MochiKit
.Iter
.forEach(this.xlabels
, MochiKit
.DOM
.removeElement
);
270 MochiKit
.Iter
.forEach(this.ylabels
, MochiKit
.DOM
.removeElement
);
271 this.xlabels
= new Array();
272 this.ylabels
= new Array();
275 // ----------------------------------------------------------------
276 // Everything below here is experimental and undocumented.
277 // ----------------------------------------------------------------
280 PlotKit
.CanvasRenderer
.isSupported
= function(canvasName
) {
283 if (MochiKit
.Base
.isUndefinedOrNull(canvasName
))
284 canvas
= MochiKit
.DOM
.CANVAS({});
286 canvas
= MochiKit
.DOM
.getElement(canvasName
);
287 var context
= canvas
.getContext("2d");
290 var ie
= navigator
.appVersion
.match(/MSIE (\d\.\d)/);
291 var opera
= (navigator
.userAgent
.toLowerCase().indexOf("opera") != -1);
292 if ((!ie
) || (ie
[1] < 6) || (opera
))