# HG changeset patch # User osimons # Date 1253226165 0 # Node ID a51af1f06e46e1c7643852c61feb7dc878a9b42a # Parent 67b1f4577359ab544b1bfaf0630341814035f49e Reverse-merging [770] to re-create history for 4 deleted files. Follow-up to [773]. diff --git a/bitten/htdocs/charts.swf b/bitten/htdocs/charts.swf new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e2821facdb1d55065baf6a435dafe72de5d8e777 GIT binary patch literal 29521 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' , - '', - ''); - - 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(' 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(''); - } else if (aFill) { - lineStr.push(''); - } else { - lineStr.push( - '' - ); - } - - lineStr.push(""); - - 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 diff --git a/bitten/htdocs/jquery.flot.js b/bitten/htdocs/jquery.flot.js 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 = $('').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 = $('').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('
    ' + l + '
    '); - } - - axis.labelHeight = 0; - if (labels.length > 0) { - var dummyDiv = $('
    ' - + labels.join("") + '
    ').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('
    ' + l + '
    '); - } - - if (labels.length > 0) { - var dummyDiv = $('
    ' - + labels.join("") + '
    ').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 = '
    '; - - 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 '
    ' + tick.label + "
    "; - }); - - - addLabels(axes.yaxis, function (tick, axis) { - return '
    ' + tick.label + "
    "; - }); - - addLabels(axes.x2axis, function (tick, axis) { - return '
    ' + tick.label + "
    "; - }); - - addLabels(axes.y2axis, function (tick, axis) { - return '
    ' + tick.label + "
    "; - }); - - html += '
    '; - - 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(''); - fragments.push(''); - rowStarted = true; - } - - var label = series[i].label; - if (options.legend.labelFormatter != null) - label = options.legend.labelFormatter(label); - - fragments.push( - '
    ' + - '' + label + ''); - } - if (rowStarted) - fragments.push(''); - - if (fragments.length == 0) - return; - - var table = '' + fragments.join("") + '
    '; - 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 = $('
    ' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
    ').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(); - $('
    ').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); diff --git a/bitten/report/coverage.py b/bitten/report/coverage.py --- 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): diff --git a/bitten/report/lint.py b/bitten/report/lint.py --- 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): diff --git a/bitten/report/testing.py b/bitten/report/testing.py --- 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): diff --git a/bitten/templates/bitten_chart_coverage.html b/bitten/templates/bitten_chart_coverage.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_chart_coverage.html @@ -0,0 +1,38 @@ + + + area + area + + + + + + + + + $value + $value + + + + + + + + + + bbbbbb + 9999ff + + + + + + + $title + + + diff --git a/bitten/templates/bitten_chart_lint.html b/bitten/templates/bitten_chart_lint.html new file mode 100644 --- /dev/null +++ b/bitten/templates/bitten_chart_lint.html @@ -0,0 +1,39 @@ + + + + area + line + line + line + line + + + + + + + + + $value + $value + + + + + + + + + + + + + + + + $title + + + diff --git a/bitten/templates/bitten_chart_tests.html b/bitten/templates/bitten_chart_tests.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_chart_tests.html @@ -0,0 +1,40 @@ + + + area + column + line + + + + + + + + + $value + $value + + + + + + + + + + 99dd99 + ff0000 + ffff00 + + + + + + + $title + + + diff --git a/bitten/templates/bitten_config.html b/bitten/templates/bitten_config.html --- a/bitten/templates/bitten_config.html +++ b/bitten/templates/bitten_config.html @@ -8,7 +8,6 @@ $title - @@ -144,20 +143,14 @@ )
    -
    - -
    + + + + +