Mercurial > bitten > bitten-test
changeset 694:a174f33676cc
Revert "First attempt to start doing open charting using [http://code.google.com/p/flot/ flot] - see #426" - not quite time to commit it...
author | dfraser |
---|---|
date | Thu, 17 Sep 2009 16:00:38 +0000 |
parents | f15151b3ab96 |
children | 67b1f4577359 |
files | bitten/htdocs/charts.swf bitten/htdocs/excanvas.js bitten/htdocs/jquery.flot.js bitten/report/coverage.py bitten/report/lint.py bitten/report/testing.py bitten/templates/bitten_chart_coverage.html bitten/templates/bitten_chart_lint.html bitten/templates/bitten_chart_tests.html bitten/templates/bitten_config.html bitten/templates/json.txt bitten/web_ui.py |
diffstat | 12 files changed, 147 insertions(+), 2969 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e2821facdb1d55065baf6a435dafe72de5d8e777 GIT binary patch literal 29521 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 AdH?_b
deleted file mode 100644 --- a/bitten/htdocs/excanvas.js +++ /dev/null @@ -1,785 +0,0 @@ -// Copyright 2006 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// Known Issues: -// -// * Patterns are not implemented. -// * Radial gradient are not implemented. The VML version of these look very -// different from the canvas one. -// * Clipping paths are not implemented. -// * Coordsize. The width and height attribute have higher priority than the -// width and height style values which isn't correct. -// * Painting mode isn't implemented. -// * Canvas width/height should is using content-box by default. IE in -// Quirks mode will draw the canvas using border-box. Either change your -// doctype to HTML5 -// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) -// or use Box Sizing Behavior from WebFX -// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) -// * Optimize. There is always room for speed improvements. - -// only add this code if we do not already have a canvas implementation -if (!window.CanvasRenderingContext2D) { - -(function () { - - // alias some functions to make (compiled) code shorter - var m = Math; - var mr = m.round; - var ms = m.sin; - var mc = m.cos; - - // this is used for sub pixel precision - var Z = 10; - var Z2 = Z / 2; - - var G_vmlCanvasManager_ = { - init: function (opt_doc) { - var doc = opt_doc || document; - if (/MSIE/.test(navigator.userAgent) && !window.opera) { - var self = this; - doc.attachEvent("onreadystatechange", function () { - self.init_(doc); - }); - } - }, - - init_: function (doc) { - if (doc.readyState == "complete") { - // create xmlns - if (!doc.namespaces["g_vml_"]) { - doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml"); - } - - // setup default css - var ss = doc.createStyleSheet(); - ss.cssText = "canvas{display:inline-block;overflow:hidden;" + - // default size is 300x150 in Gecko and Opera - "text-align:left;width:300px;height:150px}" + - "g_vml_\\:*{behavior:url(#default#VML)}"; - - // find all canvas elements - var els = doc.getElementsByTagName("canvas"); - for (var i = 0; i < els.length; i++) { - if (!els[i].getContext) { - this.initElement(els[i]); - } - } - } - }, - - fixElement_: function (el) { - // in IE before version 5.5 we would need to add HTML: to the tag name - // but we do not care about IE before version 6 - var outerHTML = el.outerHTML; - - var newEl = el.ownerDocument.createElement(outerHTML); - // if the tag is still open IE has created the children as siblings and - // it has also created a tag with the name "/FOO" - if (outerHTML.slice(-2) != "/>") { - var tagName = "/" + el.tagName; - var ns; - // remove content - while ((ns = el.nextSibling) && ns.tagName != tagName) { - ns.removeNode(); - } - // remove the incorrect closing tag - if (ns) { - ns.removeNode(); - } - } - el.parentNode.replaceChild(newEl, el); - return newEl; - }, - - /** - * Public initializes a canvas element so that it can be used as canvas - * element from now on. This is called automatically before the page is - * loaded but if you are creating elements using createElement you need to - * make sure this is called on the element. - * @param {HTMLElement} el The canvas element to initialize. - * @return {HTMLElement} the element that was created. - */ - initElement: function (el) { - el = this.fixElement_(el); - el.getContext = function () { - if (this.context_) { - return this.context_; - } - return this.context_ = new CanvasRenderingContext2D_(this); - }; - - // do not use inline function because that will leak memory - el.attachEvent('onpropertychange', onPropertyChange); - el.attachEvent('onresize', onResize); - - var attrs = el.attributes; - if (attrs.width && attrs.width.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setWidth_(attrs.width.nodeValue); - el.style.width = attrs.width.nodeValue + "px"; - } else { - el.width = el.clientWidth; - } - if (attrs.height && attrs.height.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setHeight_(attrs.height.nodeValue); - el.style.height = attrs.height.nodeValue + "px"; - } else { - el.height = el.clientHeight; - } - //el.getContext().setCoordsize_() - return el; - } - }; - - function onPropertyChange(e) { - var el = e.srcElement; - - switch (e.propertyName) { - case 'width': - el.style.width = el.attributes.width.nodeValue + "px"; - el.getContext().clearRect(); - break; - case 'height': - el.style.height = el.attributes.height.nodeValue + "px"; - el.getContext().clearRect(); - break; - } - } - - function onResize(e) { - var el = e.srcElement; - if (el.firstChild) { - el.firstChild.style.width = el.clientWidth + 'px'; - el.firstChild.style.height = el.clientHeight + 'px'; - } - } - - G_vmlCanvasManager_.init(); - - // precompute "00" to "FF" - var dec2hex = []; - for (var i = 0; i < 16; i++) { - for (var j = 0; j < 16; j++) { - dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); - } - } - - function createMatrixIdentity() { - return [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ]; - } - - function matrixMultiply(m1, m2) { - var result = createMatrixIdentity(); - - for (var x = 0; x < 3; x++) { - for (var y = 0; y < 3; y++) { - var sum = 0; - - for (var z = 0; z < 3; z++) { - sum += m1[x][z] * m2[z][y]; - } - - result[x][y] = sum; - } - } - return result; - } - - function copyState(o1, o2) { - o2.fillStyle = o1.fillStyle; - o2.lineCap = o1.lineCap; - o2.lineJoin = o1.lineJoin; - o2.lineWidth = o1.lineWidth; - o2.miterLimit = o1.miterLimit; - o2.shadowBlur = o1.shadowBlur; - o2.shadowColor = o1.shadowColor; - o2.shadowOffsetX = o1.shadowOffsetX; - o2.shadowOffsetY = o1.shadowOffsetY; - o2.strokeStyle = o1.strokeStyle; - o2.arcScaleX_ = o1.arcScaleX_; - o2.arcScaleY_ = o1.arcScaleY_; - } - - function processStyle(styleString) { - var str, alpha = 1; - - styleString = String(styleString); - if (styleString.substring(0, 3) == "rgb") { - var start = styleString.indexOf("(", 3); - var end = styleString.indexOf(")", start + 1); - var guts = styleString.substring(start + 1, end).split(","); - - str = "#"; - for (var i = 0; i < 3; i++) { - str += dec2hex[Number(guts[i])]; - } - - if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) { - alpha = guts[3]; - } - } else { - str = styleString; - } - - return [str, alpha]; - } - - function processLineCap(lineCap) { - switch (lineCap) { - case "butt": - return "flat"; - case "round": - return "round"; - case "square": - default: - return "square"; - } - } - - /** - * This class implements CanvasRenderingContext2D interface as described by - * the WHATWG. - * @param {HTMLElement} surfaceElement The element that the 2D context should - * be associated with - */ - function CanvasRenderingContext2D_(surfaceElement) { - this.m_ = createMatrixIdentity(); - - this.mStack_ = []; - this.aStack_ = []; - this.currentPath_ = []; - - // Canvas context properties - this.strokeStyle = "#000"; - this.fillStyle = "#000"; - - this.lineWidth = 1; - this.lineJoin = "miter"; - this.lineCap = "butt"; - this.miterLimit = Z * 1; - this.globalAlpha = 1; - this.canvas = surfaceElement; - - var el = surfaceElement.ownerDocument.createElement('div'); - el.style.width = surfaceElement.clientWidth + 'px'; - el.style.height = surfaceElement.clientHeight + 'px'; - el.style.overflow = 'hidden'; - el.style.position = 'absolute'; - surfaceElement.appendChild(el); - - this.element_ = el; - this.arcScaleX_ = 1; - this.arcScaleY_ = 1; - } - - var contextPrototype = CanvasRenderingContext2D_.prototype; - contextPrototype.clearRect = function() { - this.element_.innerHTML = ""; - this.currentPath_ = []; - }; - - contextPrototype.beginPath = function() { - // TODO: Branch current matrix so that save/restore has no effect - // as per safari docs. - - this.currentPath_ = []; - }; - - contextPrototype.moveTo = function(aX, aY) { - this.currentPath_.push({type: "moveTo", x: aX, y: aY}); - this.currentX_ = aX; - this.currentY_ = aY; - }; - - contextPrototype.lineTo = function(aX, aY) { - this.currentPath_.push({type: "lineTo", x: aX, y: aY}); - this.currentX_ = aX; - this.currentY_ = aY; - }; - - contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, - aCP2x, aCP2y, - aX, aY) { - this.currentPath_.push({type: "bezierCurveTo", - cp1x: aCP1x, - cp1y: aCP1y, - cp2x: aCP2x, - cp2y: aCP2y, - x: aX, - y: aY}); - this.currentX_ = aX; - this.currentY_ = aY; - }; - - contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { - // the following is lifted almost directly from - // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes - var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_); - var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_); - var cp2x = cp1x + (aX - this.currentX_) / 3.0; - var cp2y = cp1y + (aY - this.currentY_) / 3.0; - this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY); - }; - - contextPrototype.arc = function(aX, aY, aRadius, - aStartAngle, aEndAngle, aClockwise) { - aRadius *= Z; - var arcType = aClockwise ? "at" : "wa"; - - var xStart = aX + (mc(aStartAngle) * aRadius) - Z2; - var yStart = aY + (ms(aStartAngle) * aRadius) - Z2; - - var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2; - var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2; - - // IE won't render arches drawn counter clockwise if xStart == xEnd. - if (xStart == xEnd && !aClockwise) { - xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something - // that can be represented in binary - } - - this.currentPath_.push({type: arcType, - x: aX, - y: aY, - radius: aRadius, - xStart: xStart, - yStart: yStart, - xEnd: xEnd, - yEnd: yEnd}); - - }; - - contextPrototype.rect = function(aX, aY, aWidth, aHeight) { - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - }; - - contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { - // Will destroy any existing path (same as FF behaviour) - this.beginPath(); - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.stroke(); - }; - - contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { - // Will destroy any existing path (same as FF behaviour) - this.beginPath(); - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.fill(); - }; - - contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { - var gradient = new CanvasGradient_("gradient"); - return gradient; - }; - - contextPrototype.createRadialGradient = function(aX0, aY0, - aR0, aX1, - aY1, aR1) { - var gradient = new CanvasGradient_("gradientradial"); - gradient.radius1_ = aR0; - gradient.radius2_ = aR1; - gradient.focus_.x = aX0; - gradient.focus_.y = aY0; - return gradient; - }; - - contextPrototype.drawImage = function (image, var_args) { - var dx, dy, dw, dh, sx, sy, sw, sh; - - // to find the original width we overide the width and height - var oldRuntimeWidth = image.runtimeStyle.width; - var oldRuntimeHeight = image.runtimeStyle.height; - image.runtimeStyle.width = 'auto'; - image.runtimeStyle.height = 'auto'; - - // get the original size - var w = image.width; - var h = image.height; - - // and remove overides - image.runtimeStyle.width = oldRuntimeWidth; - image.runtimeStyle.height = oldRuntimeHeight; - - if (arguments.length == 3) { - dx = arguments[1]; - dy = arguments[2]; - sx = sy = 0; - sw = dw = w; - sh = dh = h; - } else if (arguments.length == 5) { - dx = arguments[1]; - dy = arguments[2]; - dw = arguments[3]; - dh = arguments[4]; - sx = sy = 0; - sw = w; - sh = h; - } else if (arguments.length == 9) { - sx = arguments[1]; - sy = arguments[2]; - sw = arguments[3]; - sh = arguments[4]; - dx = arguments[5]; - dy = arguments[6]; - dw = arguments[7]; - dh = arguments[8]; - } else { - throw "Invalid number of arguments"; - } - - var d = this.getCoords_(dx, dy); - - var w2 = sw / 2; - var h2 = sh / 2; - - var vmlStr = []; - - var W = 10; - var H = 10; - - // For some reason that I've now forgotten, using divs didn't work - vmlStr.push(' <g_vml_:group', - ' coordsize="', Z * W, ',', Z * H, '"', - ' coordorigin="0,0"' , - ' style="width:', W, ';height:', H, ';position:absolute;'); - - // If filters are necessary (rotation exists), create them - // filters are bog-slow, so only create them if abbsolutely necessary - // The following check doesn't account for skews (which don't exist - // in the canvas spec (yet) anyway. - - if (this.m_[0][0] != 1 || this.m_[0][1]) { - var filter = []; - - // Note the 12/21 reversal - filter.push("M11='", this.m_[0][0], "',", - "M12='", this.m_[1][0], "',", - "M21='", this.m_[0][1], "',", - "M22='", this.m_[1][1], "',", - "Dx='", mr(d.x / Z), "',", - "Dy='", mr(d.y / Z), "'"); - - // Bounding box calculation (need to minimize displayed area so that - // filters don't waste time on unused pixels. - var max = d; - var c2 = this.getCoords_(dx + dw, dy); - var c3 = this.getCoords_(dx, dy + dh); - var c4 = this.getCoords_(dx + dw, dy + dh); - - max.x = Math.max(max.x, c2.x, c3.x, c4.x); - max.y = Math.max(max.y, c2.y, c3.y, c4.y); - - vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z), - "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(", - filter.join(""), ", sizingmethod='clip');"); - } else { - vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;"); - } - - vmlStr.push(' ">' , - '<g_vml_:image src="', image.src, '"', - ' style="width:', Z * dw, ';', - ' height:', Z * dh, ';"', - ' cropleft="', sx / w, '"', - ' croptop="', sy / h, '"', - ' cropright="', (w - sx - sw) / w, '"', - ' cropbottom="', (h - sy - sh) / h, '"', - ' />', - '</g_vml_:group>'); - - this.element_.insertAdjacentHTML("BeforeEnd", - vmlStr.join("")); - }; - - contextPrototype.stroke = function(aFill) { - var lineStr = []; - var lineOpen = false; - var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); - var color = a[0]; - var opacity = a[1] * this.globalAlpha; - - var W = 10; - var H = 10; - - lineStr.push('<g_vml_:shape', - ' fillcolor="', color, '"', - ' filled="', Boolean(aFill), '"', - ' style="position:absolute;width:', W, ';height:', H, ';"', - ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"', - ' stroked="', !aFill, '"', - ' strokeweight="', this.lineWidth, '"', - ' strokecolor="', color, '"', - ' path="'); - - var newSeq = false; - var min = {x: null, y: null}; - var max = {x: null, y: null}; - - for (var i = 0; i < this.currentPath_.length; i++) { - var p = this.currentPath_[i]; - - if (p.type == "moveTo") { - lineStr.push(" m "); - var c = this.getCoords_(p.x, p.y); - lineStr.push(mr(c.x), ",", mr(c.y)); - } else if (p.type == "lineTo") { - lineStr.push(" l "); - var c = this.getCoords_(p.x, p.y); - lineStr.push(mr(c.x), ",", mr(c.y)); - } else if (p.type == "close") { - lineStr.push(" x "); - } else if (p.type == "bezierCurveTo") { - lineStr.push(" c "); - var c = this.getCoords_(p.x, p.y); - var c1 = this.getCoords_(p.cp1x, p.cp1y); - var c2 = this.getCoords_(p.cp2x, p.cp2y); - lineStr.push(mr(c1.x), ",", mr(c1.y), ",", - mr(c2.x), ",", mr(c2.y), ",", - mr(c.x), ",", mr(c.y)); - } else if (p.type == "at" || p.type == "wa") { - lineStr.push(" ", p.type, " "); - var c = this.getCoords_(p.x, p.y); - var cStart = this.getCoords_(p.xStart, p.yStart); - var cEnd = this.getCoords_(p.xEnd, p.yEnd); - - lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",", - mr(c.y - this.arcScaleY_ * p.radius), " ", - mr(c.x + this.arcScaleX_ * p.radius), ",", - mr(c.y + this.arcScaleY_ * p.radius), " ", - mr(cStart.x), ",", mr(cStart.y), " ", - mr(cEnd.x), ",", mr(cEnd.y)); - } - - - // TODO: Following is broken for curves due to - // move to proper paths. - - // Figure out dimensions so we can do gradient fills - // properly - if(c) { - if (min.x == null || c.x < min.x) { - min.x = c.x; - } - if (max.x == null || c.x > max.x) { - max.x = c.x; - } - if (min.y == null || c.y < min.y) { - min.y = c.y; - } - if (max.y == null || c.y > max.y) { - max.y = c.y; - } - } - } - lineStr.push(' ">'); - - if (typeof this.fillStyle == "object") { - var focus = {x: "50%", y: "50%"}; - var width = (max.x - min.x); - var height = (max.y - min.y); - var dimension = (width > height) ? width : height; - - focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; - focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; - - var colors = []; - - // inside radius (%) - if (this.fillStyle.type_ == "gradientradial") { - var inside = (this.fillStyle.radius1_ / dimension * 100); - - // percentage that outside radius exceeds inside radius - var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; - } else { - var inside = 0; - var expansion = 100; - } - - var insidecolor = {offset: null, color: null}; - var outsidecolor = {offset: null, color: null}; - - // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie - // won't interpret it correctly - this.fillStyle.colors_.sort(function (cs1, cs2) { - return cs1.offset - cs2.offset; - }); - - for (var i = 0; i < this.fillStyle.colors_.length; i++) { - var fs = this.fillStyle.colors_[i]; - - colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); - - if (fs.offset > insidecolor.offset || insidecolor.offset == null) { - insidecolor.offset = fs.offset; - insidecolor.color = fs.color; - } - - if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) { - outsidecolor.offset = fs.offset; - outsidecolor.color = fs.color; - } - } - colors.pop(); - - lineStr.push('<g_vml_:fill', - ' color="', outsidecolor.color, '"', - ' color2="', insidecolor.color, '"', - ' type="', this.fillStyle.type_, '"', - ' focusposition="', focus.x, ', ', focus.y, '"', - ' colors="', colors.join(""), '"', - ' opacity="', opacity, '" />'); - } else if (aFill) { - lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />'); - } else { - lineStr.push( - '<g_vml_:stroke', - ' opacity="', opacity,'"', - ' joinstyle="', this.lineJoin, '"', - ' miterlimit="', this.miterLimit, '"', - ' endcap="', processLineCap(this.lineCap) ,'"', - ' weight="', this.lineWidth, 'px"', - ' color="', color,'" />' - ); - } - - lineStr.push("</g_vml_:shape>"); - - this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); - - //this.currentPath_ = []; - }; - - contextPrototype.fill = function() { - this.stroke(true); - }; - - contextPrototype.closePath = function() { - this.currentPath_.push({type: "close"}); - }; - - /** - * @private - */ - contextPrototype.getCoords_ = function(aX, aY) { - return { - x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2, - y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2 - } - }; - - contextPrototype.save = function() { - var o = {}; - copyState(this, o); - this.aStack_.push(o); - this.mStack_.push(this.m_); - this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); - }; - - contextPrototype.restore = function() { - copyState(this.aStack_.pop(), this); - this.m_ = this.mStack_.pop(); - }; - - contextPrototype.translate = function(aX, aY) { - var m1 = [ - [1, 0, 0], - [0, 1, 0], - [aX, aY, 1] - ]; - - this.m_ = matrixMultiply(m1, this.m_); - }; - - contextPrototype.rotate = function(aRot) { - var c = mc(aRot); - var s = ms(aRot); - - var m1 = [ - [c, s, 0], - [-s, c, 0], - [0, 0, 1] - ]; - - this.m_ = matrixMultiply(m1, this.m_); - }; - - contextPrototype.scale = function(aX, aY) { - this.arcScaleX_ *= aX; - this.arcScaleY_ *= aY; - var m1 = [ - [aX, 0, 0], - [0, aY, 0], - [0, 0, 1] - ]; - - this.m_ = matrixMultiply(m1, this.m_); - }; - - /******** STUBS ********/ - contextPrototype.clip = function() { - // TODO: Implement - }; - - contextPrototype.arcTo = function() { - // TODO: Implement - }; - - contextPrototype.createPattern = function() { - return new CanvasPattern_; - }; - - // Gradient / Pattern Stubs - function CanvasGradient_(aType) { - this.type_ = aType; - this.radius1_ = 0; - this.radius2_ = 0; - this.colors_ = []; - this.focus_ = {x: 0, y: 0}; - } - - CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { - aColor = processStyle(aColor); - this.colors_.push({offset: 1-aOffset, color: aColor}); - }; - - function CanvasPattern_() {} - - // set up externs - G_vmlCanvasManager = G_vmlCanvasManager_; - CanvasRenderingContext2D = CanvasRenderingContext2D_; - CanvasGradient = CanvasGradient_; - CanvasPattern = CanvasPattern_; - -})(); - -} // if
deleted file mode 100644 --- a/bitten/htdocs/jquery.flot.js +++ /dev/null @@ -1,2136 +0,0 @@ -/* Javascript plotting library for jQuery, v. 0.5. - * - * Released under the MIT license by IOLA, December 2007. - * - */ - -(function($) { - function Plot(target_, data_, options_) { - // data is on the form: - // [ series1, series2 ... ] - // where series is either just the data as [ [x1, y1], [x2, y2], ... ] - // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" } - - var series = [], - options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of colums in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85 // set to 0 to avoid background - }, - xaxis: { - mode: null, // null or "time" - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - - // mode specific options - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null, // number or [number, "unit"] - monthNames: null, // list of names of months - timeformat: null // format string to use - }, - yaxis: { - autoscaleMargin: 0.02 - }, - x2axis: { - autoscaleMargin: null - }, - y2axis: { - autoscaleMargin: 0.02 - }, - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff" - }, - lines: { - show: false, - lineWidth: 2, // in pixels - fill: false, - fillColor: null - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left" // or "center" - }, - grid: { - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - tickColor: "#dddddd", // color used for the ticks - labelMargin: 5, // in pixels - borderWidth: 2, - markings: null, // array of ranges or fn: axes -> array of ranges - markingsColor: "#f4f4f4", - markingsLineWidth: 2, - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - selection: { - mode: null, // one of null, "x", "y" or "xy" - color: "#e8cfac" - }, - shadowSize: 4 - }, - canvas = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - target = target_, - axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} }, - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - canvasWidth = 0, canvasHeight = 0, - plotWidth = 0, plotHeight = 0, - // dedicated to storing data for buggy standard compliance cases - workarounds = {}; - - this.setData = setData; - this.setupGrid = setupGrid; - this.draw = draw; - this.clearSelection = clearSelection; - this.setSelection = setSelection; - this.getCanvas = function() { return canvas; }; - this.getPlotOffset = function() { return plotOffset; }; - this.getData = function() { return series; }; - this.getAxes = function() { return axes; }; - this.highlight = highlight; - this.unhighlight = unhighlight; - - // initialize - parseOptions(options_); - setData(data_); - constructCanvas(); - setupGrid(); - draw(); - - - function setData(d) { - series = parseData(d); - - fillInSeriesOptions(); - processData(); - } - - function parseData(d) { - var res = []; - for (var i = 0; i < d.length; ++i) { - var s; - if (d[i].data) { - s = {}; - for (var v in d[i]) - s[v] = d[i][v]; - } - else { - s = { data: d[i] }; - } - res.push(s); - } - - return res; - } - - function parseOptions(o) { - $.extend(true, options, o); - - // backwards compatibility, to be removed in future - if (options.xaxis.noTicks && options.xaxis.ticks == null) - options.xaxis.ticks = options.xaxis.noTicks; - if (options.yaxis.noTicks && options.yaxis.ticks == null) - options.yaxis.ticks = options.yaxis.noTicks; - if (options.grid.coloredAreas) - options.grid.markings = options.grid.coloredAreas; - if (options.grid.coloredAreasColor) - options.grid.markingsColor = options.grid.coloredAreasColor; - } - - function fillInSeriesOptions() { - var i; - - // collect what we already got of colors - var neededColors = series.length, - usedColors = [], - assignedColors = []; - for (i = 0; i < series.length; ++i) { - var sc = series[i].color; - if (sc != null) { - --neededColors; - if (typeof sc == "number") - assignedColors.push(sc); - else - usedColors.push(parseColor(series[i].color)); - } - } - - // we might need to generate more colors if higher indices - // are assigned - for (i = 0; i < assignedColors.length; ++i) { - neededColors = Math.max(neededColors, assignedColors[i] + 1); - } - - // produce colors as needed - var colors = [], variation = 0; - i = 0; - while (colors.length < neededColors) { - var c; - if (options.colors.length == i) // check degenerate case - c = new Color(100, 100, 100); - else - c = parseColor(options.colors[i]); - - // vary color if needed - var sign = variation % 2 == 1 ? -1 : 1; - var factor = 1 + sign * Math.ceil(variation / 2) * 0.2; - c.scale(factor, factor, factor); - - // FIXME: if we're getting to close to something else, - // we should probably skip this one - colors.push(c); - - ++i; - if (i >= options.colors.length) { - i = 0; - ++variation; - } - } - - // fill in the options - var colori = 0, s; - for (i = 0; i < series.length; ++i) { - s = series[i]; - - // assign colors - if (s.color == null) { - s.color = colors[colori].toString(); - ++colori; - } - else if (typeof s.color == "number") - s.color = colors[s.color].toString(); - - // copy the rest - s.lines = $.extend(true, {}, options.lines, s.lines); - s.points = $.extend(true, {}, options.points, s.points); - s.bars = $.extend(true, {}, options.bars, s.bars); - if (s.shadowSize == null) - s.shadowSize = options.shadowSize; - if (s.xaxis && s.xaxis == 2) - s.xaxis = axes.x2axis; - else - s.xaxis = axes.xaxis; - if (s.yaxis && s.yaxis == 2) - s.yaxis = axes.y2axis; - else - s.yaxis = axes.yaxis; - } - } - - function processData() { - var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY, - axis; - - for (axis in axes) { - axes[axis].datamin = topSentry; - axes[axis].datamax = bottomSentry; - axes[axis].used = false; - } - - for (var i = 0; i < series.length; ++i) { - var data = series[i].data, - axisx = series[i].xaxis, - axisy = series[i].yaxis, - mindelta = 0, maxdelta = 0; - - // make sure we got room for the bar - if (series[i].bars.show) { - mindelta = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2; - maxdelta = mindelta + series[i].bars.barWidth; - } - - axisx.used = axisy.used = true; - for (var j = 0; j < data.length; ++j) { - if (data[j] == null) - continue; - - var x = data[j][0], y = data[j][1]; - - // convert to number - if (x != null && !isNaN(x = +x)) { - if (x + mindelta < axisx.datamin) - axisx.datamin = x + mindelta; - if (x + maxdelta > axisx.datamax) - axisx.datamax = x + maxdelta; - } - - if (y != null && !isNaN(y = +y)) { - if (y < axisy.datamin) - axisy.datamin = y; - if (y > axisy.datamax) - axisy.datamax = y; - } - - if (x == null || y == null || isNaN(x) || isNaN(y)) - data[j] = null; // mark this point as invalid - } - } - - for (axis in axes) { - if (axes[axis].datamin == topSentry) - axes[axis].datamin = 0; - if (axes[axis].datamax == bottomSentry) - axes[axis].datamax = 1; - } - } - - function constructCanvas() { - canvasWidth = target.width(); - canvasHeight = target.height(); - target.html(""); // clear target - target.css("position", "relative"); // for positioning labels and overlay - - if (canvasWidth <= 0 || canvasHeight <= 0) - throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; - - // the canvas - canvas = $('<canvas width="' + canvasWidth + '" height="' + canvasHeight + '"></canvas>').appendTo(target).get(0); - if ($.browser.msie) // excanvas hack - canvas = window.G_vmlCanvasManager.initElement(canvas); - ctx = canvas.getContext("2d"); - - // overlay canvas for interactive features - overlay = $('<canvas style="position:absolute;left:0px;top:0px;" width="' + canvasWidth + '" height="' + canvasHeight + '"></canvas>').appendTo(target).get(0); - if ($.browser.msie) // excanvas hack - overlay = window.G_vmlCanvasManager.initElement(overlay); - octx = overlay.getContext("2d"); - - // we include the canvas in the event holder too, because IE 7 - // sometimes has trouble with the stacking order - eventHolder = $([overlay, canvas]); - - // bind events - if (options.selection.mode != null || options.grid.hoverable) { - // FIXME: temp. work-around until jQuery bug 1871 is fixed - eventHolder.each(function () { - this.onmousemove = onMouseMove; - }); - - if (options.selection.mode != null) - eventHolder.mousedown(onMouseDown); - } - - if (options.grid.clickable) - eventHolder.click(onClick); - } - - function setupGrid() { - function setupAxis(axis, options) { - setRange(axis, options); - prepareTickGeneration(axis, options); - setTicks(axis, options); - // add transformation helpers - if (axis == axes.xaxis || axis == axes.x2axis) { - // data point to canvas coordinate - axis.p2c = function (p) { return (p - axis.min) * axis.scale; }; - // canvas coordinate to data point - axis.c2p = function (c) { return axis.min + c / axis.scale; }; - } - else { - axis.p2c = function (p) { return (axis.max - p) * axis.scale; }; - axis.c2p = function (p) { return axis.max - p / axis.scale; }; - } - } - - for (var axis in axes) - setupAxis(axes[axis], options[axis]); - - setSpacing(); - insertLabels(); - insertLegend(); - } - - function setRange(axis, axisOptions) { - var min = axisOptions.min != null ? axisOptions.min : axis.datamin; - var max = axisOptions.max != null ? axisOptions.max : axis.datamax; - - if (max - min == 0.0) { - // degenerate case - var widen; - if (max == 0.0) - widen = 1.0; - else - widen = 0.01; - - min -= widen; - max += widen; - } - else { - // consider autoscaling - var margin = axisOptions.autoscaleMargin; - if (margin != null) { - if (axisOptions.min == null) { - min -= (max - min) * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin >= 0) - min = 0; - } - if (axisOptions.max == null) { - max += (max - min) * margin; - if (max > 0 && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - } - - function prepareTickGeneration(axis, axisOptions) { - // estimate number of ticks - var noTicks; - if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0) - noTicks = axisOptions.ticks; - else if (axis == axes.xaxis || axis == axes.x2axis) - noTicks = canvasWidth / 100; - else - noTicks = canvasHeight / 60; - - var delta = (axis.max - axis.min) / noTicks; - var size, generator, unit, formatter, i, magn, norm; - - if (axisOptions.mode == "time") { - // pretty handling of time - - function formatDate(d, fmt, monthNames) { - var leftPad = function(n) { - n = "" + n; - return n.length == 1 ? "0" + n : n; - }; - - var r = []; - var escape = false; - if (monthNames == null) - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - for (var i = 0; i < fmt.length; ++i) { - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'h': c = "" + d.getUTCHours(); break; - case 'H': c = leftPad(d.getUTCHours()); break; - case 'M': c = leftPad(d.getUTCMinutes()); break; - case 'S': c = leftPad(d.getUTCSeconds()); break; - case 'd': c = "" + d.getUTCDate(); break; - case 'm': c = "" + (d.getUTCMonth() + 1); break; - case 'y': c = "" + d.getUTCFullYear(); break; - case 'b': c = "" + monthNames[d.getUTCMonth()]; break; - } - r.push(c); - escape = false; - } - else { - if (c == "%") - escape = true; - else - r.push(c); - } - } - return r.join(""); - } - - - // map of app. size of time units in milliseconds - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - var spec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"], [3, "month"], [6, "month"], - [1, "year"] - ]; - - var minSize = 0; - if (axisOptions.minTickSize != null) { - if (typeof axisOptions.tickSize == "number") - minSize = axisOptions.tickSize; - else - minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]]; - } - - for (i = 0; i < spec.length - 1; ++i) - if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) - break; - size = spec[i][0]; - unit = spec[i][1]; - - // special-case the possibility of several years - if (unit == "year") { - magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); - norm = (delta / timeUnitSize.year) / magn; - if (norm < 1.5) - size = 1; - else if (norm < 3) - size = 2; - else if (norm < 7.5) - size = 5; - else - size = 10; - - size *= magn; - } - - if (axisOptions.tickSize) { - size = axisOptions.tickSize[0]; - unit = axisOptions.tickSize[1]; - } - - generator = function(axis) { - var ticks = [], - tickSize = axis.tickSize[0], unit = axis.tickSize[1], - d = new Date(axis.min); - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") - d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); - if (unit == "minute") - d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); - if (unit == "hour") - d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); - if (unit == "month") - d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); - if (unit == "year") - d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); - - // reset smaller components - d.setUTCMilliseconds(0); - if (step >= timeUnitSize.minute) - d.setUTCSeconds(0); - if (step >= timeUnitSize.hour) - d.setUTCMinutes(0); - if (step >= timeUnitSize.day) - d.setUTCHours(0); - if (step >= timeUnitSize.day * 4) - d.setUTCDate(1); - if (step >= timeUnitSize.year) - d.setUTCMonth(0); - - - var carry = 0, v = Number.NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); - if (unit == "month") { - if (tickSize < 1) { - // a bit complicated - we'll divide the month - // up but we need to take care of fractions - // so we don't end up in the middle of a day - d.setUTCDate(1); - var start = d.getTime(); - d.setUTCMonth(d.getUTCMonth() + 1); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getUTCHours(); - d.setUTCHours(0); - } - else - d.setUTCMonth(d.getUTCMonth() + tickSize); - } - else if (unit == "year") { - d.setUTCFullYear(d.getUTCFullYear() + tickSize); - } - else - d.setTime(v + step); - } while (v < axis.max && v != prev); - - return ticks; - }; - - formatter = function (v, axis) { - var d = new Date(v); - - // first check global format - if (axisOptions.timeformat != null) - return formatDate(d, axisOptions.timeformat, axisOptions.monthNames); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - - if (t < timeUnitSize.minute) - fmt = "%h:%M:%S"; - else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) - fmt = "%h:%M"; - else - fmt = "%b %d %h:%M"; - } - else if (t < timeUnitSize.month) - fmt = "%b %d"; - else if (t < timeUnitSize.year) { - if (span < timeUnitSize.year) - fmt = "%b"; - else - fmt = "%b %y"; - } - else - fmt = "%y"; - - return formatDate(d, fmt, axisOptions.monthNames); - }; - } - else { - // pretty rounding of base-10 numbers - var maxDec = axisOptions.tickDecimals; - var dec = -Math.floor(Math.log(delta) / Math.LN10); - if (maxDec != null && dec > maxDec) - dec = maxDec; - - magn = Math.pow(10, -dec); - norm = delta / magn; // norm is between 1.0 and 10.0 - - if (norm < 1.5) - size = 1; - else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } - else if (norm < 7.5) - size = 5; - else - size = 10; - - size *= magn; - - if (axisOptions.minTickSize != null && size < axisOptions.minTickSize) - size = axisOptions.minTickSize; - - if (axisOptions.tickSize != null) - size = axisOptions.tickSize; - - axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec); - - generator = function (axis) { - var ticks = []; - - // spew out all possible ticks - var start = floorInBase(axis.min, axis.tickSize), - i = 0, v = Number.NaN, prev; - do { - prev = v; - v = start + i * axis.tickSize; - ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); - ++i; - } while (v < axis.max && v != prev); - return ticks; - }; - - formatter = function (v, axis) { - return v.toFixed(axis.tickDecimals); - }; - } - - axis.tickSize = unit ? [size, unit] : size; - axis.tickGenerator = generator; - if ($.isFunction(axisOptions.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); }; - else - axis.tickFormatter = formatter; - if (axisOptions.labelWidth != null) - axis.labelWidth = axisOptions.labelWidth; - if (axisOptions.labelHeight != null) - axis.labelHeight = axisOptions.labelHeight; - } - - function setTicks(axis, axisOptions) { - axis.ticks = []; - - if (!axis.used) - return; - - if (axisOptions.ticks == null) - axis.ticks = axis.tickGenerator(axis); - else if (typeof axisOptions.ticks == "number") { - if (axisOptions.ticks > 0) - axis.ticks = axis.tickGenerator(axis); - } - else if (axisOptions.ticks) { - var ticks = axisOptions.ticks; - - if ($.isFunction(ticks)) - // generate the ticks - ticks = ticks({ min: axis.min, max: axis.max }); - - // clean up the user-supplied ticks, copy them over - var i, v; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = t; - if (label == null) - label = axis.tickFormatter(v, axis); - axis.ticks[i] = { v: v, label: label }; - } - } - - if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) { - // snap to ticks - if (axisOptions.min == null) - axis.min = Math.min(axis.min, axis.ticks[0].v); - if (axisOptions.max == null && axis.ticks.length > 1) - axis.max = Math.min(axis.max, axis.ticks[axis.ticks.length - 1].v); - } - } - - function setSpacing() { - function measureXLabels(axis) { - // to avoid measuring the widths of the labels, we - // construct fixed-size boxes and put the labels inside - // them, we don't need the exact figures and the - // fixed-size box content is easy to center - if (axis.labelWidth == null) - axis.labelWidth = canvasWidth / 6; - - // measure x label heights - if (axis.labelHeight == null) { - labels = []; - for (i = 0; i < axis.ticks.length; ++i) { - l = axis.ticks[i].label; - if (l) - labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>'); - } - - axis.labelHeight = 0; - if (labels.length > 0) { - var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">' - + labels.join("") + '<div style="clear:left"></div></div>').appendTo(target); - axis.labelHeight = dummyDiv.height(); - dummyDiv.remove(); - } - } - } - - function measureYLabels(axis) { - if (axis.labelWidth == null || axis.labelHeight == null) { - var i, labels = [], l; - // calculate y label dimensions - for (i = 0; i < axis.ticks.length; ++i) { - l = axis.ticks[i].label; - if (l) - labels.push('<div class="tickLabel">' + l + '</div>'); - } - - if (labels.length > 0) { - var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">' - + labels.join("") + '</div>').appendTo(target); - if (axis.labelWidth == null) - axis.labelWidth = dummyDiv.width(); - if (axis.labelHeight == null) - axis.labelHeight = dummyDiv.find("div").height(); - dummyDiv.remove(); - } - - if (axis.labelWidth == null) - axis.labelWidth = 0; - if (axis.labelHeight == null) - axis.labelHeight = 0; - } - } - - measureXLabels(axes.xaxis); - measureYLabels(axes.yaxis); - measureXLabels(axes.x2axis); - measureYLabels(axes.y2axis); - - // get the most space needed around the grid for things - // that may stick out - var maxOutset = options.grid.borderWidth / 2; - for (i = 0; i < series.length; ++i) - maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); - - plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset; - - if (axes.xaxis.labelHeight > 0) - plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + options.grid.labelMargin); - if (axes.yaxis.labelWidth > 0) - plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + options.grid.labelMargin); - - if (axes.x2axis.labelHeight > 0) - plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + options.grid.labelMargin); - - if (axes.y2axis.labelWidth > 0) - plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + options.grid.labelMargin); - - plotWidth = canvasWidth - plotOffset.left - plotOffset.right; - plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; - - // precompute how much the axis is scaling a point in canvas space - axes.xaxis.scale = plotWidth / (axes.xaxis.max - axes.xaxis.min); - axes.yaxis.scale = plotHeight / (axes.yaxis.max - axes.yaxis.min); - axes.x2axis.scale = plotWidth / (axes.x2axis.max - axes.x2axis.min); - axes.y2axis.scale = plotHeight / (axes.y2axis.max - axes.y2axis.min); - } - - function draw() { - drawGrid(); - for (var i = 0; i < series.length; i++) { - drawSeries(series[i]); - } - } - - function extractRange(ranges, coord) { - var firstAxis = coord + "axis", - secondaryAxis = coord + "2axis", - axis, from, to, reverse; - - if (ranges[firstAxis]) { - axis = axes[firstAxis]; - from = ranges[firstAxis].from; - to = ranges[firstAxis].to; - } - else if (ranges[secondaryAxis]) { - axis = axes[secondaryAxis]; - from = ranges[secondaryAxis].from; - to = ranges[secondaryAxis].to; - } - else { - // backwards-compat stuff - to be removed in future - axis = axes[firstAxis]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) - return { from: to, to: from, axis: axis }; - - return { from: from, to: to, axis: axis }; - } - - function drawGrid() { - var i; - - ctx.save(); - ctx.clearRect(0, 0, canvasWidth, canvasHeight); - ctx.translate(plotOffset.left, plotOffset.top); - - // draw background, if any - if (options.grid.backgroundColor) { - ctx.fillStyle = options.grid.backgroundColor; - ctx.fillRect(0, 0, plotWidth, plotHeight); - } - - // draw markings - if (options.grid.markings) { - var markings = options.grid.markings; - if ($.isFunction(markings)) - // xmin etc. are backwards-compatible, to be removed in future - markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis }); - - for (i = 0; i < markings.length; ++i) { - var m = markings[i], - xrange = extractRange(m, "x"), - yrange = extractRange(m, "y"); - - // fill in missing - if (xrange.from == null) - xrange.from = xrange.axis.min; - if (xrange.to == null) - xrange.to = xrange.axis.max; - if (yrange.from == null) - yrange.from = yrange.axis.min; - if (yrange.to == null) - yrange.to = yrange.axis.max; - - // clip - if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || - yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) - continue; - - xrange.from = Math.max(xrange.from, xrange.axis.min); - xrange.to = Math.min(xrange.to, xrange.axis.max); - yrange.from = Math.max(yrange.from, yrange.axis.min); - yrange.to = Math.min(yrange.to, yrange.axis.max); - - if (xrange.from == xrange.to && yrange.from == yrange.to) - continue; - - // then draw - xrange.from = xrange.axis.p2c(xrange.from); - xrange.to = xrange.axis.p2c(xrange.to); - yrange.from = yrange.axis.p2c(yrange.from); - yrange.to = yrange.axis.p2c(yrange.to); - - if (xrange.from == xrange.to || yrange.from == yrange.to) { - // draw line - ctx.strokeStyle = m.color || options.grid.markingsColor; - ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; - ctx.moveTo(Math.floor(xrange.from), Math.floor(yrange.from)); - ctx.lineTo(Math.floor(xrange.to), Math.floor(yrange.to)); - ctx.stroke(); - } - else { - // fill area - ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(Math.floor(xrange.from), - Math.floor(yrange.to), - Math.floor(xrange.to - xrange.from), - Math.floor(yrange.from - yrange.to)); - } - } - } - - // draw the inner grid - ctx.lineWidth = 1; - ctx.strokeStyle = options.grid.tickColor; - ctx.beginPath(); - var v, axis = axes.xaxis; - for (i = 0; i < axis.ticks.length; ++i) { - v = axis.ticks[i].v; - if (v <= axis.min || v >= axes.xaxis.max) - continue; // skip those lying on the axes - - ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0); - ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight); - } - - axis = axes.yaxis; - for (i = 0; i < axis.ticks.length; ++i) { - v = axis.ticks[i].v; - if (v <= axis.min || v >= axis.max) - continue; - - ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); - ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); - } - - axis = axes.x2axis; - for (i = 0; i < axis.ticks.length; ++i) { - v = axis.ticks[i].v; - if (v <= axis.min || v >= axis.max) - continue; - - ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5); - ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5); - } - - axis = axes.y2axis; - for (i = 0; i < axis.ticks.length; ++i) { - v = axis.ticks[i].v; - if (v <= axis.min || v >= axis.max) - continue; - - ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); - ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); - } - - ctx.stroke(); - - if (options.grid.borderWidth) { - // draw border - ctx.lineWidth = options.grid.borderWidth; - ctx.strokeStyle = options.grid.color; - ctx.lineJoin = "round"; - ctx.strokeRect(0, 0, plotWidth, plotHeight); - } - - ctx.restore(); - } - - function insertLabels() { - target.find(".tickLabels").remove(); - - var html = '<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'; - - function addLabels(axis, labelGenerator) { - for (var i = 0; i < axis.ticks.length; ++i) { - var tick = axis.ticks[i]; - if (!tick.label || tick.v < axis.min || tick.v > axis.max) - continue; - html += labelGenerator(tick, axis); - } - } - - addLabels(axes.xaxis, function (tick, axis) { - return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + options.grid.labelMargin) + 'px;left:' + (plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; - }); - - - addLabels(axes.yaxis, function (tick, axis) { - return '<div style="position:absolute;top:' + (plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + options.grid.labelMargin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>"; - }); - - addLabels(axes.x2axis, function (tick, axis) { - return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + options.grid.labelMargin) + 'px;left:' + (plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; - }); - - addLabels(axes.y2axis, function (tick, axis) { - return '<div style="position:absolute;top:' + (plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + options.grid.labelMargin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>"; - }); - - html += '</div>'; - - target.append(html); - } - - function drawSeries(series) { - if (series.lines.show || (!series.bars.show && !series.points.show)) - drawSeriesLines(series); - if (series.bars.show) - drawSeriesBars(series); - if (series.points.show) - drawSeriesPoints(series); - } - - function drawSeriesLines(series) { - function plotLine(data, offset, axisx, axisy) { - var prev, cur = null, drawx = null, drawy = null; - - ctx.beginPath(); - for (var i = 0; i < data.length; ++i) { - prev = cur; - cur = data[i]; - - if (prev == null || cur == null) - continue; - - var x1 = prev[0], y1 = prev[1], - x2 = cur[0], y2 = cur[1]; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (drawx != axisx.p2c(x1) || drawy != axisy.p2c(y1) + offset) - ctx.moveTo(axisx.p2c(x1), axisy.p2c(y1) + offset); - - drawx = axisx.p2c(x2); - drawy = axisy.p2c(y2) + offset; - ctx.lineTo(drawx, drawy); - } - ctx.stroke(); - } - - function plotLineArea(data, axisx, axisy) { - var prev, cur = null; - - var bottom = Math.min(Math.max(0, axisy.min), axisy.max); - var top, lastX = 0; - - var areaOpen = false; - - for (var i = 0; i < data.length; ++i) { - prev = cur; - cur = data[i]; - - if (areaOpen && prev != null && cur == null) { - // close area - ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); - ctx.fill(); - areaOpen = false; - continue; - } - - if (prev == null || cur == null) - continue; - - var x1 = prev[0], y1 = prev[1], - x2 = cur[0], y2 = cur[1]; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be two rectangles and two triangles we need to fill - // in; to find these keep track of the current x values - var x1old = x1, x2old = x2; - - // and clip the y values, without shortcutting - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - if (y1 <= axisy.min) - top = axisy.min; - else - top = axisy.max; - - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top)); - ctx.lineTo(axisx.p2c(x1), axisy.p2c(top)); - } - - // fill the triangles - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - if (y2 <= axisy.min) - top = axisy.min; - else - top = axisy.max; - - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(top)); - } - - lastX = Math.max(x2, x2old); - } - - if (areaOpen) { - ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); - ctx.fill(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - var lw = series.lines.lineWidth; - var sw = series.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (sw > 0) { - // draw shadow in two steps - ctx.lineWidth = sw / 2; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotLine(series.data, lw/2 + sw/2 + ctx.lineWidth/2, series.xaxis, series.yaxis); - - ctx.lineWidth = sw / 2; - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotLine(series.data, lw/2 + ctx.lineWidth/2, series.xaxis, series.yaxis); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - setFillStyle(series.lines, series.color); - if (series.lines.fill) - plotLineArea(series.data, series.xaxis, series.yaxis); - plotLine(series.data, 0, series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawSeriesPoints(series) { - function plotPoints(data, radius, fill, axisx, axisy) { - for (var i = 0; i < data.length; ++i) { - if (data[i] == null) - continue; - - var x = data[i][0], y = data[i][1]; - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - - ctx.beginPath(); - ctx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true); - if (fill) - ctx.fill(); - ctx.stroke(); - } - } - - function plotPointShadows(data, offset, radius, axisx, axisy) { - for (var i = 0; i < data.length; ++i) { - if (data[i] == null) - continue; - - var x = data[i][0], y = data[i][1]; - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - ctx.beginPath(); - ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, Math.PI, false); - ctx.stroke(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var lw = series.lines.lineWidth; - var sw = series.shadowSize; - if (sw > 0) { - // draw shadow in two steps - ctx.lineWidth = sw / 2; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPointShadows(series.data, sw/2 + ctx.lineWidth/2, - series.points.radius, series.xaxis, series.yaxis); - - ctx.lineWidth = sw / 2; - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPointShadows(series.data, ctx.lineWidth/2, - series.points.radius, series.xaxis, series.yaxis); - } - - ctx.lineWidth = series.points.lineWidth; - ctx.strokeStyle = series.color; - setFillStyle(series.points, series.color); - plotPoints(series.data, series.points.radius, series.points.fill, - series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawBar(x, y, barLeft, barRight, offset, fill, axisx, axisy, c) { - var drawLeft = true, drawRight = true, - drawTop = true, drawBottom = false, - left = x + barLeft, right = x + barRight, - bottom = 0, top = y; - - // account for negative bars - if (top < bottom) { - top = 0; - bottom = y; - drawBottom = true; - drawTop = false; - } - - // clip - if (right < axisx.min || left > axisx.max || - top < axisy.min || bottom > axisy.max) - return; - - if (left < axisx.min) { - left = axisx.min; - drawLeft = false; - } - - if (right > axisx.max) { - right = axisx.max; - drawRight = false; - } - - if (bottom < axisy.min) { - bottom = axisy.min; - drawBottom = false; - } - - if (top > axisy.max) { - top = axisy.max; - drawTop = false; - } - - // fill the bar - if (fill) { - c.beginPath(); - c.moveTo(axisx.p2c(left), axisy.p2c(bottom) + offset); - c.lineTo(axisx.p2c(left), axisy.p2c(top) + offset); - c.lineTo(axisx.p2c(right), axisy.p2c(top) + offset); - c.lineTo(axisx.p2c(right), axisy.p2c(bottom) + offset); - c.fill(); - } - - // draw outline - if (drawLeft || drawRight || drawTop || drawBottom) { - c.beginPath(); - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - - c.moveTo(left, bottom + offset); - if (drawLeft) - c.lineTo(left, top + offset); - else - c.moveTo(left, top + offset); - if (drawTop) - c.lineTo(right, top + offset); - else - c.moveTo(right, top + offset); - if (drawRight) - c.lineTo(right, bottom + offset); - else - c.moveTo(right, bottom + offset); - if (drawBottom) - c.lineTo(left, bottom + offset); - else - c.moveTo(left, bottom + offset); - c.stroke(); - } - } - - function drawSeriesBars(series) { - function plotBars(data, barLeft, barRight, offset, fill, axisx, axisy) { - for (var i = 0; i < data.length; i++) { - if (data[i] == null) - continue; - drawBar(data[i][0], data[i][1], barLeft, barRight, offset, fill, axisx, axisy, ctx); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - // FIXME: figure out a way to add shadows - /* - var bw = series.bars.barWidth; - var lw = series.bars.lineWidth; - var sw = series.shadowSize; - if (sw > 0) { - // draw shadow in two steps - ctx.lineWidth = sw / 2; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotBars(series.data, bw, lw/2 + sw/2 + ctx.lineWidth/2, false); - - ctx.lineWidth = sw / 2; - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotBars(series.data, bw, lw/2 + ctx.lineWidth/2, false); - }*/ - - ctx.lineWidth = series.bars.lineWidth; - ctx.strokeStyle = series.color; - setFillStyle(series.bars, series.color); - var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; - plotBars(series.data, barLeft, barLeft + series.bars.barWidth, 0, series.bars.fill, series.xaxis, series.yaxis); - ctx.restore(); - } - - function setFillStyle(obj, seriesColor) { - var fill = obj.fill; - if (!fill) - return; - - if (obj.fillColor) - ctx.fillStyle = obj.fillColor; - else { - var c = parseColor(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - ctx.fillStyle = c.toString(); - } - } - - function insertLegend() { - target.find(".legend").remove(); - - if (!options.legend.show) - return; - - var fragments = []; - var rowStarted = false; - for (i = 0; i < series.length; ++i) { - if (!series[i].label) - continue; - - if (i % options.legend.noColumns == 0) { - if (rowStarted) - fragments.push('</tr>'); - fragments.push('<tr>'); - rowStarted = true; - } - - var label = series[i].label; - if (options.legend.labelFormatter != null) - label = options.legend.labelFormatter(label); - - fragments.push( - '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:14px;height:10px;background-color:' + series[i].color + ';overflow:hidden"></div></div></td>' + - '<td class="legendLabel">' + label + '</td>'); - } - if (rowStarted) - fragments.push('</tr>'); - - if (fragments.length == 0) - return; - - var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>'; - if (options.legend.container != null) - options.legend.container.html(table); - else { - var pos = ""; - var p = options.legend.position, m = options.legend.margin; - if (p.charAt(0) == "n") - pos += 'top:' + (m + plotOffset.top) + 'px;'; - else if (p.charAt(0) == "s") - pos += 'bottom:' + (m + plotOffset.bottom) + 'px;'; - if (p.charAt(1) == "e") - pos += 'right:' + (m + plotOffset.right) + 'px;'; - else if (p.charAt(1) == "w") - pos += 'left:' + (m + plotOffset.left) + 'px;'; - var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(target); - if (options.legend.backgroundOpacity != 0.0) { - // put in the transparent background - // separately to avoid blended labels and - // label boxes - var c = options.legend.backgroundColor; - if (c == null) { - var tmp; - if (options.grid.backgroundColor) - tmp = options.grid.backgroundColor; - else - tmp = extractColor(legend); - c = parseColor(tmp).adjust(null, null, null, 1).toString(); - } - var div = legend.children(); - $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - - } - } - } - - - // interactive features - - var lastMousePos = { pageX: null, pageY: null }, - selection = { - first: { x: -1, y: -1}, second: { x: -1, y: -1}, - show: false, active: false }, - highlights = [], - clickIsMouseUp = false, - redrawTimeout = null, - hoverTimeout = null; - - // Returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY) { - var maxDistance = options.grid.mouseActiveRadius, - lowestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false; - - function result(i, j) { - return { datapoint: series[i].data[j], - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - - for (var i = 0; i < series.length; ++i) { - var data = series[i].data, - axisx = series[i].xaxis, - axisy = series[i].yaxis, - - // precompute some stuff to make the loop faster - mx = axisx.c2p(mouseX), - my = axisy.c2p(mouseY), - maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale, - checkbar = series[i].bars.show, - checkpoint = !(series[i].bars.show && !(series[i].lines.show || series[i].points.show)), - barLeft = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2, - barRight = barLeft + series[i].bars.barWidth; - for (var j = 0; j < data.length; ++j) { - if (data[j] == null) - continue; - - var x = data[j][0], y = data[j][1]; - - if (checkbar) { - // For a bar graph, the cursor must be inside the bar - // and no other point can be nearby - if (!foundPoint && mx >= x + barLeft && - mx <= x + barRight && - my >= Math.min(0, y) && my <= Math.max(0, y)) - item = result(i, j); - } - - if (checkpoint) { - // For points and lines, the cursor must be within a - // certain distance to the data point - - // check bounding box first - if ((x - mx > maxx || x - mx < -maxx) || - (y - my > maxy || y - my < -maxy)) - continue; - - // We have to calculate distances in pixels, not in - // data units, because the scale of the axes may be different - var dx = Math.abs(axisx.p2c(x) - mouseX), - dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; - if (dist < lowestDistance) { - lowestDistance = dist; - foundPoint = true; - item = result(i, j); - } - } - } - } - - return item; - } - - function onMouseMove(ev) { - // FIXME: temp. work-around until jQuery bug 1871 is fixed - var e = ev || window.event; - if (e.pageX == null && e.clientX != null) { - var de = document.documentElement, b = document.body; - lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0); - lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0); - } - else { - lastMousePos.pageX = e.pageX; - lastMousePos.pageY = e.pageY; - } - - if (options.grid.hoverable && !hoverTimeout) - hoverTimeout = setTimeout(onHover, 100); - - if (selection.active) - updateSelection(lastMousePos); - } - - function onMouseDown(e) { - if (e.which != 1) // only accept left-click - return; - - // cancel out any text selections - document.body.focus(); - - // prevent text selection and drag in old-school browsers - if (document.onselectstart !== undefined && workarounds.onselectstart == null) { - workarounds.onselectstart = document.onselectstart; - document.onselectstart = function () { return false; }; - } - if (document.ondrag !== undefined && workarounds.ondrag == null) { - workarounds.ondrag = document.ondrag; - document.ondrag = function () { return false; }; - } - - setSelectionPos(selection.first, e); - - lastMousePos.pageX = null; - selection.active = true; - $(document).one("mouseup", onSelectionMouseUp); - } - - function onClick(e) { - if (clickIsMouseUp) { - clickIsMouseUp = false; - return; - } - - triggerClickHoverEvent("plotclick", e); - } - - function onHover() { - triggerClickHoverEvent("plothover", lastMousePos); - hoverTimeout = null; - } - - // trigger click or hover event (they send the same parameters - // so we share their code) - function triggerClickHoverEvent(eventname, event) { - var offset = eventHolder.offset(), - pos = { pageX: event.pageX, pageY: event.pageY }, - canvasX = event.pageX - offset.left - plotOffset.left, - canvasY = event.pageY - offset.top - plotOffset.top; - - if (axes.xaxis.used) - pos.x = axes.xaxis.c2p(canvasX); - if (axes.yaxis.used) - pos.y = axes.yaxis.c2p(canvasY); - if (axes.x2axis.used) - pos.x2 = axes.x2axis.c2p(canvasX); - if (axes.y2axis.used) - pos.y2 = axes.y2axis.c2p(canvasY); - - var item = findNearbyItem(canvasX, canvasY); - - if (item) { - // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); - - - } - - if (options.grid.autoHighlight) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto && - !(item && h.series == item.series && h.point == item.datapoint)) - unhighlight(h.series, h.point); - } - - if (item) - highlight(item.series, item.datapoint, true); - } - - target.trigger(eventname, [ pos, item ]); - } - - function triggerRedrawOverlay() { - if (!redrawTimeout) - redrawTimeout = setTimeout(redrawOverlay, 50); - } - - function redrawOverlay() { - redrawTimeout = null; - - // redraw highlights - octx.save(); - octx.clearRect(0, 0, canvasWidth, canvasHeight); - octx.translate(plotOffset.left, plotOffset.top); - - var i, hi; - for (i = 0; i < highlights.length; ++i) { - hi = highlights[i]; - - if (hi.series.bars.show) - drawBarHighlight(hi.series, hi.point); - else - drawPointHighlight(hi.series, hi.point); - } - octx.restore(); - - // redraw selection - if (selection.show && selectionIsSane()) { - octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString(); - octx.lineWidth = 1; - ctx.lineJoin = "round"; - octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString(); - - var x = Math.min(selection.first.x, selection.second.x), - y = Math.min(selection.first.y, selection.second.y), - w = Math.abs(selection.second.x - selection.first.x), - h = Math.abs(selection.second.y - selection.first.y); - - octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h); - octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h); - } - } - - function highlight(s, point, auto) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") - point = s.data[point]; - - var i = indexOfHighlight(s, point); - if (i == -1) { - highlights.push({ series: s, point: point, auto: auto }); - - triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s, point) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") - point = s.data[point]; - - var i = indexOfHighlight(s, point); - if (i != -1) { - highlights.splice(i, 1); - - triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s, p) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s && h.point[0] == p[0] - && h.point[1] == p[1]) - return i; - } - return -1; - } - - function drawPointHighlight(series, point) { - var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis; - - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - return; - - var pointRadius = series.points.radius + series.points.lineWidth / 2; - octx.lineWidth = pointRadius; - octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); - var radius = 1.5 * pointRadius; - octx.beginPath(); - octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true); - octx.stroke(); - } - - function drawBarHighlight(series, point) { - octx.lineJoin = "round"; - octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); - octx.fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); - var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; - drawBar(point[0], point[1], barLeft, barLeft + series.bars.barWidth, - 0, true, series.xaxis, series.yaxis, octx); - } - - function triggerSelectedEvent() { - var x1 = Math.min(selection.first.x, selection.second.x), - x2 = Math.max(selection.first.x, selection.second.x), - y1 = Math.max(selection.first.y, selection.second.y), - y2 = Math.min(selection.first.y, selection.second.y); - - var r = {}; - if (axes.xaxis.used) - r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) }; - if (axes.x2axis.used) - r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) }; - if (axes.yaxis.used) - r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) }; - if (axes.y2axis.used) - r.yaxis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) }; - - target.trigger("plotselected", [ r ]); - - // backwards-compat stuff, to be removed in future - if (axes.xaxis.used && axes.yaxis.used) - target.trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); - } - - function onSelectionMouseUp(e) { - // revert drag stuff for old-school browsers - if (document.onselectstart !== undefined) - document.onselectstart = workarounds.onselectstart; - if (document.ondrag !== undefined) - document.ondrag = workarounds.ondrag; - - // no more draggy-dee-drag - selection.active = false; - updateSelection(e); - - if (selectionIsSane()) { - triggerSelectedEvent(); - clickIsMouseUp = true; - } - - return false; - } - - function setSelectionPos(pos, e) { - var offset = eventHolder.offset(); - if (options.selection.mode == "y") { - if (pos == selection.first) - pos.x = 0; - else - pos.x = plotWidth; - } - else { - pos.x = e.pageX - offset.left - plotOffset.left; - pos.x = Math.min(Math.max(0, pos.x), plotWidth); - } - - if (options.selection.mode == "x") { - if (pos == selection.first) - pos.y = 0; - else - pos.y = plotHeight; - } - else { - pos.y = e.pageY - offset.top - plotOffset.top; - pos.y = Math.min(Math.max(0, pos.y), plotHeight); - } - } - - function updateSelection(pos) { - if (pos.pageX == null) - return; - - setSelectionPos(selection.second, pos); - if (selectionIsSane()) { - selection.show = true; - triggerRedrawOverlay(); - } - else - clearSelection(); - } - - function clearSelection() { - if (selection.show) { - selection.show = false; - triggerRedrawOverlay(); - } - } - - function setSelection(ranges, preventEvent) { - var range; - - if (options.selection.mode == "y") { - selection.first.x = 0; - selection.second.x = plotWidth; - } - else { - range = extractRange(ranges, "x"); - - selection.first.x = range.axis.p2c(range.from); - selection.second.x = range.axis.p2c(range.to); - } - - if (options.selection.mode == "x") { - selection.first.y = 0; - selection.second.y = plotHeight; - } - else { - range = extractRange(ranges, "y"); - - selection.first.y = range.axis.p2c(range.from); - selection.second.y = range.axis.p2c(range.to); - } - - selection.show = true; - triggerRedrawOverlay(); - if (!preventEvent) - triggerSelectedEvent(); - } - - function selectionIsSane() { - var minSize = 5; - return Math.abs(selection.second.x - selection.first.x) >= minSize && - Math.abs(selection.second.y - selection.first.y) >= minSize; - } - } - - $.plot = function(target, data, options) { - var plot = new Plot(target, data, options); - /*var t0 = new Date(); - var t1 = new Date(); - var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime()) - if (window.console) - console.log(tstr); - else - alert(tstr);*/ - return plot; - }; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - - function clamp(min, value, max) { - if (value < min) - return value; - else if (value > max) - return max; - else - return value; - } - - // color helpers, inspiration from the jquery color animation - // plugin by John Resig - function Color (r, g, b, a) { - - var rgba = ['r','g','b','a']; - var x = 4; //rgba.length - - while (-1<--x) { - this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); - } - - this.toString = function() { - if (this.a >= 1.0) { - return "rgb("+[this.r,this.g,this.b].join(",")+")"; - } else { - return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")"; - } - }; - - this.scale = function(rf, gf, bf, af) { - x = 4; //rgba.length - while (-1<--x) { - if (arguments[x] != null) - this[rgba[x]] *= arguments[x]; - } - return this.normalize(); - }; - - this.adjust = function(rd, gd, bd, ad) { - x = 4; //rgba.length - while (-1<--x) { - if (arguments[x] != null) - this[rgba[x]] += arguments[x]; - } - return this.normalize(); - }; - - this.clone = function() { - return new Color(this.r, this.b, this.g, this.a); - }; - - var limit = function(val,minVal,maxVal) { - return Math.max(Math.min(val, maxVal), minVal); - }; - - this.normalize = function() { - this.r = limit(parseInt(this.r), 0, 255); - this.g = limit(parseInt(this.g), 0, 255); - this.b = limit(parseInt(this.b), 0, 255); - this.a = limit(this.a, 0, 1); - return this; - }; - - this.normalize(); - } - - var lookupColors = { - aqua:[0,255,255], - azure:[240,255,255], - beige:[245,245,220], - black:[0,0,0], - blue:[0,0,255], - brown:[165,42,42], - cyan:[0,255,255], - darkblue:[0,0,139], - darkcyan:[0,139,139], - darkgrey:[169,169,169], - darkgreen:[0,100,0], - darkkhaki:[189,183,107], - darkmagenta:[139,0,139], - darkolivegreen:[85,107,47], - darkorange:[255,140,0], - darkorchid:[153,50,204], - darkred:[139,0,0], - darksalmon:[233,150,122], - darkviolet:[148,0,211], - fuchsia:[255,0,255], - gold:[255,215,0], - green:[0,128,0], - indigo:[75,0,130], - khaki:[240,230,140], - lightblue:[173,216,230], - lightcyan:[224,255,255], - lightgreen:[144,238,144], - lightgrey:[211,211,211], - lightpink:[255,182,193], - lightyellow:[255,255,224], - lime:[0,255,0], - magenta:[255,0,255], - maroon:[128,0,0], - navy:[0,0,128], - olive:[128,128,0], - orange:[255,165,0], - pink:[255,192,203], - purple:[128,0,128], - violet:[128,0,128], - red:[255,0,0], - silver:[192,192,192], - white:[255,255,255], - yellow:[255,255,0] - }; - - function extractColor(element) { - var color, elem = element; - do { - color = elem.css("background-color").toLowerCase(); - // keep going until we find an element that has color, or - // we hit the body - if (color != '' && color != 'transparent') - break; - elem = elem.parent(); - } while (!$.nodeName(elem.get(0), "body")); - - // catch Safari's way of signalling transparent - if (color == "rgba(0, 0, 0, 0)") - return "transparent"; - - return color; - } - - // parse string, returns Color - function parseColor(str) { - var result; - - // Look for rgb(num,num,num) - if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)); - - // Look for rgba(num,num,num,num) - if (result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4])); - - // Look for rgb(num%,num%,num%) - if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); - - // Look for rgba(num%,num%,num%,num) - if (result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); - - // Look for #a0b1c2 - if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) - return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); - - // Look for #fff - if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) - return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16)); - - // Otherwise, we're most likely dealing with a named color - var name = $.trim(str).toLowerCase(); - if (name == "transparent") - return new Color(255, 255, 255, 0); - else { - result = lookupColors[name]; - return new Color(result[0], result[1], result[2]); - } - } - -})(jQuery);
--- a/bitten/report/coverage.py +++ b/bitten/report/coverage.py @@ -61,14 +61,12 @@ data = {'title': 'Test Coverage', 'data': [ - {'label': 'Lines of code', 'data': [[item[0], item[1]] for item in coverage]}, - {'label': 'Coverage', 'data': [[item[0], item[2]] for item in coverage]}, - ], - 'options': [ - {'xaxis': {'ticks': [[item[0], '[%s]' % item[0]] for item in coverage]}}, - ], - } - return 'json.txt', {"json": data} + [''] + ['[%s]' % item[0] for item in coverage], + ['Lines of code'] + [item[1] for item in coverage], + ['Coverage'] + [int(item[2]) for item in coverage] + ]} + + return 'bitten_chart_coverage.html', data class TestCoverageSummarizer(Component):
--- a/bitten/report/lint.py +++ b/bitten/report/lint.py @@ -69,18 +69,15 @@ data = {'title': 'Lint Problems by Type', 'data': [ - {'label': 'Total Problems', 'data': [[item[0], item[1]] for item in lint]}, - {'label': 'Convention', 'data': [[item[0], item[2]] for item in lint]}, - {'label': 'Error', 'data': [[item[0], item[3]] for item in lint]}, - {'label': 'Refactor', 'data': [[item[0], item[4]] for item in lint]}, - {'label': 'Warning', 'data': [[item[0], item[5]] for item in lint]}, - ], - 'options': [ - {'xaxis': {'ticks': [[item[0], '[%s]' % item[0]] for item in lint]}}, - ], - } + ['Revision'] + ['[%s]' % item[0] for item in lint], + ['Total Problems'] + [item[1] for item in lint], + ['Convention'] + [item[2] for item in lint], + ['Error'] + [item[3] for item in lint], + ['Refactor'] + [item[4] for item in lint], + ['Warning'] + [item[5] for item in lint], + ]} - return 'json.txt', {"json": data} + return 'bitten_chart_lint.html', data class PyLintSummarizer(Component):
--- a/bitten/report/testing.py +++ b/bitten/report/testing.py @@ -61,16 +61,13 @@ data = {'title': 'Unit Tests', 'data': [ - {'label': 'Total', 'data': [[item[0], item[1]] for item in tests]}, - {'label': 'Failures', 'data': [[item[0], item[2]] for item in tests]}, - {'label': 'Ignored', 'data': [[item[0], item[3]] for item in tests]}, - ], - 'options': [ - {'xaxis': {'ticks': [[item[0], '[%s]' % item[0]] for item in tests]}}, - ], - } + [''] + ['[%s]' % item[0] for item in tests], + ['Total'] + [item[1] for item in tests], + ['Failures'] + [item[2] for item in tests], + ['Ignored'] + [item[3] for item in tests], + ]} - return 'json.txt', {"json": data} + return 'bitten_chart_tests.html', data class TestResultsSummarizer(Component):
new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_chart_coverage.html @@ -0,0 +1,38 @@ +<chart xmlns:py="http://genshi.edgewall.org/"> + <chart_type> + <value>area</value> + <value>area</value> + </chart_type> + + <axis_category size="10" orientation="diagonal_up" + skip="${len(data[0]) / 6}"/> + <axis_ticks value_ticks="false" category_ticks="true" major_thickness="1" + minor_thickness="0" major_color="000000" position="outside"/> + + <chart_data> + <row py:for="idx, row in enumerate(data)"> + <py:choose py:for="jdx, value in enumerate(row)"> + <string py:when="not idx or not jdx">$value</string> + <number py:otherwise="">$value</number> + </py:choose> + </row> + </chart_data> + + <chart_border color="999999" left_thickness="1" bottom_thickness="1"/> + <chart_grid_h alpha="5" color="666666" thickness="3"/> + <chart_pref line_thickness="2" point_shape="none"/> + <chart_value position="cursor"/> + <series_color> + <color>bbbbbb</color> + <color>9999ff</color> + </series_color> + + <legend_label layout="vertical" alpha="60"/> + <legend_rect x="60" y="50" width="10"/> + + <draw> + <text width="320" height="40" h_align="center" v_align="bottom" + size="12">$title</text> + </draw> + +</chart>
new file mode 100644 --- /dev/null +++ b/bitten/templates/bitten_chart_lint.html @@ -0,0 +1,39 @@ +<chart xmlns:py="http://genshi.edgewall.org/"> + + <chart_type> + <value>area</value> + <value>line</value> + <value>line</value> + <value>line</value> + <value>line</value> + </chart_type> + + <axis_category size="10" orientation="diagonal_up" + skip="${len(data[0]) / 6}"/> + <axis_ticks value_ticks="false" category_ticks="true" major_thickness="1" + minor_thickness="0" major_color="000000" position="outside"/> + + <chart_data> + <row py:for="idx, row in enumerate(data)"> + <py:choose py:for="jdx, value in enumerate(row)"> + <string py:when="not idx or not jdx">$value</string> + <number py:otherwise="">$value</number> + </py:choose> + </row> + </chart_data> + + <chart_border color="999999" left_thickness="1" bottom_thickness="1"/> + <chart_grid_h alpha="5" color="666666" thickness="3"/> + <chart_pref line_thickness="2" point_shape="none"/> + <chart_value position="cursor"/> + <series_color> + </series_color> + + <legend_label layout="vertical" alpha="60"/> + <legend_rect x="60" y="50" width="10"/> + + <draw> + <text width="320" height="40" h_align="center" v_align="bottom" size="12" >$title</text> + </draw> + +</chart>
new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_chart_tests.html @@ -0,0 +1,40 @@ +<chart xmlns:py="http://genshi.edgewall.org/"> + <chart_type> + <value>area</value> + <value>column</value> + <value>line</value> + </chart_type> + + <axis_category size="10" orientation="diagonal_up" + skip="${len(data[0]) / 6}"/> + <axis_ticks value_ticks="false" category_ticks="true" major_thickness="2" + minor_thickness="0" major_color="000000" position="outside"/> + + <chart_data> + <row py:for="idx, row in enumerate(data)"> + <py:choose py:for="jdx, value in enumerate(row)"> + <string py:when="not idx or not jdx">$value</string> + <number py:otherwise="">$value</number> + </py:choose> + </row> + </chart_data> + + <chart_border color="999999" left_thickness="1" bottom_thickness="1"/> + <chart_grid_h alpha="5" color="666666" thickness="3"/> + <chart_pref line_thickness="2" point_shape="none"/> + <chart_value position="cursor"/> + <series_color> + <color>99dd99</color> + <color>ff0000</color> + <color>ffff00</color> + </series_color> + + <legend_label layout="vertical" alpha="60"/> + <legend_rect x="60" y="50" width="10"/> + + <draw> + <text width="320" height="40" h_align="center" v_align="bottom" + size="12">$title</text> + </draw> + +</chart>
--- a/bitten/templates/bitten_config.html +++ b/bitten/templates/bitten_config.html @@ -8,7 +8,6 @@ <xi:include href="macros.html" /> <head> <title>$title</title> - <script type="text/javascript" src="${chrome.htdocs_location}../bitten/jquery.flot.js"></script> </head> <body> <strong py:def="build_status(status)" class="status"> @@ -144,20 +143,14 @@ </py:if></py:for>)</i> </div></py:if> <div id="charts"><py:for each="chart in config.charts"> - <div id="chart_${chart.category}" style="width:600px;height:300px;"></div> - <script type="text/javascript"> - $(document).ready(function() { - - $.getJSON('${chart.href}', function(json) { - - var plot_data = new Array(); - var plot_ticks = new Array(); - - $.plot($("#chart_${chart.category}"), json.data, json.options); - }); - }); - </script> - <br /></py:for> + <object type="application/x-shockwave-flash" + width="320" height="240" + data="${href.chrome('bitten', 'charts.swf')}"> + <param name="movie" value="${href.chrome('bitten', 'charts.swf')}" /> + <param name="FlashVars" + value="library_path=${href.chrome('bitten')}&xml_source=$chart.href${config.charts_license and '&license='+config.charts_license or ''}" /> + <param name="wmode" value="transparent" /> + </object><br /></py:for> </div> <table py:if="config.platforms and config.builds"
deleted file mode 100644 --- a/bitten/templates/json.txt +++ /dev/null @@ -1,1 +0,0 @@ -${__import__("simplejson").dumps(json)}
--- a/bitten/web_ui.py +++ b/bitten/web_ui.py @@ -394,8 +394,7 @@ for generator in ReportChartController(self.env).generators: for category in generator.get_supported_categories(): chart_generators.append({ - 'href': req.href.build(config.name, 'chart/' + category), - 'category': category, + 'href': req.href.build(config.name, 'chart/' + category) }) data['config']['charts'] = chart_generators charts_license = self.config.get('bitten', 'charts_license') @@ -538,7 +537,6 @@ add_script(req, 'common/js/folding.js') add_script(req, 'bitten/tabset.js') - add_script(req, 'bitten/jquery.flot.js') add_stylesheet(req, 'bitten/bitten.css') return 'bitten_build.html', data, None @@ -702,7 +700,7 @@ else: raise TracError('Unknown report category "%s"' % category) - return tmpl, data, 'text/plain' + return tmpl, data, 'text/xml' class SourceFileLinkFormatter(Component):