Commit | Line | Data |
---|---|---|
6a1aa64f DV |
1 | /* |
2 | PlotKit SVG | |
3 | =========== | |
4 | SVG Renderer for PlotKit | |
5 | ||
6 | Copyright | |
7 | --------- | |
8 | Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net> | |
9 | For use under the BSD license. <http://www.liquidx.net/plotkit> | |
10 | */ | |
11 | ||
12 | // ------------------------------------------------------------------------- | |
13 | // NOTES: - If you use XHTML1.1 strict, then you must include each MochiKit | |
14 | // file individuall. | |
15 | // - For IE support, you must include the AdobeSVG object hack. | |
16 | // See tests/svg.html for details. | |
17 | // ------------------------------------------------------------------------- | |
18 | // ------------------------------------------------------------------------- | |
19 | // Check required components | |
20 | // ------------------------------------------------------------------------- | |
21 | ||
22 | try { | |
23 | if (typeof(PlotKit.Layout) == 'undefined') | |
24 | { | |
25 | throw ""; | |
26 | } | |
27 | } | |
28 | catch (e) { | |
29 | throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Layout" | |
30 | } | |
31 | ||
32 | ||
33 | // --------------------------------------------------------------------------- | |
34 | // SVG Renderer | |
35 | // --------------------------------------------------------------------------- | |
36 | ||
37 | PlotKit.SVGRenderer = function(element, layout, options) { | |
38 | if (arguments.length > 0) | |
39 | this.__init__(element, layout, options); | |
40 | }; | |
41 | ||
42 | PlotKit.SVGRenderer.NAME = "PlotKit.SVGRenderer"; | |
43 | PlotKit.SVGRenderer.VERSION = PlotKit.VERSION; | |
44 | ||
45 | PlotKit.SVGRenderer.__repr__ = function() { | |
46 | return "[" + this.NAME + " " + this.VERSION + "]"; | |
47 | }; | |
48 | ||
49 | PlotKit.SVGRenderer.toString = function() { | |
50 | return this.__repr__(); | |
51 | } | |
52 | ||
53 | PlotKit.SVGRenderer.SVGNS = 'http://www.w3.org/2000/svg'; | |
54 | ||
55 | PlotKit.SVGRenderer.prototype.__init__ = function(element, layout, options) { | |
56 | var isNil = MochiKit.Base.isUndefinedOrNull; | |
57 | ||
58 | // default options | |
59 | this.options = { | |
60 | "drawBackground": true, | |
61 | "backgroundColor": Color.whiteColor(), | |
62 | "padding": {left: 30, right: 30, top: 5, bottom: 10}, | |
63 | "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[1]), | |
64 | "strokeColor": Color.whiteColor(), | |
65 | "strokeColorTransform": "asStrokeColor", | |
66 | "strokeWidth": 0.5, | |
67 | "shouldFill": true, | |
68 | "shouldStroke": true, | |
69 | "drawXAxis": true, | |
70 | "drawYAxis": true, | |
71 | "axisLineColor": Color.blackColor(), | |
72 | "axisLineWidth": 0.5, | |
73 | "axisTickSize": 3, | |
74 | "axisLabelColor": Color.blackColor(), | |
75 | "axisLabelFont": "Arial", | |
76 | "axisLabelFontSize": 9, | |
77 | "axisLabelWidth": 50, | |
78 | "axisLabelUseDiv": true, | |
79 | "pieRadius": 0.4, | |
80 | "enableEvents": true | |
81 | }; | |
82 | ||
83 | MochiKit.Base.update(this.options, options ? options : {}); | |
84 | this.layout = layout; | |
85 | this.element = MochiKit.DOM.getElement(element); | |
86 | this.container = this.element.parentNode; | |
87 | this.height = parseInt(this.element.getAttribute("height")); | |
88 | this.width = parseInt(this.element.getAttribute("width")); | |
89 | this.document = document; | |
90 | this.root = this.element; | |
91 | ||
92 | // Adobe SVG Support: | |
93 | // - if an exception is thrown, then no Adobe SVG Plugin support. | |
94 | try { | |
95 | this.document = this.element.getSVGDocument(); | |
96 | this.root = isNil(this.document.documentElement) ? this.element : this.document.documentElement; | |
97 | } | |
98 | catch (e) { | |
99 | } | |
100 | ||
101 | this.element.style.zIndex = 1; | |
102 | ||
103 | if (isNil(this.element)) | |
104 | throw "SVGRenderer() - passed SVG object is not found"; | |
105 | ||
106 | if (isNil(this.container) || this.container.nodeName.toLowerCase() != "div") | |
107 | throw "SVGRenderer() - No DIV's around the SVG."; | |
108 | ||
109 | // internal state | |
110 | this.xlabels = new Array(); | |
111 | this.ylabels = new Array(); | |
112 | ||
113 | // initialise some meta structures in SVG | |
114 | this.defs = this.createSVGElement("defs"); | |
115 | ||
116 | this.area = { | |
117 | x: this.options.padding.left, | |
118 | y: this.options.padding.top, | |
119 | w: this.width - this.options.padding.left - this.options.padding.right, | |
120 | h: this.height - this.options.padding.top - this.options.padding.bottom | |
121 | }; | |
122 | ||
123 | MochiKit.DOM.updateNodeAttributes(this.container, | |
124 | {"style":{ "position": "relative", "width": this.width + "px"}}); | |
125 | ||
126 | ||
127 | }; | |
128 | ||
129 | ||
130 | PlotKit.SVGRenderer.prototype.render = function() { | |
131 | if (this.options.drawBackground) | |
132 | this._renderBackground(); | |
133 | ||
134 | if (this.layout.style == "bar") { | |
135 | this._renderBarChart(); | |
136 | this._renderBarAxis(); | |
137 | } | |
138 | else if (this.layout.style == "pie") { | |
139 | this._renderPieChart(); | |
140 | this._renderPieAxis(); | |
141 | } | |
142 | else if (this.layout.style == "line") { | |
143 | this._renderLineChart(); | |
144 | this._renderLineAxis(); | |
145 | } | |
146 | }; | |
147 | ||
148 | PlotKit.SVGRenderer.prototype._renderBarOrLine = function(data, plotFunc, startFunc, endFunc) { | |
149 | ||
150 | var colorCount = this.options.colorScheme.length; | |
151 | var colorScheme = this.options.colorScheme; | |
152 | var setNames = MochiKit.Base.keys(this.layout.datasets); | |
153 | var setCount = setNames.length; | |
154 | ||
155 | for (var i = 0; i < setCount; i++) { | |
156 | var setName = setNames[i]; | |
157 | var attrs = new Array(); | |
158 | var color = colorScheme[i%colorCount]; | |
159 | ||
160 | if (this.options.shouldFill) | |
161 | attrs["fill"] = color.toRGBString(); | |
162 | else | |
163 | attrs["fill"] = "none"; | |
164 | ||
165 | if (this.options.shouldStroke && | |
166 | (this.options.strokeColor || this.options.strokeColorTransform)) { | |
167 | if (this.options.strokeColor) | |
168 | attrs["stroke"] = this.options.strokeColor.toRGBString(); | |
169 | else if (this.options.strokeColorTransform) | |
170 | attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString(); | |
171 | attrs["strokeWidth"] = this.options.strokeWidth; | |
172 | } | |
173 | ||
174 | if (startFunc) | |
175 | startFunc(attrs); | |
176 | ||
177 | var forEachFunc = function(obj) { | |
178 | if (obj.name == setName) | |
179 | plotFunc(attrs, obj); | |
180 | }; | |
181 | ||
182 | MochiKit.Iter.forEach(data, bind(forEachFunc, this)); | |
183 | if (endFunc) | |
184 | endFunc(attrs); | |
185 | } | |
186 | }; | |
187 | ||
188 | PlotKit.SVGRenderer.prototype._renderBarChart = function() { | |
189 | var bind = MochiKit.Base.bind; | |
190 | ||
191 | var drawRect = function(attrs, bar) { | |
192 | var x = this.area.w * bar.x + this.area.x; | |
193 | var y = this.area.h * bar.y + this.area.y; | |
194 | var w = this.area.w * bar.w; | |
195 | var h = this.area.h * bar.h; | |
196 | this._drawRect(x, y, w, h, attrs); | |
197 | }; | |
198 | this._renderBarOrLine(this.layout.bars, bind(drawRect, this)); | |
199 | }; | |
200 | ||
201 | PlotKit.SVGRenderer.prototype._renderLineChart = function() { | |
202 | var bind = MochiKit.Base.bind; | |
203 | ||
204 | var addPoint = function(attrs, point) { | |
205 | this._tempPointsBuffer += (this.area.w * point.x + this.area.x) + "," + | |
206 | (this.area.h * point.y + this.area.y) + " "; | |
207 | }; | |
208 | ||
209 | var startLine = function(attrs) { | |
210 | this._tempPointsBuffer = ""; | |
211 | this._tempPointsBuffer += (this.area.x) + "," + (this.area.y+this.area.h) + " "; | |
212 | }; | |
213 | ||
214 | var endLine = function(attrs) { | |
215 | this._tempPointsBuffer += (this.area.w + this.area.x) + "," +(this.area.h + this.area.y); | |
216 | attrs["points"] = this._tempPointsBuffer; | |
217 | var elem = this.createSVGElement("polygon", attrs); | |
218 | this.root.appendChild(elem); | |
219 | }; | |
220 | ||
221 | this._renderBarOrLine(this.layout.points, | |
222 | bind(addPoint, this), | |
223 | bind(startLine, this), | |
224 | bind(endLine, this)); | |
225 | }; | |
226 | ||
227 | ||
228 | PlotKit.SVGRenderer.prototype._renderPieChart = function() { | |
229 | var colorCount = this.options.colorScheme.length; | |
230 | var slices = this.layout.slices; | |
231 | ||
232 | var centerx = this.area.x + this.area.w * 0.5; | |
233 | var centery = this.area.y + this.area.h * 0.5; | |
234 | var radius = Math.min(this.area.w * this.options.pieRadius, | |
235 | this.area.h * this.options.pieRadius); | |
236 | ||
237 | // NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1 | |
238 | // so we have to subtract 90 degrees to make it start at y = 1, x = 0 | |
239 | ||
240 | // workaround if we only have 1 slice of 100% | |
241 | if (slices.length == 1 && (Math.abs(slices[0].startAngle) - Math.abs(slices[0].endAngle) < 0.1)) { | |
242 | var attrs = {"cx": centerx , "cy": centery , "r": radius }; | |
243 | var color = this.options.colorScheme[0]; | |
244 | if (this.options.shouldFill) | |
245 | attrs["fill"] = color.toRGBString(); | |
246 | else | |
247 | attrs["fill"] = "none"; | |
248 | ||
249 | if (this.options.shouldStroke && | |
250 | (this.options.strokeColor || this.options.strokeColorTransform)) { | |
251 | if (this.options.strokeColor) | |
252 | attrs["stroke"] = this.options.strokeColor.toRGBString(); | |
253 | else if (this.options.strokeColorTransform) | |
254 | attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString(); | |
255 | attrs["style"] = "stroke-width: " + this.options.strokeWidth; | |
256 | } | |
257 | ||
258 | this.root.appendChild(this.createSVGElement("circle", attrs)); | |
259 | return; | |
260 | } | |
261 | ||
262 | for (var i = 0; i < slices.length; i++) { | |
263 | var attrs = new Array(); | |
264 | var color = this.options.colorScheme[i%colorCount]; | |
265 | if (this.options.shouldFill) | |
266 | attrs["fill"] = color.toRGBString(); | |
267 | else | |
268 | attrs["fill"] = "none"; | |
269 | ||
270 | if (this.options.shouldStroke && | |
271 | (this.options.strokeColor || this.options.strokeColorTransform)) { | |
272 | if (this.options.strokeColor) | |
273 | attrs["stroke"] = this.options.strokeColor.toRGBString(); | |
274 | else if (this.options.strokeColorTransform) | |
275 | attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString(); | |
276 | attrs["style"] = "stroke-width:" + this.options.strokeWidth; | |
277 | } | |
278 | ||
279 | var largearc = 0; | |
280 | if (Math.abs(slices[i].endAngle - slices[i].startAngle) > Math.PI) | |
281 | largearc = 1; | |
282 | var x1 = Math.cos(slices[i].startAngle - Math.PI/2) * radius; | |
283 | var y1 = Math.sin(slices[i].startAngle - Math.PI/2) * radius; | |
284 | var x2 = Math.cos(slices[i].endAngle - Math.PI/2) * radius; | |
285 | var y2 = Math.sin(slices[i].endAngle - Math.PI/2) * radius; | |
286 | var rx = x2 - x1; | |
287 | var ry = y2 - y1; | |
288 | ||
289 | var pathString = "M" + centerx + "," + centery + " "; | |
290 | pathString += "l" + x1 + "," + y1 + " "; | |
291 | pathString += "a" + radius + "," + radius + " 0 " + largearc + ",1 " + rx + "," + ry + " z"; | |
292 | ||
293 | attrs["d"] = pathString; | |
294 | ||
295 | var elem = this.createSVGElement("path", attrs); | |
296 | this.root.appendChild(elem); | |
297 | } | |
298 | }; | |
299 | ||
300 | PlotKit.SVGRenderer.prototype._renderBarAxis = function() { | |
301 | this._renderAxis(); | |
302 | } | |
303 | ||
304 | PlotKit.SVGRenderer.prototype._renderLineAxis = function() { | |
305 | this._renderAxis(); | |
306 | }; | |
307 | ||
308 | ||
309 | PlotKit.SVGRenderer.prototype._renderAxis = function() { | |
310 | ||
311 | if (!this.options.drawXAxis && !this.options.drawYAxis) | |
312 | return; | |
313 | ||
314 | var labelStyle = {"style": | |
315 | {"position": "absolute", | |
316 | "textAlign": "center", | |
317 | "fontSize": this.options.axisLabelFontSize + "px", | |
318 | "zIndex": 10, | |
319 | "color": this.options.axisLabelColor.toRGBString(), | |
320 | "width": this.options.axisLabelWidth + "px", | |
321 | "overflow": "hidden" | |
322 | } | |
323 | }; | |
324 | ||
325 | // axis lines | |
326 | var lineAttrs = { | |
327 | "stroke": this.options.axisLineColor.toRGBString(), | |
328 | "strokeWidth": this.options.axisLineWidth | |
329 | }; | |
330 | ||
331 | ||
332 | if (this.options.drawYAxis) { | |
333 | if (this.layout.yticks) { | |
334 | var drawTick = function(tick) { | |
335 | var x = this.area.x; | |
336 | var y = this.area.y + tick[0] * this.area.h; | |
337 | this._drawLine(x, y, x - 3, y, lineAttrs); | |
338 | ||
339 | if (this.options.axisLabelUseDiv) { | |
340 | var label = DIV(labelStyle, tick[1]); | |
341 | label.style.top = (y - this.options.axisLabelFontSize) + "px"; | |
342 | label.style.left = (x - this.options.padding.left + this.options.axisTickSize) + "px"; | |
343 | label.style.textAlign = "left"; | |
344 | label.style.width = (this.options.padding.left - 3) + "px"; | |
345 | MochiKit.DOM.appendChildNodes(this.container, label); | |
346 | this.ylabels.push(label); | |
347 | } | |
348 | else { | |
349 | var attrs = { | |
350 | y: y + 3, | |
351 | x: (x - this.options.padding.left + 3), | |
352 | width: (this.options.padding.left - this.options.axisTickSize) + "px", | |
353 | height: (this.options.axisLabelFontSize + 3) + "px", | |
354 | fontFamily: "Arial", | |
355 | fontSize: this.options.axisLabelFontSize + "px", | |
356 | fill: this.options.axisLabelColor.toRGBString() | |
357 | }; | |
358 | ||
359 | /* we can do clipping just like DIVs | |
360 | http://www.xml.com/pub/a/2004/06/02/svgtype.html */ | |
361 | /* | |
362 | var mask = this.createSVGElement("mask", {id: "mask" + tick[0]}); | |
363 | var maskShape = this.createSVGElement("rect", | |
364 | {y: y + 3, | |
365 | x: (x - this.options.padding.left + 3), | |
366 | width: (this.options.padding.left - this.options.axisTickSize) + "px", | |
367 | height: (this.options.axisLabelFontSize + 3) + "px", | |
368 | style: {"fill": "#ffffff", "stroke": "#000000"}}); | |
369 | mask.appendChild(maskShape); | |
370 | this.defs.appendChild(mask); | |
371 | ||
372 | attrs["filter"] = "url(#mask" + tick[0] + ")"; | |
373 | */ | |
374 | ||
375 | var label = this.createSVGElement("text", attrs); | |
376 | label.appendChild(this.document.createTextNode(tick[1])); | |
377 | this.root.appendChild(label); | |
378 | } | |
379 | }; | |
380 | ||
381 | MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this)); | |
382 | } | |
383 | ||
384 | this._drawLine(this.area.x, this.area.y, this.area.x, this.area.y + this.area.h, lineAttrs); | |
385 | } | |
386 | ||
387 | if (this.options.drawXAxis) { | |
388 | if (this.layout.xticks) { | |
389 | var drawTick = function(tick) { | |
390 | var x = this.area.x + tick[0] * this.area.w; | |
391 | var y = this.area.y + this.area.h; | |
392 | this._drawLine(x, y, x, y + this.options.axisTickSize, lineAttrs); | |
393 | ||
394 | if (this.options.axisLabelUseDiv) { | |
395 | var label = DIV(labelStyle, tick[1]); | |
396 | label.style.top = (y + this.options.axisTickSize) + "px"; | |
397 | label.style.left = (x - this.options.axisLabelWidth/2) + "px"; | |
398 | label.style.textAlign = "center"; | |
399 | label.style.width = this.options.axisLabelWidth + "px"; | |
400 | MochiKit.DOM.appendChildNodes(this.container, label); | |
401 | this.xlabels.push(label); | |
402 | } | |
403 | else { | |
404 | var attrs = { | |
405 | y: (y + this.options.axisTickSize + this.options.axisLabelFontSize), | |
406 | x: x - 3, | |
407 | width: this.options.axisLabelWidth + "px", | |
408 | height: (this.options.axisLabelFontSize + 3) + "px", | |
409 | fontFamily: "Arial", | |
410 | fontSize: this.options.axisLabelFontSize + "px", | |
411 | fill: this.options.axisLabelColor.toRGBString(), | |
412 | textAnchor: "middle" | |
413 | }; | |
414 | var label = this.createSVGElement("text", attrs); | |
415 | label.appendChild(this.document.createTextNode(tick[1])); | |
416 | this.root.appendChild(label); | |
417 | } | |
418 | }; | |
419 | ||
420 | MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this)); | |
421 | } | |
422 | ||
423 | this._drawLine(this.area.x, this.area.y + this.area.h, this.area.x + this.area.w, this.area.y + this.area.h, lineAttrs) | |
424 | } | |
425 | }; | |
426 | ||
427 | PlotKit.SVGRenderer.prototype._renderPieAxis = function() { | |
428 | ||
429 | if (this.layout.xticks) { | |
430 | // make a lookup dict for x->slice values | |
431 | var lookup = new Array(); | |
432 | for (var i = 0; i < this.layout.slices.length; i++) { | |
433 | lookup[this.layout.slices[i].xval] = this.layout.slices[i]; | |
434 | } | |
435 | ||
436 | var centerx = this.area.x + this.area.w * 0.5; | |
437 | var centery = this.area.y + this.area.h * 0.5; | |
438 | var radius = Math.min(this.area.w * this.options.pieRadius + 10, | |
439 | this.area.h * this.options.pieRadius + 10); | |
440 | var labelWidth = this.options.axisLabelWidth; | |
441 | ||
442 | for (var i = 0; i < this.layout.xticks.length; i++) { | |
443 | var slice = lookup[this.layout.xticks[i][0]]; | |
444 | if (MochiKit.Base.isUndefinedOrNull(slice)) | |
445 | continue; | |
446 | ||
447 | ||
448 | var angle = (slice.startAngle + slice.endAngle)/2; | |
449 | // normalize the angle | |
450 | var normalisedAngle = angle; | |
451 | if (normalisedAngle > Math.PI * 2) | |
452 | normalisedAngle = normalisedAngle - Math.PI * 2; | |
453 | else if (normalisedAngle < 0) | |
454 | normalisedAngle = normalisedAngle + Math.PI * 2; | |
455 | ||
456 | var labelx = centerx + Math.sin(normalisedAngle) * (radius + 10); | |
457 | var labely = centery - Math.cos(normalisedAngle) * (radius + 10); | |
458 | ||
459 | var attrib = { | |
460 | "position": "absolute", | |
461 | "zIndex": 11, | |
462 | "width": labelWidth + "px", | |
463 | "fontSize": this.options.axisLabelFontSize + "px", | |
464 | "overflow": "hidden", | |
465 | "color": this.options.axisLabelColor.toHexString() | |
466 | }; | |
467 | ||
468 | var svgattrib = { | |
469 | "width": labelWidth + "px", | |
470 | "fontSize": this.options.axisLabelFontSize + "px", | |
471 | "height": (this.options.axisLabelFontSize + 3) + "px", | |
472 | "fill": this.options.axisLabelColor.toRGBString() | |
473 | }; | |
474 | ||
475 | if (normalisedAngle <= Math.PI * 0.5) { | |
476 | // text on top and align left | |
477 | MochiKit.Base.update(attrib, { | |
478 | 'textAlign': 'left', 'verticalAlign': 'top', | |
479 | 'left': labelx + 'px', | |
480 | 'top': (labely - this.options.axisLabelFontSize) + "px" | |
481 | }); | |
482 | MochiKit.Base.update(svgattrib, { | |
483 | "x": labelx, | |
484 | "y" :(labely - this.options.axisLabelFontSize), | |
485 | "textAnchor": "left" | |
486 | }); | |
487 | } | |
488 | else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) { | |
489 | // text on bottom and align left | |
490 | MochiKit.Base.update(attrib, { | |
491 | 'textAlign': 'left', 'verticalAlign': 'bottom', | |
492 | 'left': labelx + 'px', | |
493 | 'top': labely + "px" | |
494 | }); | |
495 | MochiKit.Base.update(svgattrib, { | |
496 | 'textAnchor': 'left', | |
497 | 'x': labelx, | |
498 | 'y': labely | |
499 | }); | |
500 | } | |
501 | else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) { | |
502 | // text on bottom and align right | |
503 | MochiKit.Base.update(attrib, { | |
504 | 'textAlign': 'right', 'verticalAlign': 'bottom', | |
505 | 'left': labelx + 'px', | |
506 | 'top': labely + "px" | |
507 | }); | |
508 | MochiKit.Base.update(svgattrib, { | |
509 | 'textAnchor': 'right', | |
510 | 'x': labelx - labelWidth, | |
511 | 'y': labely | |
512 | }); | |
513 | } | |
514 | else { | |
515 | // text on top and align right | |
516 | MochiKit.Base.update(attrib, { | |
517 | 'textAlign': 'left', 'verticalAlign': 'bottom', | |
518 | 'left': labelx + 'px', | |
519 | 'top': labely + "px" | |
520 | }); | |
521 | MochiKit.Base.update(svgattrib, { | |
522 | 'textAnchor': 'left', | |
523 | 'x': labelx - labelWidth, | |
524 | 'y': labely - this.options.axisLabelFontSize | |
525 | }); | |
526 | } | |
527 | ||
528 | if (this.options.axisLabelUseDiv) { | |
529 | var label = DIV({'style': attrib}, this.layout.xticks[i][1]); | |
530 | this.xlabels.push(label); | |
531 | MochiKit.DOM.appendChildNodes(this.container, label); | |
532 | } | |
533 | else { | |
534 | var label = this.createSVGElement("text", svgattrib); | |
535 | label.appendChild(this.document.createTextNode(this.layout.xticks[i][1])) | |
536 | this.root.appendChild(label); | |
537 | } | |
538 | } | |
539 | ||
540 | } | |
541 | }; | |
542 | ||
543 | PlotKit.SVGRenderer.prototype._renderBackground = function() { | |
544 | var opts = {"stroke": "none", | |
545 | "fill": this.options.backgroundColor.toRGBString() | |
546 | }; | |
547 | this._drawRect(0, 0, this.width, this.height, opts); | |
548 | }; | |
549 | ||
550 | PlotKit.SVGRenderer.prototype._drawRect = function(x, y, w, h, moreattrs) { | |
551 | var attrs = {x: x + "px", y: y + "px", width: w + "px", height: h + "px"}; | |
552 | if (moreattrs) | |
553 | MochiKit.Base.update(attrs, moreattrs); | |
554 | ||
555 | var elem = this.createSVGElement("rect", attrs); | |
556 | this.root.appendChild(elem); | |
557 | }; | |
558 | ||
559 | PlotKit.SVGRenderer.prototype._drawLine = function(x1, y1, x2, y2, moreattrs) { | |
560 | var attrs = {x1: x1 + "px", y1: y1 + "px", x2: x2 + "px", y2: y2 + "px"}; | |
561 | if (moreattrs) | |
562 | MochiKit.Base.update(attrs, moreattrs); | |
563 | ||
564 | var elem = this.createSVGElement("line", attrs); | |
565 | this.root.appendChild(elem); | |
566 | } | |
567 | ||
568 | PlotKit.SVGRenderer.prototype.clear = function() { | |
569 | while(this.element.firstChild) { | |
570 | this.element.removeChild(this.element.firstChild); | |
571 | } | |
572 | ||
573 | if (this.options.axisLabelUseDiv) { | |
574 | for (var i = 0; i < this.xlabels.length; i++) { | |
575 | MochiKit.DOM.removeElement(this.xlabels[i]); | |
576 | } | |
577 | for (var i = 0; i < this.ylabels.length; i++) { | |
578 | MochiKit.DOM.removeElement(this.ylabels[i]); | |
579 | } | |
580 | } | |
581 | this.xlabels = new Array(); | |
582 | this.ylabels = new Array(); | |
583 | }; | |
584 | ||
585 | ||
586 | PlotKit.SVGRenderer.prototype.createSVGElement = function(name, attrs) { | |
587 | var isNil = MochiKit.Base.isUndefinedOrNull; | |
588 | var elem; | |
589 | var doc = isNil(this.document) ? document : this.document; | |
590 | ||
591 | try { | |
592 | elem = doc.createElementNS(PlotKit.SVGRenderer.SVGNS, name); | |
593 | } | |
594 | catch (e) { | |
595 | elem = doc.createElement(name); | |
596 | elem.setAttribute("xmlns", PlotKit.SVGRenderer.SVGNS); | |
597 | } | |
598 | ||
599 | if (attrs) | |
600 | MochiKit.DOM.updateNodeAttributes(elem, attrs); | |
601 | ||
602 | // TODO: we don't completely emulate the MochiKit.DOM.createElement | |
603 | // as we don't care about nodes contained. We really should though. | |
604 | ||
605 | return elem; | |
606 | ||
607 | }; | |
608 | ||
609 | ||
610 | PlotKit.SVGRenderer.SVG = function(attrs) { | |
611 | // we have to do things differently for IE+AdobeSVG. | |
612 | // My guess this works (via trial and error) is that we need to | |
613 | // have an SVG object in order to use SVGDocument.createElementNS | |
614 | // but IE doesn't allow us to that. | |
615 | ||
616 | var ie = navigator.appVersion.match(/MSIE (\d\.\d)/); | |
617 | var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1); | |
618 | if (ie && (ie[1] >= 6) && (!opera)) { | |
619 | var width = attrs["width"] ? attrs["width"] : "100"; | |
620 | var height = attrs["height"] ? attrs["height"] : "100"; | |
621 | var eid = attrs["id"] ? attrs["id"] : "notunique"; | |
622 | ||
623 | var html = '<svg:svg width="' + width + '" height="' + height + '" '; | |
624 | html += 'id="' + eid + '" version="1.1" baseProfile="full" />'; | |
625 | ||
626 | var canvas = document.createElement(html); | |
627 | ||
628 | // create embedded SVG inside SVG. | |
629 | var group = canvas.getSVGDocument().createElementNS(PlotKit.SVGRenderer.SVGNS, "svg"); | |
630 | group.setAttribute("width", width); | |
631 | group.setAttribute("height", height); | |
632 | canvas.getSVGDocument().appendChild(group); | |
633 | ||
634 | return canvas; | |
635 | } | |
636 | else { | |
637 | return PlotKit.SVGRenderer.prototype.createSVGElement("svg", attrs); | |
638 | } | |
639 | }; | |
640 | ||
641 | PlotKit.SVGRenderer.isSupported = function() { | |
642 | var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1); | |
643 | var ieVersion = navigator.appVersion.match(/MSIE (\d\.\d)/); | |
644 | var safariVersion = navigator.userAgent.match(/AppleWebKit\/(\d+)/); | |
645 | var operaVersion = navigator.userAgent.match(/Opera\/(\d*\.\d*)/); | |
646 | var mozillaVersion = navigator.userAgent.match(/rv:(\d*\.\d*).*Gecko/); | |
647 | var svgFeature = "http://www.w3.org/TR/SVG11/feature#SVG"; | |
648 | ||
649 | if (ieVersion && (ieVersion[1] >= 6) && !isOpera) { | |
650 | return document.implementation.hasFeature(svgFeature,"1.1"); | |
651 | /* | |
652 | var dummysvg = document.createElement('<svg:svg width="1" height="1" baseProfile="full" version="1.1" id="dummy">'); | |
653 | try { | |
654 | dummysvg.getSVGDocument(); | |
655 | dummysvg = null; | |
656 | return true; | |
657 | } | |
658 | catch (e) { | |
659 | return false; | |
660 | } | |
661 | */ | |
662 | ||
663 | } | |
664 | ||
665 | /* support not really there yet. no text and paths are buggy | |
666 | if (safariVersion && (safariVersion[1] > 419)) | |
667 | return true; | |
668 | */ | |
669 | ||
670 | if (operaVersion && (operaVersion[1] > 8.9)) | |
671 | return true | |
672 | ||
673 | if (mozillaVersion && (mozillaVersion > 1.7)) | |
674 | return true; | |
675 | ||
676 | return false; | |
677 | }; | |
678 | ||
679 | // Namespace Iniitialisation | |
680 | ||
681 | PlotKit.SVG = {} | |
682 | PlotKit.SVG.SVGRenderer = PlotKit.SVGRenderer; | |
683 | ||
684 | PlotKit.SVG.EXPORT = [ | |
685 | "SVGRenderer" | |
686 | ]; | |
687 | ||
688 | PlotKit.SVG.EXPORT_OK = [ | |
689 | "SVGRenderer" | |
690 | ]; | |
691 | ||
692 | PlotKit.SVG.__new__ = function() { | |
693 | var m = MochiKit.Base; | |
694 | ||
695 | m.nameFunctions(this); | |
696 | ||
697 | this.EXPORT_TAGS = { | |
698 | ":common": this.EXPORT, | |
699 | ":all": m.concat(this.EXPORT, this.EXPORT_OK) | |
700 | }; | |
701 | }; | |
702 | ||
703 | PlotKit.SVG.__new__(); | |
704 | MochiKit.Base._exportSymbols(this, PlotKit.SVG); | |
705 |