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