diff options
author | 2017-09-01 11:57:47 +0800 | |
---|---|---|
committer | 2017-09-01 11:57:58 +0800 | |
commit | 57dbba269d19bc59fad89160200bb2dbcccb9003 (patch) | |
tree | d466041ceffa2161124ca79a48b3e077777c74b8 /usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js | |
parent | 4ff32341a0af1972b44a7410e76e9b231131e7ab (diff) |
Upload Monitor function code
Change-Id: I33ad76221b4cb771a298ff240245fc24be664efb
Issue-Id: USECASEUI-6
Signed-off-by: shentao <shentao@chinamobile.com>
Diffstat (limited to 'usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js')
36 files changed, 14857 insertions, 0 deletions
diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dashed-canvas.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dashed-canvas.js new file mode 100644 index 00000000..cec77298 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dashed-canvas.js @@ -0,0 +1,176 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Adds support for dashed lines to the HTML5 canvas. + * + * Usage: + * var ctx = canvas.getContext("2d"); + * ctx.installPattern([10, 5]) // draw 10 pixels, skip 5 pixels, repeat. + * ctx.beginPath(); + * ctx.moveTo(100, 100); // start the first line segment. + * ctx.lineTo(150, 200); + * ctx.lineTo(200, 100); + * ctx.moveTo(300, 150); // start a second, unconnected line + * ctx.lineTo(400, 250); + * ... + * ctx.stroke(); // draw the dashed line. + * ctx.uninstallPattern(); + * + * This is designed to leave the canvas untouched when it's not used. + * If you never install a pattern, or call uninstallPattern(), then the canvas + * will be exactly as it would have if you'd never used this library. The only + * difference from the standard canvas will be the "installPattern" method of + * the drawing context. + */ + +/** + * Change the stroking style of the canvas drawing context from a solid line to + * a pattern (e.g. dashes, dash-dot-dash, etc.) + * + * Once you've installed the pattern, you can draw with it by using the + * beginPath(), moveTo(), lineTo() and stroke() method calls. Note that some + * more advanced methods (e.g. quadraticCurveTo() and bezierCurveTo()) are not + * supported. See file overview for a working example. + * + * Side effects of calling this method include adding an "isPatternInstalled" + * property and "uninstallPattern" method to this particular canvas context. + * You must call uninstallPattern() before calling installPattern() again. + * + * @param {Array.<number>} pattern A description of the stroke pattern. Even + * indices indicate a draw and odd indices indicate a gap (in pixels). The + * array should have a even length as any odd lengthed array could be expressed + * as a smaller even length array. + */ +CanvasRenderingContext2D.prototype.installPattern = function(pattern) { + "use strict"; + + if (typeof(this.isPatternInstalled) !== 'undefined') { + throw "Must un-install old line pattern before installing a new one."; + } + this.isPatternInstalled = true; + + var dashedLineToHistory = [0, 0]; + + // list of connected line segements: + // [ [x1, y1], ..., [xn, yn] ], [ [x1, y1], ..., [xn, yn] ] + var segments = []; + + // Stash away copies of the unmodified line-drawing functions. + var realBeginPath = this.beginPath; + var realLineTo = this.lineTo; + var realMoveTo = this.moveTo; + var realStroke = this.stroke; + + /** @type {function()|undefined} */ + this.uninstallPattern = function() { + this.beginPath = realBeginPath; + this.lineTo = realLineTo; + this.moveTo = realMoveTo; + this.stroke = realStroke; + this.uninstallPattern = undefined; + this.isPatternInstalled = undefined; + }; + + // Keep our own copies of the line segments as they're drawn. + this.beginPath = function() { + segments = []; + realBeginPath.call(this); + }; + this.moveTo = function(x, y) { + segments.push([[x, y]]); + realMoveTo.call(this, x, y); + }; + this.lineTo = function(x, y) { + var last = segments[segments.length - 1]; + last.push([x, y]); + }; + + this.stroke = function() { + if (segments.length === 0) { + // Maybe the user is drawing something other than a line. + // TODO(danvk): test this case. + realStroke.call(this); + return; + } + + for (var i = 0; i < segments.length; i++) { + var seg = segments[i]; + var x1 = seg[0][0], y1 = seg[0][1]; + for (var j = 1; j < seg.length; j++) { + // Draw a dashed line from (x1, y1) - (x2, y2) + var x2 = seg[j][0], y2 = seg[j][1]; + this.save(); + + // Calculate transformation parameters + var dx = (x2-x1); + var dy = (y2-y1); + var len = Math.sqrt(dx*dx + dy*dy); + var rot = Math.atan2(dy, dx); + + // Set transformation + this.translate(x1, y1); + realMoveTo.call(this, 0, 0); + this.rotate(rot); + + // Set last pattern index we used for this pattern. + var patternIndex = dashedLineToHistory[0]; + var x = 0; + while (len > x) { + // Get the length of the pattern segment we are dealing with. + var segment = pattern[patternIndex]; + // If our last draw didn't complete the pattern segment all the way + // we will try to finish it. Otherwise we will try to do the whole + // segment. + if (dashedLineToHistory[1]) { + x += dashedLineToHistory[1]; + } else { + x += segment; + } + + if (x > len) { + // We were unable to complete this pattern index all the way, keep + // where we are the history so our next draw continues where we + // left off in the pattern. + dashedLineToHistory = [patternIndex, x-len]; + x = len; + } else { + // We completed this patternIndex, we put in the history that we + // are on the beginning of the next segment. + dashedLineToHistory = [(patternIndex+1)%pattern.length, 0]; + } + + // We do a line on a even pattern index and just move on a odd + // pattern index. The move is the empty space in the dash. + if (patternIndex % 2 === 0) { + realLineTo.call(this, x, 0); + } else { + realMoveTo.call(this, x, 0); + } + + // If we are not done, next loop process the next pattern segment, or + // the first segment again if we are at the end of the pattern. + patternIndex = (patternIndex+1) % pattern.length; + } + + this.restore(); + x1 = x2, y1 = y2; + } + } + realStroke.call(this); + segments = []; + }; +}; + +/** + * Removes the previously-installed pattern. + * You must call installPattern() before calling this. You can install at most + * one pattern at a time--there is no pattern stack. + */ +CanvasRenderingContext2D.prototype.uninstallPattern = function() { + // This will be replaced by a non-error version when a pattern is installed. + throw "Must install a line pattern before uninstalling it."; +} diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/data.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/data.js new file mode 100644 index 00000000..d92a6339 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/data.js @@ -0,0 +1,63 @@ +function StubbedData() { +return "" + +"Date,A,B\n" + +"20061001,3.01953818828,0.7212041046,2.18487394958,0.599318549691\n" + +"20061002,3.63321799308,0.778297234566,1.69491525424,0.531417655826\n" + +"20061003,2.44328097731,0.644967734352,2.51256281407,0.640539070386\n" + +"20061004,3.52733686067,0.774700921683,2.68456375839,0.66207105053\n" + +"20061005,3.28719723183,0.741636245748,2.35294117647,0.621407707226\n" + +"20061006,1.58450704225,0.523967868159,3.78657487091,0.791868460623\n" + +"20061007,5.32859680284,0.946589405904,4.0404040404,0.807910739509\n" + +"20061008,2.64084507042,0.672799548916,2.37288135593,0.626609885481\n" + +"20061009,2.26480836237,0.620990945917,3.5413153457,0.75897176848\n" + +"20061010,3.29289428076,0.74289969528,2.02702702703,0.579191340004\n" + +"20061011,2.7633851468,0.681234043829,1.1744966443,0.4413034044\n" + +"20061012,3.28719723183,0.741636245748,3.37268128162,0.741327769578\n" + +"20061013,1.77304964539,0.55569466381,1.85810810811,0.555011329732\n" + +"20061014,3.39892665474,0.7664008338,1.67224080268,0.524368852929\n" + +"20061015,2.65017667845,0.675144574777,3.35570469799,0.737661045752\n" + +"20061016,3.63951473137,0.779620631266,2.34899328859,0.620377617453\n" + +"20061017,2.25694444444,0.618859623032,1.68067226891,0.526990133716\n" + +"20061018,4.47504302926,0.857766274964,2.51677852349,0.641599927369\n" + +"20061019,2.44755244755,0.646081155692,1.68067226891,0.526990133716\n" + +"20061020,3.67775831874,0.787656442774,3.066439523,0.711598843969\n" + +"20061021,3.94265232975,0.823839169829,3.85906040268,0.788990618726\n" + +"20061022,2.59067357513,0.660187558973,3.71621621622,0.777438794254\n" + +"20061023,4.33275563258,0.847570482324,3.85906040268,0.788990618726\n" + +"20061024,3.10344827586,0.720049610821,2.84280936455,0.679611549697\n" + +"20061025,1.40350877193,0.492720767725,2.7027027027,0.666482380968\n" + +"20061026,1.95035460993,0.582291234145,2.36486486486,0.624518599275\n" + +"20061027,2.30905861456,0.632980642182,2.03045685279,0.580161203819\n" + +"20061028,4.09252669039,0.835706590809,2.87648054146,0.68754192469\n" + +"20061029,2.66903914591,0.679883997626,2.02360876897,0.578224712918\n" + +"20061030,4.74516695958,0.89127787497,4.36241610738,0.836670992529\n" + +"20061031,2.78260869565,0.685905251933,3.20945945946,0.724388507178\n" + +"20061101,1.5873015873,0.524884521441,1.51260504202,0.500373860545\n" + +"20061102,2.78745644599,0.687083077461,2.0202020202,0.57726130639\n" + +"20061103,5.11463844797,0.925157232782,2.68907563025,0.663168401088\n" + +"20061104,4.9001814882,0.919644816432,3.07692307692,0.713993047527\n" + +"20061105,5.13274336283,0.928343545136,3.55329949239,0.761492892041\n" + +"20061106,1.92644483363,0.575222935029,2.35294117647,0.621407707226\n" + +"20061107,2.46478873239,0.650573541306,1.52027027027,0.502889967904\n" + +"20061108,2.13523131673,0.609772022763,2.6981450253,0.665374048085\n" + +"20061109,3.88007054674,0.811026422222,2.72572402044,0.672079879106\n" + +"20061110,2.63620386643,0.671633132526,3.71621621622,0.777438794254\n" + +"20061111,3.69718309859,0.791736755355,3.0303030303,0.703344064467\n" + +"20061112,3.83944153578,0.802703592906,4.05405405405,0.81058250986\n" + +"20061113,2.47787610619,0.653984033555,2.20338983051,0.604340313133\n" + +"20061114,1.77304964539,0.55569466381,2.22222222222,0.60944692682\n" + +"20061115,2.30088495575,0.630766388737,0.843170320405,0.375484163785\n" + +"20061116,1.57894736842,0.522144132232,2.19594594595,0.602321544724\n" + +"20061118,2.45183887916,0.647198426991,1.69491525424,0.531417655826\n" + +"20061119,3.52733686067,0.774700921683,1.85185185185,0.55316023504\n" + +"20061120,2.97723292469,0.711254751484,2.6981450253,0.665374048085\n" + +"20061121,2.29681978799,0.629665059963,2.01680672269,0.576301104352\n" + +"20061122,3.01418439716,0.719945245328,2.5466893039,0.649125445325\n" + +"20061123,3.78378378378,0.809917534069,2.6936026936,0.664269394219\n" + +"20061124,3.18584070796,0.738851643987,2.01005025126,0.57439025002\n" + +"20061125,2.83185840708,0.697868332879,3.066439523,0.711598843969\n" + +"20061126,3.01953818828,0.7212041046,2.53378378378,0.645878720149\n" + +"20061127,2.81195079086,0.693033387099,1.51006711409,0.499540743312\n" + +"20061128,2.97723292469,0.711254751484,2.54237288136,0.648039583782\n" + +"20061129,1.41093474427,0.495309102312,3.02013422819,0.701020603129"; +} diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-canvas.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-canvas.js new file mode 100644 index 00000000..cba4f449 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-canvas.js @@ -0,0 +1,816 @@ +/** + * @license + * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the + * needs of dygraphs. + * + * In particular, support for: + * - grid overlays + * - error bars + * - dygraphs attribute system + */ + +/** + * The DygraphCanvasRenderer class does the actual rendering of the chart onto + * a canvas. It's based on PlotKit.CanvasRenderer. + * @param {Object} element The canvas to attach to + * @param {Object} elementContext The 2d context of the canvas (injected so it + * can be mocked for testing.) + * @param {Layout} layout The DygraphLayout object for this graph. + * @constructor + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false,RGBColorParser:false */ +"use strict"; + + +/** + * @constructor + * + * This gets called when there are "new points" to chart. This is generally the + * case when the underlying data being charted has changed. It is _not_ called + * in the common case that the user has zoomed or is panning the view. + * + * The chart canvas has already been created by the Dygraph object. The + * renderer simply gets a drawing context. + * + * @param {Dygraph} dygraph The chart to which this renderer belongs. + * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw. + * @param {CanvasRenderingContext2D} elementContext The drawing context. + * @param {DygraphLayout} layout The chart's DygraphLayout object. + * + * TODO(danvk): remove the elementContext property. + */ +var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { + this.dygraph_ = dygraph; + + this.layout = layout; + this.element = element; + this.elementContext = elementContext; + this.container = this.element.parentNode; + + this.height = this.element.height; + this.width = this.element.width; + + // --- check whether everything is ok before we return + // NOTE(konigsberg): isIE is never defined in this object. Bug of some sort. + if (!this.isIE && !(DygraphCanvasRenderer.isSupported(this.element))) + throw "Canvas is not supported."; + + // internal state + this.area = layout.getPlotArea(); + this.container.style.position = "relative"; + this.container.style.width = this.width + "px"; + + // Set up a clipping area for the canvas (and the interaction canvas). + // This ensures that we don't overdraw. + if (this.dygraph_.isUsingExcanvas_) { + this._createIEClipArea(); + } else { + // on Android 3 and 4, setting a clipping area on a canvas prevents it from + // displaying anything. + if (!Dygraph.isAndroid()) { + var ctx = this.dygraph_.canvas_ctx_; + ctx.beginPath(); + ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); + ctx.clip(); + + ctx = this.dygraph_.hidden_ctx_; + ctx.beginPath(); + ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); + ctx.clip(); + } + } +}; + +/** + * This just forwards to dygraph.attr_. + * TODO(danvk): remove this? + * @private + */ +DygraphCanvasRenderer.prototype.attr_ = function(name, opt_seriesName) { + return this.dygraph_.attr_(name, opt_seriesName); +}; + +/** + * Clears out all chart content and DOM elements. + * This is called immediately before render() on every frame, including + * during zooms and pans. + * @private + */ +DygraphCanvasRenderer.prototype.clear = function() { + var context; + if (this.isIE) { + // VML takes a while to start up, so we just poll every this.IEDelay + try { + if (this.clearDelay) { + this.clearDelay.cancel(); + this.clearDelay = null; + } + context = this.elementContext; + } + catch (e) { + // TODO(danvk): this is broken, since MochiKit.Async is gone. + // this.clearDelay = MochiKit.Async.wait(this.IEDelay); + // this.clearDelay.addCallback(bind(this.clear, this)); + return; + } + } + + context = this.elementContext; + context.clearRect(0, 0, this.width, this.height); +}; + +/** + * Checks whether the browser supports the <canvas> tag. + * @private + */ +DygraphCanvasRenderer.isSupported = function(canvasName) { + var canvas = null; + try { + if (typeof(canvasName) == 'undefined' || canvasName === null) { + canvas = document.createElement("canvas"); + } else { + canvas = canvasName; + } + canvas.getContext("2d"); + } + catch (e) { + var ie = navigator.appVersion.match(/MSIE (\d\.\d)/); + var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1); + if ((!ie) || (ie[1] < 6) || (opera)) + return false; + return true; + } + return true; +}; + +/** + * This method is responsible for drawing everything on the chart, including + * lines, error bars, fills and axes. + * It is called immediately after clear() on every frame, including during pans + * and zooms. + * @private + */ +DygraphCanvasRenderer.prototype.render = function() { + // attaches point.canvas{x,y} + this._updatePoints(); + + // actually draws the chart. + this._renderLineChart(); +}; + +DygraphCanvasRenderer.prototype._createIEClipArea = function() { + var className = 'dygraph-clip-div'; + var graphDiv = this.dygraph_.graphDiv; + + // Remove old clip divs. + for (var i = graphDiv.childNodes.length-1; i >= 0; i--) { + if (graphDiv.childNodes[i].className == className) { + graphDiv.removeChild(graphDiv.childNodes[i]); + } + } + + // Determine background color to give clip divs. + var backgroundColor = document.bgColor; + var element = this.dygraph_.graphDiv; + while (element != document) { + var bgcolor = element.currentStyle.backgroundColor; + if (bgcolor && bgcolor != 'transparent') { + backgroundColor = bgcolor; + break; + } + element = element.parentNode; + } + + function createClipDiv(area) { + if (area.w === 0 || area.h === 0) { + return; + } + var elem = document.createElement('div'); + elem.className = className; + elem.style.backgroundColor = backgroundColor; + elem.style.position = 'absolute'; + elem.style.left = area.x + 'px'; + elem.style.top = area.y + 'px'; + elem.style.width = area.w + 'px'; + elem.style.height = area.h + 'px'; + graphDiv.appendChild(elem); + } + + var plotArea = this.area; + // Left side + createClipDiv({ + x:0, y:0, + w:plotArea.x, + h:this.height + }); + + // Top + createClipDiv({ + x: plotArea.x, y: 0, + w: this.width - plotArea.x, + h: plotArea.y + }); + + // Right side + createClipDiv({ + x: plotArea.x + plotArea.w, y: 0, + w: this.width-plotArea.x - plotArea.w, + h: this.height + }); + + // Bottom + createClipDiv({ + x: plotArea.x, + y: plotArea.y + plotArea.h, + w: this.width - plotArea.x, + h: this.height - plotArea.h - plotArea.y + }); +}; + + +/** + * Returns a predicate to be used with an iterator, which will + * iterate over points appropriately, depending on whether + * connectSeparatedPoints is true. When it's false, the predicate will + * skip over points with missing yVals. + */ +DygraphCanvasRenderer._getIteratorPredicate = function(connectSeparatedPoints) { + return connectSeparatedPoints ? + DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : + null; +}; + +DygraphCanvasRenderer._predicateThatSkipsEmptyPoints = + function(array, idx) { + return array[idx].yval !== null; +}; + +/** + * Draws a line with the styles passed in and calls all the drawPointCallbacks. + * @param {Object} e The dictionary passed to the plotter function. + * @private + */ +DygraphCanvasRenderer._drawStyledLine = function(e, + color, strokeWidth, strokePattern, drawPoints, + drawPointCallback, pointSize) { + var g = e.dygraph; + // TODO(konigsberg): Compute attributes outside this method call. + var stepPlot = g.getOption("stepPlot", e.setName); + + if (!Dygraph.isArrayLike(strokePattern)) { + strokePattern = null; + } + + var drawGapPoints = g.getOption('drawGapEdgePoints', e.setName); + + var points = e.points; + var iter = Dygraph.createIterator(points, 0, points.length, + DygraphCanvasRenderer._getIteratorPredicate( + g.getOption("connectSeparatedPoints"))); // TODO(danvk): per-series? + + var stroking = strokePattern && (strokePattern.length >= 2); + + var ctx = e.drawingContext; + ctx.save(); + if (stroking) { + ctx.installPattern(strokePattern); + } + + var pointsOnLine = DygraphCanvasRenderer._drawSeries( + e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color); + DygraphCanvasRenderer._drawPointsOnLine( + e, pointsOnLine, drawPointCallback, color, pointSize); + + if (stroking) { + ctx.uninstallPattern(); + } + + ctx.restore(); +}; + +/** + * This does the actual drawing of lines on the canvas, for just one series. + * Returns a list of [canvasx, canvasy] pairs for points for which a + * drawPointCallback should be fired. These include isolated points, or all + * points if drawPoints=true. + * @param {Object} e The dictionary passed to the plotter function. + * @private + */ +DygraphCanvasRenderer._drawSeries = function(e, + iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) { + + var prevCanvasX = null; + var prevCanvasY = null; + var nextCanvasY = null; + var isIsolated; // true if this point is isolated (no line segments) + var point; // the point being processed in the while loop + var pointsOnLine = []; // Array of [canvasx, canvasy] pairs. + var first = true; // the first cycle through the while loop + + var ctx = e.drawingContext; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = strokeWidth; + + // NOTE: we break the iterator's encapsulation here for about a 25% speedup. + var arr = iter.array_; + var limit = iter.end_; + var predicate = iter.predicate_; + + for (var i = iter.start_; i < limit; i++) { + point = arr[i]; + if (predicate) { + while (i < limit && !predicate(arr, i)) { + i++; + } + if (i == limit) break; + point = arr[i]; + } + + if (point.canvasy === null || point.canvasy != point.canvasy) { + if (stepPlot && prevCanvasX !== null) { + // Draw a horizontal line to the start of the missing data + ctx.moveTo(prevCanvasX, prevCanvasY); + ctx.lineTo(point.canvasx, prevCanvasY); + } + prevCanvasX = prevCanvasY = null; + } else { + isIsolated = false; + if (drawGapPoints || !prevCanvasX) { + iter.nextIdx_ = i; + iter.next(); + nextCanvasY = iter.hasNext ? iter.peek.canvasy : null; + + var isNextCanvasYNullOrNaN = nextCanvasY === null || + nextCanvasY != nextCanvasY; + isIsolated = (!prevCanvasX && isNextCanvasYNullOrNaN); + if (drawGapPoints) { + // Also consider a point to be "isolated" if it's adjacent to a + // null point, excluding the graph edges. + if ((!first && !prevCanvasX) || + (iter.hasNext && isNextCanvasYNullOrNaN)) { + isIsolated = true; + } + } + } + + if (prevCanvasX !== null) { + if (strokeWidth) { + if (stepPlot) { + ctx.moveTo(prevCanvasX, prevCanvasY); + ctx.lineTo(point.canvasx, prevCanvasY); + } + + ctx.lineTo(point.canvasx, point.canvasy); + } + } else { + ctx.moveTo(point.canvasx, point.canvasy); + } + if (drawPoints || isIsolated) { + pointsOnLine.push([point.canvasx, point.canvasy, point.idx]); + } + prevCanvasX = point.canvasx; + prevCanvasY = point.canvasy; + } + first = false; + } + ctx.stroke(); + return pointsOnLine; +}; + +/** + * This fires the drawPointCallback functions, which draw dots on the points by + * default. This gets used when the "drawPoints" option is set, or when there + * are isolated points. + * @param {Object} e The dictionary passed to the plotter function. + * @private + */ +DygraphCanvasRenderer._drawPointsOnLine = function( + e, pointsOnLine, drawPointCallback, color, pointSize) { + var ctx = e.drawingContext; + for (var idx = 0; idx < pointsOnLine.length; idx++) { + var cb = pointsOnLine[idx]; + ctx.save(); + drawPointCallback( + e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]); + ctx.restore(); + } +}; + +/** + * Attaches canvas coordinates to the points array. + * @private + */ +DygraphCanvasRenderer.prototype._updatePoints = function() { + // Update Points + // TODO(danvk): here + // + // TODO(bhs): this loop is a hot-spot for high-point-count charts. These + // transformations can be pushed into the canvas via linear transformation + // matrices. + // NOTE(danvk): this is trickier than it sounds at first. The transformation + // needs to be done before the .moveTo() and .lineTo() calls, but must be + // undone before the .stroke() call to ensure that the stroke width is + // unaffected. An alternative is to reduce the stroke width in the + // transformed coordinate space, but you can't specify different values for + // each dimension (as you can with .scale()). The speedup here is ~12%. + var sets = this.layout.points; + for (var i = sets.length; i--;) { + var points = sets[i]; + for (var j = points.length; j--;) { + var point = points[j]; + point.canvasx = this.area.w * point.x + this.area.x; + point.canvasy = this.area.h * point.y + this.area.y; + } + } +}; + +/** + * Add canvas Actually draw the lines chart, including error bars. + * + * This function can only be called if DygraphLayout's points array has been + * updated with canvas{x,y} attributes, i.e. by + * DygraphCanvasRenderer._updatePoints. + * + * @param {string=} opt_seriesName when specified, only that series will + * be drawn. (This is used for expedited redrawing with highlightSeriesOpts) + * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing + * context. However, lines are typically drawn on the object's + * elementContext. + * @private + */ +DygraphCanvasRenderer.prototype._renderLineChart = function(opt_seriesName, opt_ctx) { + var ctx = opt_ctx || this.elementContext; + var i; + + var sets = this.layout.points; + var setNames = this.layout.setNames; + var setName; + + this.colors = this.dygraph_.colorsMap_; + + // Determine which series have specialized plotters. + var plotter_attr = this.attr_("plotter"); + var plotters = plotter_attr; + if (!Dygraph.isArrayLike(plotters)) { + plotters = [plotters]; + } + + var setPlotters = {}; // series name -> plotter fn. + for (i = 0; i < setNames.length; i++) { + setName = setNames[i]; + var setPlotter = this.attr_("plotter", setName); + if (setPlotter == plotter_attr) continue; // not specialized. + + setPlotters[setName] = setPlotter; + } + + for (i = 0; i < plotters.length; i++) { + var plotter = plotters[i]; + var is_last = (i == plotters.length - 1); + + for (var j = 0; j < sets.length; j++) { + setName = setNames[j]; + if (opt_seriesName && setName != opt_seriesName) continue; + + var points = sets[j]; + + // Only throw in the specialized plotters on the last iteration. + var p = plotter; + if (setName in setPlotters) { + if (is_last) { + p = setPlotters[setName]; + } else { + // Don't use the standard plotters in this case. + continue; + } + } + + var color = this.colors[setName]; + var strokeWidth = this.dygraph_.getOption("strokeWidth", setName); + + ctx.save(); + ctx.strokeStyle = color; + ctx.lineWidth = strokeWidth; + p({ + points: points, + setName: setName, + drawingContext: ctx, + color: color, + strokeWidth: strokeWidth, + dygraph: this.dygraph_, + axis: this.dygraph_.axisPropertiesForSeries(setName), + plotArea: this.area, + seriesIndex: j, + seriesCount: sets.length, + singleSeriesName: opt_seriesName, + allSeriesPoints: sets + }); + ctx.restore(); + } + } +}; + +/** + * Standard plotters. These may be used by clients via Dygraph.Plotters. + * See comments there for more details. + */ +DygraphCanvasRenderer._Plotters = { + linePlotter: function(e) { + DygraphCanvasRenderer._linePlotter(e); + }, + + fillPlotter: function(e) { + DygraphCanvasRenderer._fillPlotter(e); + }, + + errorPlotter: function(e) { + DygraphCanvasRenderer._errorPlotter(e); + } +}; + +/** + * Plotter which draws the central lines for a series. + * @private + */ +DygraphCanvasRenderer._linePlotter = function(e) { + var g = e.dygraph; + var setName = e.setName; + var strokeWidth = e.strokeWidth; + + // TODO(danvk): Check if there's any performance impact of just calling + // getOption() inside of _drawStyledLine. Passing in so many parameters makes + // this code a bit nasty. + var borderWidth = g.getOption("strokeBorderWidth", setName); + var drawPointCallback = g.getOption("drawPointCallback", setName) || + Dygraph.Circles.DEFAULT; + var strokePattern = g.getOption("strokePattern", setName); + var drawPoints = g.getOption("drawPoints", setName); + var pointSize = g.getOption("pointSize", setName); + + if (borderWidth && strokeWidth) { + DygraphCanvasRenderer._drawStyledLine(e, + g.getOption("strokeBorderColor", setName), + strokeWidth + 2 * borderWidth, + strokePattern, + drawPoints, + drawPointCallback, + pointSize + ); + } + + DygraphCanvasRenderer._drawStyledLine(e, + e.color, + strokeWidth, + strokePattern, + drawPoints, + drawPointCallback, + pointSize + ); +}; + +/** + * Draws the shaded error bars/confidence intervals for each series. + * This happens before the center lines are drawn, since the center lines + * need to be drawn on top of the error bars for all series. + * @private + */ +DygraphCanvasRenderer._errorPlotter = function(e) { + var g = e.dygraph; + var setName = e.setName; + var errorBars = g.getOption("errorBars") || g.getOption("customBars"); + if (!errorBars) return; + + var fillGraph = g.getOption("fillGraph", setName); + if (fillGraph) { + g.warn("Can't use fillGraph option with error bars"); + } + + var ctx = e.drawingContext; + var color = e.color; + var fillAlpha = g.getOption('fillAlpha', setName); + var stepPlot = g.getOption("stepPlot", setName); + var points = e.points; + + var iter = Dygraph.createIterator(points, 0, points.length, + DygraphCanvasRenderer._getIteratorPredicate( + g.getOption("connectSeparatedPoints"))); + + var newYs; + + // setup graphics context + var prevX = NaN; + var prevY = NaN; + var prevYs = [-1, -1]; + // should be same color as the lines but only 15% opaque. + var rgb = new RGBColorParser(color); + var err_color = + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; + ctx.fillStyle = err_color; + ctx.beginPath(); + + var isNullUndefinedOrNaN = function(x) { + return (x === null || + x === undefined || + isNaN(x)); + }; + + while (iter.hasNext) { + var point = iter.next(); + if ((!stepPlot && isNullUndefinedOrNaN(point.y)) || + (stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY))) { + prevX = NaN; + continue; + } + + if (stepPlot) { + newYs = [ point.y_bottom, point.y_top ]; + prevY = point.y; + } else { + newYs = [ point.y_bottom, point.y_top ]; + } + newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y; + newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y; + if (!isNaN(prevX)) { + if (stepPlot) { + ctx.moveTo(prevX, prevYs[0]); + ctx.lineTo(point.canvasx, prevYs[0]); + ctx.lineTo(point.canvasx, prevYs[1]); + } else { + ctx.moveTo(prevX, prevYs[0]); + ctx.lineTo(point.canvasx, newYs[0]); + ctx.lineTo(point.canvasx, newYs[1]); + } + ctx.lineTo(prevX, prevYs[1]); + ctx.closePath(); + } + prevYs = newYs; + prevX = point.canvasx; + } + ctx.fill(); +}; + +/** + * Draws the shaded regions when "fillGraph" is set. Not to be confused with + * error bars. + * + * For stacked charts, it's more convenient to handle all the series + * simultaneously. So this plotter plots all the points on the first series + * it's asked to draw, then ignores all the other series. + * + * @private + */ +DygraphCanvasRenderer._fillPlotter = function(e) { + // Skip if we're drawing a single series for interactive highlight overlay. + if (e.singleSeriesName) return; + + // We'll handle all the series at once, not one-by-one. + if (e.seriesIndex !== 0) return; + + var g = e.dygraph; + var setNames = g.getLabels().slice(1); // remove x-axis + + // getLabels() includes names for invisible series, which are not included in + // allSeriesPoints. We remove those to make the two match. + // TODO(danvk): provide a simpler way to get this information. + for (var i = setNames.length; i >= 0; i--) { + if (!g.visibility()[i]) setNames.splice(i, 1); + } + + var anySeriesFilled = (function() { + for (var i = 0; i < setNames.length; i++) { + if (g.getOption("fillGraph", setNames[i])) return true; + } + return false; + })(); + + if (!anySeriesFilled) return; + + var ctx = e.drawingContext; + var area = e.plotArea; + var sets = e.allSeriesPoints; + var setCount = sets.length; + + var fillAlpha = g.getOption('fillAlpha'); + var stackedGraph = g.getOption("stackedGraph"); + var colors = g.getColors(); + + // For stacked graphs, track the baseline for filling. + // + // The filled areas below graph lines are trapezoids with two + // vertical edges. The top edge is the line segment being drawn, and + // the baseline is the bottom edge. Each baseline corresponds to the + // top line segment from the previous stacked line. In the case of + // step plots, the trapezoids are rectangles. + var baseline = {}; + var currBaseline; + var prevStepPlot; // for different line drawing modes (line/step) per series + + // process sets in reverse order (needed for stacked graphs) + for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) { + var setName = setNames[setIdx]; + if (!g.getOption('fillGraph', setName)) continue; + + var stepPlot = g.getOption('stepPlot', setName); + var color = colors[setIdx]; + var axis = g.axisPropertiesForSeries(setName); + var axisY = 1.0 + axis.minyval * axis.yscale; + if (axisY < 0.0) axisY = 0.0; + else if (axisY > 1.0) axisY = 1.0; + axisY = area.h * axisY + area.y; + + var points = sets[setIdx]; + var iter = Dygraph.createIterator(points, 0, points.length, + DygraphCanvasRenderer._getIteratorPredicate( + g.getOption("connectSeparatedPoints"))); + + // setup graphics context + var prevX = NaN; + var prevYs = [-1, -1]; + var newYs; + // should be same color as the lines but only 15% opaque. + var rgb = new RGBColorParser(color); + var err_color = + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; + ctx.fillStyle = err_color; + ctx.beginPath(); + var last_x, is_first = true; + while (iter.hasNext) { + var point = iter.next(); + if (!Dygraph.isOK(point.y)) { + prevX = NaN; + if (point.y_stacked !== null && !isNaN(point.y_stacked)) { + baseline[point.canvasx] = area.h * point.y_stacked + area.y; + } + continue; + } + if (stackedGraph) { + if (!is_first && last_x == point.xval) { + continue; + } else { + is_first = false; + last_x = point.xval; + } + + currBaseline = baseline[point.canvasx]; + var lastY; + if (currBaseline === undefined) { + lastY = axisY; + } else { + if(prevStepPlot) { + lastY = currBaseline[0]; + } else { + lastY = currBaseline; + } + } + newYs = [ point.canvasy, lastY ]; + + if(stepPlot) { + // Step plots must keep track of the top and bottom of + // the baseline at each point. + if(prevYs[0] === -1) { + baseline[point.canvasx] = [ point.canvasy, axisY ]; + } else { + baseline[point.canvasx] = [ point.canvasy, prevYs[0] ]; + } + } else { + baseline[point.canvasx] = point.canvasy; + } + + } else { + newYs = [ point.canvasy, axisY ]; + } + if (!isNaN(prevX)) { + ctx.moveTo(prevX, prevYs[0]); + + // Move to top fill point + if (stepPlot) { + ctx.lineTo(point.canvasx, prevYs[0]); + } else { + ctx.lineTo(point.canvasx, newYs[0]); + } + // Move to bottom fill point + if (prevStepPlot && currBaseline) { + // Draw to the bottom of the baseline + ctx.lineTo(point.canvasx, currBaseline[1]); + } else { + ctx.lineTo(point.canvasx, newYs[1]); + } + + ctx.lineTo(prevX, prevYs[1]); + ctx.closePath(); + } + prevYs = newYs; + prevX = point.canvasx; + } + prevStepPlot = stepPlot; + ctx.fill(); + } +}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-combined.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-combined.js new file mode 100644 index 00000000..4f864538 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-combined.js @@ -0,0 +1,2 @@ +/*! @license Copyright 2011 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */ +Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(a,c,b){if(typeof(b)=="undefined"){b=10}for(;parseInt(a,10)<b&&b>1;b/=10){a=c.toString()+a}return a.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(a){return Date.ext.locales[a.locale].a[a.getDay()]},A:function(a){return Date.ext.locales[a.locale].A[a.getDay()]},b:function(a){return Date.ext.locales[a.locale].b[a.getMonth()]},B:function(a){return Date.ext.locales[a.locale].B[a.getMonth()]},c:"toLocaleString",C:function(a){return Date.ext.util.xPad(parseInt(a.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(a){return Date.ext.util.xPad(parseInt(Date.ext.util.G(a)/100,10),0)},G:function(c){var e=c.getFullYear();var b=parseInt(Date.ext.formats.V(c),10);var a=parseInt(Date.ext.formats.W(c),10);if(a>b){e++}else{if(a===0&&b>=52){e--}}return e},H:["getHours","0"],I:function(b){var a=b.getHours()%12;return Date.ext.util.xPad(a===0?12:a,0)},j:function(c){var a=c-new Date(""+c.getFullYear()+"/1/1 GMT");a+=c.getTimezoneOffset()*60000;var b=parseInt(a/60000/60/24,10)+1;return Date.ext.util.xPad(b,0,100)},m:function(a){return Date.ext.util.xPad(a.getMonth()+1,0)},M:["getMinutes","0"],p:function(a){return Date.ext.locales[a.locale].p[a.getHours()>=12?1:0]},P:function(a){return Date.ext.locales[a.locale].P[a.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(a){var b=a.getDay();return b===0?7:b},U:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=6-e.getDay();var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0)},V:function(e){var c=parseInt(Date.ext.formats.W(e),10);var a=(new Date(""+e.getFullYear()+"/1/1")).getDay();var b=c+(a>4||a<=1?0:1);if(b==53&&(new Date(""+e.getFullYear()+"/12/31")).getDay()<4){b=1}else{if(b===0){b=Date.ext.formats.V(new Date(""+(e.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(b,0)},w:"getDay",W:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=7-Date.ext.formats.u(e);var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0,10)},y:function(a){return Date.ext.util.xPad(a.getFullYear()%100,0)},Y:"getFullYear",z:function(c){var b=c.getTimezoneOffset();var a=Date.ext.util.xPad(parseInt(Math.abs(b/60),10),0);var e=Date.ext.util.xPad(b%60,0);return(b>0?"-":"+")+a+e},Z:function(a){return a.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(a){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(a){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var c=this;while(a.match(/%[cDhnrRtTxXzZ]/)){a=a.replace(/%([cDhnrRtTxXzZ])/g,function(e,d){var g=Date.ext.aggregates[d];return(g=="locale"?Date.ext.locales[c.locale][d]:g)})}var b=a.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(e,d){var g=Date.ext.formats[d];if(typeof(g)=="string"){return c[g]()}else{if(typeof(g)=="function"){return g.call(c,c)}else{if(typeof(g)=="object"&&typeof(g[0])=="string"){return Date.ext.util.xPad(c[g[0]](),g[1])}else{return d}}}});c=null;return b};"use strict";function RGBColorParser(f){this.ok=false;if(f.charAt(0)=="#"){f=f.substr(1,6)}f=f.replace(/ /g,"");f=f.toLowerCase();var b={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};for(var g in b){if(f==g){f=b[g]}}var e=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(i){return[parseInt(i[1]),parseInt(i[2]),parseInt(i[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(i){return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(i){return[parseInt(i[1]+i[1],16),parseInt(i[2]+i[2],16),parseInt(i[3]+i[3],16)]}}];for(var c=0;c<e.length;c++){var j=e[c].re;var a=e[c].process;var h=j.exec(f);if(h){var d=a(h);this.r=d[0];this.g=d[1];this.b=d[2];this.ok=true}}this.r=(this.r<0||isNaN(this.r))?0:((this.r>255)?255:this.r);this.g=(this.g<0||isNaN(this.g))?0:((this.g>255)?255:this.g);this.b=(this.b<0||isNaN(this.b))?0:((this.b>255)?255:this.b);this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var l=this.r.toString(16);var k=this.g.toString(16);var i=this.b.toString(16);if(l.length==1){l="0"+l}if(k.length==1){k="0"+k}if(i.length==1){i="0"+i}return"#"+l+k+i}}function printStackTrace(b){b=b||{guess:true};var c=b.e||null,e=!!b.guess;var d=new printStackTrace.implementation(),a=d.run(c);return(e)?d.guessAnonymousFunctions(a):a}printStackTrace.implementation=function(){};printStackTrace.implementation.prototype={run:function(a,b){a=a||this.createException();b=b||this.mode(a);if(b==="other"){return this.other(arguments.callee)}else{return this[b](a)}},createException:function(){try{this.undef()}catch(a){return a}},mode:function(a){if(a["arguments"]&&a.stack){return"chrome"}else{if(typeof a.message==="string"&&typeof window!=="undefined"&&window.opera){if(!a.stacktrace){return"opera9"}if(a.message.indexOf("\n")>-1&&a.message.split("\n").length>a.stacktrace.split("\n").length){return"opera9"}if(!a.stack){return"opera10a"}if(a.stacktrace.indexOf("called from line")<0){return"opera10b"}return"opera11"}else{if(a.stack){return"firefox"}}}return"other"},instrumentFunction:function(b,d,e){b=b||window;var a=b[d];b[d]=function c(){e.call(this,printStackTrace().slice(4));return b[d]._instrumented.apply(this,arguments)};b[d]._instrumented=a},deinstrumentFunction:function(a,b){if(a[b].constructor===Function&&a[b]._instrumented&&a[b]._instrumented.constructor===Function){a[b]=a[b]._instrumented}},chrome:function(b){var a=(b.stack+"\n").replace(/^\S[^\(]+?[\n$]/gm,"").replace(/^\s+at\s+/gm,"").replace(/^([^\(]+?)([\n$])/gm,"{anonymous}()@$1$2").replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm,"{anonymous}()@$1").split("\n");a.pop();return a},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^\(/gm,"{anonymous}(").split("\n")},opera11:function(g){var a="{anonymous}",h=/^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;var k=g.stacktrace.split("\n"),l=[];for(var c=0,f=k.length;c<f;c+=2){var d=h.exec(k[c]);if(d){var j=d[4]+":"+d[1]+":"+d[2];var b=d[3]||"global code";b=b.replace(/<anonymous function: (\S+)>/,"$1").replace(/<anonymous function>/,a);l.push(b+"@"+j+" -- "+k[c+1].replace(/^\s+/,""))}}return l},opera10b:function(g){var a="{anonymous}",h=/^(.*)@(.+):(\d+)$/;var j=g.stacktrace.split("\n"),k=[];for(var c=0,f=j.length;c<f;c++){var d=h.exec(j[c]);if(d){var b=d[1]?(d[1]+"()"):"global code";k.push(b+"@"+d[2]+":"+d[3])}}return k},opera10a:function(g){var a="{anonymous}",h=/Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;var j=g.stacktrace.split("\n"),k=[];for(var c=0,f=j.length;c<f;c+=2){var d=h.exec(j[c]);if(d){var b=d[3]||a;k.push(b+"()@"+d[2]+":"+d[1]+" -- "+j[c+1].replace(/^\s+/,""))}}return k},opera9:function(j){var d="{anonymous}",h=/Line (\d+).*script (?:in )?(\S+)/i;var c=j.message.split("\n"),b=[];for(var g=2,a=c.length;g<a;g+=2){var f=h.exec(c[g]);if(f){b.push(d+"()@"+f[2]+":"+f[1]+" -- "+c[g+1].replace(/^\s+/,""))}}return b},other:function(g){var b="{anonymous}",f=/function\s*([\w\-$]+)?\s*\(/i,a=[],d,c,e=10;while(g&&a.length<e){d=f.test(g.toString())?RegExp.$1||b:b;c=Array.prototype.slice.call(g["arguments"]||[]);a[a.length]=d+"("+this.stringifyArguments(c)+")";g=g.caller}return a},stringifyArguments:function(c){var b=[];var e=Array.prototype.slice;for(var d=0;d<c.length;++d){var a=c[d];if(a===undefined){b[d]="undefined"}else{if(a===null){b[d]="null"}else{if(a.constructor){if(a.constructor===Array){if(a.length<3){b[d]="["+this.stringifyArguments(a)+"]"}else{b[d]="["+this.stringifyArguments(e.call(a,0,1))+"..."+this.stringifyArguments(e.call(a,-1))+"]"}}else{if(a.constructor===Object){b[d]="#object"}else{if(a.constructor===Function){b[d]="#function"}else{if(a.constructor===String){b[d]='"'+a+'"'}else{if(a.constructor===Number){b[d]=a}}}}}}}}}return b.join(",")},sourceCache:{},ajax:function(a){var b=this.createXMLHTTPObject();if(b){try{b.open("GET",a,false);b.send(null);return b.responseText}catch(c){}}return""},createXMLHTTPObject:function(){var c,a=[function(){return new XMLHttpRequest()},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml3.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")}];for(var b=0;b<a.length;b++){try{c=a[b]();this.createXMLHTTPObject=a[b];return c}catch(d){}}},isSameDomain:function(a){return a.indexOf(location.hostname)!==-1},getSource:function(a){if(!(a in this.sourceCache)){this.sourceCache[a]=this.ajax(a).split("\n")}return this.sourceCache[a]},guessAnonymousFunctions:function(k){for(var g=0;g<k.length;++g){var f=/\{anonymous\}\(.*\)@(.*)/,l=/^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,b=k[g],c=f.exec(b);if(c){var e=l.exec(c[1]),d=e[1],a=e[2],j=e[3]||0;if(d&&this.isSameDomain(d)&&a){var h=this.guessAnonymousFunction(d,a,j);k[g]=b.replace("{anonymous}",h)}}}return k},guessAnonymousFunction:function(c,f,a){var b;try{b=this.findFunctionName(this.getSource(c),f)}catch(d){b="getSource failed with url: "+c+", exception: "+d.toString()}return b},findFunctionName:function(a,e){var g=/function\s+([^(]*?)\s*\(([^)]*)\)/;var k=/['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/;var h=/['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/;var b="",l,j=Math.min(e,20),d,c;for(var f=0;f<j;++f){l=a[e-f-1];c=l.indexOf("//");if(c>=0){l=l.substr(0,c)}if(l){b=l+b;d=k.exec(b);if(d&&d[1]){return d[1]}d=g.exec(b);if(d&&d[1]){return d[1]}d=h.exec(b);if(d&&d[1]){return d[1]}}}return"(?)"}};CanvasRenderingContext2D.prototype.installPattern=function(e){if(typeof(this.isPatternInstalled)!=="undefined"){throw"Must un-install old line pattern before installing a new one."}this.isPatternInstalled=true;var g=[0,0];var b=[];var f=this.beginPath;var d=this.lineTo;var c=this.moveTo;var a=this.stroke;this.uninstallPattern=function(){this.beginPath=f;this.lineTo=d;this.moveTo=c;this.stroke=a;this.uninstallPattern=undefined;this.isPatternInstalled=undefined};this.beginPath=function(){b=[];f.call(this)};this.moveTo=function(h,i){b.push([[h,i]]);c.call(this,h,i)};this.lineTo=function(h,j){var i=b[b.length-1];i.push([h,j])};this.stroke=function(){if(b.length===0){a.call(this);return}for(var p=0;p<b.length;p++){var o=b[p];var l=o[0][0],u=o[0][1];for(var n=1;n<o.length;n++){var h=o[n][0],s=o[n][1];this.save();var w=(h-l);var v=(s-u);var r=Math.sqrt(w*w+v*v);var k=Math.atan2(v,w);this.translate(l,u);c.call(this,0,0);this.rotate(k);var m=g[0];var t=0;while(r>t){var q=e[m];if(g[1]){t+=g[1]}else{t+=q}if(t>r){g=[m,t-r];t=r}else{g=[(m+1)%e.length,0]}if(m%2===0){d.call(this,t,0)}else{c.call(this,t,0)}m=(m+1)%e.length}this.restore();l=h,u=s}}a.call(this);b=[]}};CanvasRenderingContext2D.prototype.uninstallPattern=function(){throw"Must install a line pattern before uninstalling it."};var DygraphOptions=(function(){var a=function(b){this.dygraph_=b;this.yAxes_=[];this.xAxis_={};this.series_={};this.global_=this.dygraph_.attrs_;this.user_=this.dygraph_.user_attrs_||{};this.labels_=[];this.highlightSeries_=this.get("highlightSeriesOpts")||{};this.reparseSeries()};a.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1};a.axisToIndex_=function(b){if(typeof(b)=="string"){if(a.AXIS_STRING_MAPPINGS_.hasOwnProperty(b)){return a.AXIS_STRING_MAPPINGS_[b]}throw"Unknown axis : "+b}if(typeof(b)=="number"){if(b===0||b===1){return b}throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(b){throw"Unknown axis : "+b}return 0};a.prototype.reparseSeries=function(){var g=this.get("labels");if(!g){return}this.labels_=g.slice(1);this.yAxes_=[{series:[],options:{}}];this.xAxis_={options:{}};this.series_={};var h=!this.user_.series;if(h){var c=0;for(var j=0;j<this.labels_.length;j++){var i=this.labels_[j];var e=this.user_[i]||{};var b=0;var d=e.axis;if(typeof(d)=="object"){b=++c;this.yAxes_[b]={series:[i],options:d}}if(!d){this.yAxes_[0].series.push(i)}this.series_[i]={idx:j,yAxis:b,options:e}}for(var j=0;j<this.labels_.length;j++){var i=this.labels_[j];var e=this.series_[i]["options"];var d=e.axis;if(typeof(d)=="string"){if(!this.series_.hasOwnProperty(d)){Dygraph.error("Series "+i+" wants to share a y-axis with series "+d+", which does not define its own axis.");return}var b=this.series_[d].yAxis;this.series_[i].yAxis=b;this.yAxes_[b].series.push(i)}}}else{for(var j=0;j<this.labels_.length;j++){var i=this.labels_[j];var e=this.user_.series[i]||{};var b=a.axisToIndex_(e.axis);this.series_[i]={idx:j,yAxis:b,options:e};if(!this.yAxes_[b]){this.yAxes_[b]={series:[i],options:{}}}else{this.yAxes_[b].series.push(i)}}}var f=this.user_.axes||{};Dygraph.update(this.yAxes_[0].options,f.y||{});if(this.yAxes_.length>1){Dygraph.update(this.yAxes_[1].options,f.y2||{})}Dygraph.update(this.xAxis_.options,f.x||{})};a.prototype.get=function(c){var b=this.getGlobalUser_(c);if(b!==null){return b}return this.getGlobalDefault_(c)};a.prototype.getGlobalUser_=function(b){if(this.user_.hasOwnProperty(b)){return this.user_[b]}return null};a.prototype.getGlobalDefault_=function(b){if(this.global_.hasOwnProperty(b)){return this.global_[b]}if(Dygraph.DEFAULT_ATTRS.hasOwnProperty(b)){return Dygraph.DEFAULT_ATTRS[b]}return null};a.prototype.getForAxis=function(d,e){var f;var i;if(typeof(e)=="number"){f=e;i=f===0?"y":"y2"}else{if(e=="y1"){e="y"}if(e=="y"){f=0}else{if(e=="y2"){f=1}else{if(e=="x"){f=-1}else{throw"Unknown axis "+e}}}i=e}var g=(f==-1)?this.xAxis_:this.yAxes_[f];if(g){var h=g.options;if(h.hasOwnProperty(d)){return h[d]}}var c=this.getGlobalUser_(d);if(c!==null){return c}var b=Dygraph.DEFAULT_ATTRS.axes[i];if(b.hasOwnProperty(d)){return b[d]}return this.getGlobalDefault_(d)};a.prototype.getForSeries=function(c,e){if(e===this.dygraph_.getHighlightSeries()){if(this.highlightSeries_.hasOwnProperty(c)){return this.highlightSeries_[c]}}if(!this.series_.hasOwnProperty(e)){throw"Unknown series: "+e}var d=this.series_[e];var b=d.options;if(b.hasOwnProperty(c)){return b[c]}return this.getForAxis(c,d.yAxis)};a.prototype.numAxes=function(){return this.yAxes_.length};a.prototype.axisForSeries=function(b){return this.series_[b].yAxis};a.prototype.axisOptions=function(b){return this.yAxes_[b].options};a.prototype.seriesForAxis=function(b){return this.yAxes_[b].series};a.prototype.seriesNames=function(){return this.labels_};return a})();"use strict";var DygraphLayout=function(a){this.dygraph_=a;this.points=[];this.setNames=[];this.annotations=[];this.yAxes_=null;this.xTicks_=null;this.yTicks_=null};DygraphLayout.prototype.attr_=function(a){return this.dygraph_.attr_(a)};DygraphLayout.prototype.addDataset=function(a,b){this.points.push(b);this.setNames.push(a)};DygraphLayout.prototype.getPlotArea=function(){return this.area_};DygraphLayout.prototype.computePlotArea=function(){var a={x:0,y:0};a.w=this.dygraph_.width_-a.x-this.attr_("rightGap");a.h=this.dygraph_.height_;var b={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(c){var d={x:a.x,y:a.y,w:c,h:a.h};a.x+=c;a.w-=c;return d},reserveSpaceRight:function(c){var d={x:a.x+a.w-c,y:a.y,w:c,h:a.h};a.w-=c;return d},reserveSpaceTop:function(c){var d={x:a.x,y:a.y,w:a.w,h:c};a.y+=c;a.h-=c;return d},reserveSpaceBottom:function(c){var d={x:a.x,y:a.y+a.h-c,w:a.w,h:c};a.h-=c;return d},chartRect:function(){return{x:a.x,y:a.y,w:a.w,h:a.h}}};this.dygraph_.cascadeEvents_("layout",b);this.area_=a};DygraphLayout.prototype.setAnnotations=function(d){this.annotations=[];var e=this.attr_("xValueParser")||function(a){return a};for(var c=0;c<d.length;c++){var b={};if(!d[c].xval&&d[c].x===undefined){this.dygraph_.error("Annotations must have an 'x' property");return}if(d[c].icon&&!(d[c].hasOwnProperty("width")&&d[c].hasOwnProperty("height"))){this.dygraph_.error("Must set width and height when setting annotation.icon property");return}Dygraph.update(b,d[c]);if(!b.xval){b.xval=e(b.x)}this.annotations.push(b)}};DygraphLayout.prototype.setXTicks=function(a){this.xTicks_=a};DygraphLayout.prototype.setYAxes=function(a){this.yAxes_=a};DygraphLayout.prototype.evaluate=function(){this._evaluateLimits();this._evaluateLineCharts();this._evaluateLineTicks();this._evaluateAnnotations()};DygraphLayout.prototype._evaluateLimits=function(){var a=this.dygraph_.xAxisRange();this.minxval=a[0];this.maxxval=a[1];var d=a[1]-a[0];this.xscale=(d!==0?1/d:1);for(var b=0;b<this.yAxes_.length;b++){var c=this.yAxes_[b];c.minyval=c.computedValueRange[0];c.maxyval=c.computedValueRange[1];c.yrange=c.maxyval-c.minyval;c.yscale=(c.yrange!==0?1/c.yrange:1);if(c.g.attr_("logscale")){c.ylogrange=Dygraph.log10(c.maxyval)-Dygraph.log10(c.minyval);c.ylogscale=(c.ylogrange!==0?1/c.ylogrange:1);if(!isFinite(c.ylogrange)||isNaN(c.ylogrange)){c.g.error("axis "+b+" of graph at "+c.g+" can't be displayed in log scale for range ["+c.minyval+" - "+c.maxyval+"]")}}}};DygraphLayout._calcYNormal=function(b,c,a){if(a){return 1-((Dygraph.log10(c)-Dygraph.log10(b.minyval))*b.ylogscale)}else{return 1-((c-b.minyval)*b.yscale)}};DygraphLayout.prototype._evaluateLineCharts=function(){var c=this.attr_("connectSeparatedPoints");var k=this.attr_("stackedGraph");var e=this.attr_("errorBars")||this.attr_("customBars");for(var a=0;a<this.points.length;a++){var l=this.points[a];var f=this.setNames[a];var b=this.dygraph_.axisPropertiesForSeries(f);var g=this.dygraph_.attributes_.getForSeries("logscale",f);for(var d=0;d<l.length;d++){var i=l[d];i.x=(i.xval-this.minxval)*this.xscale;var h=i.yval;if(k){i.y_stacked=DygraphLayout._calcYNormal(b,i.yval_stacked,g);if(h!==null&&!isNaN(h)){h=i.yval_stacked}}if(h===null){h=NaN;if(!c){i.yval=NaN}}i.y=DygraphLayout._calcYNormal(b,h,g);if(e){i.y_top=DygraphLayout._calcYNormal(b,h-i.yval_minus,g);i.y_bottom=DygraphLayout._calcYNormal(b,h+i.yval_plus,g)}}}};DygraphLayout.parseFloat_=function(a){if(a===null){return NaN}return a};DygraphLayout.prototype._evaluateLineTicks=function(){var d,c,b,f;this.xticks=[];for(d=0;d<this.xTicks_.length;d++){c=this.xTicks_[d];b=c.label;f=this.xscale*(c.v-this.minxval);if((f>=0)&&(f<=1)){this.xticks.push([f,b])}}this.yticks=[];for(d=0;d<this.yAxes_.length;d++){var e=this.yAxes_[d];for(var a=0;a<e.ticks.length;a++){c=e.ticks[a];b=c.label;f=this.dygraph_.toPercentYCoord(c.v,d);if((f>=0)&&(f<=1)){this.yticks.push([d,f,b])}}}};DygraphLayout.prototype._evaluateAnnotations=function(){var d;var g={};for(d=0;d<this.annotations.length;d++){var b=this.annotations[d];g[b.xval+","+b.series]=b}this.annotated_points=[];if(!this.annotations||!this.annotations.length){return}for(var h=0;h<this.points.length;h++){var e=this.points[h];for(d=0;d<e.length;d++){var f=e[d];var c=f.xval+","+f.name;if(c in g){f.annotation=g[c];this.annotated_points.push(f)}}}};DygraphLayout.prototype.removeAllDatasets=function(){delete this.points;delete this.setNames;delete this.setPointsLengths;delete this.setPointsOffsets;this.points=[];this.setNames=[];this.setPointsLengths=[];this.setPointsOffsets=[]};"use strict";var DygraphCanvasRenderer=function(d,c,b,e){this.dygraph_=d;this.layout=e;this.element=c;this.elementContext=b;this.container=this.element.parentNode;this.height=this.element.height;this.width=this.element.width;if(!this.isIE&&!(DygraphCanvasRenderer.isSupported(this.element))){throw"Canvas is not supported."}this.area=e.getPlotArea();this.container.style.position="relative";this.container.style.width=this.width+"px";if(this.dygraph_.isUsingExcanvas_){this._createIEClipArea()}else{if(!Dygraph.isAndroid()){var a=this.dygraph_.canvas_ctx_;a.beginPath();a.rect(this.area.x,this.area.y,this.area.w,this.area.h);a.clip();a=this.dygraph_.hidden_ctx_;a.beginPath();a.rect(this.area.x,this.area.y,this.area.w,this.area.h);a.clip()}}};DygraphCanvasRenderer.prototype.attr_=function(a,b){return this.dygraph_.attr_(a,b)};DygraphCanvasRenderer.prototype.clear=function(){var a;if(this.isIE){try{if(this.clearDelay){this.clearDelay.cancel();this.clearDelay=null}a=this.elementContext}catch(b){return}}a=this.elementContext;a.clearRect(0,0,this.width,this.height)};DygraphCanvasRenderer.isSupported=function(f){var b=null;try{if(typeof(f)=="undefined"||f===null){b=document.createElement("canvas")}else{b=f}b.getContext("2d")}catch(c){var d=navigator.appVersion.match(/MSIE (\d\.\d)/);var a=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);if((!d)||(d[1]<6)||(a)){return false}return true}return true};DygraphCanvasRenderer.prototype.render=function(){this._updatePoints();this._renderLineChart()};DygraphCanvasRenderer.prototype._createIEClipArea=function(){var g="dygraph-clip-div";var f=this.dygraph_.graphDiv;for(var e=f.childNodes.length-1;e>=0;e--){if(f.childNodes[e].className==g){f.removeChild(f.childNodes[e])}}var c=document.bgColor;var d=this.dygraph_.graphDiv;while(d!=document){var a=d.currentStyle.backgroundColor;if(a&&a!="transparent"){c=a;break}d=d.parentNode}function b(j){if(j.w===0||j.h===0){return}var i=document.createElement("div");i.className=g;i.style.backgroundColor=c;i.style.position="absolute";i.style.left=j.x+"px";i.style.top=j.y+"px";i.style.width=j.w+"px";i.style.height=j.h+"px";f.appendChild(i)}var h=this.area;b({x:0,y:0,w:h.x,h:this.height});b({x:h.x,y:0,w:this.width-h.x,h:h.y});b({x:h.x+h.w,y:0,w:this.width-h.x-h.w,h:this.height});b({x:h.x,y:h.y+h.h,w:this.width-h.x,h:this.height-h.h-h.y})};DygraphCanvasRenderer._getIteratorPredicate=function(a){return a?DygraphCanvasRenderer._predicateThatSkipsEmptyPoints:null};DygraphCanvasRenderer._predicateThatSkipsEmptyPoints=function(b,a){return b[a].yval!==null};DygraphCanvasRenderer._drawStyledLine=function(i,a,m,q,f,n,d){var h=i.dygraph;var c=h.getOption("stepPlot",i.setName);if(!Dygraph.isArrayLike(q)){q=null}var l=h.getOption("drawGapEdgePoints",i.setName);var o=i.points;var k=Dygraph.createIterator(o,0,o.length,DygraphCanvasRenderer._getIteratorPredicate(h.getOption("connectSeparatedPoints")));var j=q&&(q.length>=2);var p=i.drawingContext;p.save();if(j){p.installPattern(q)}var b=DygraphCanvasRenderer._drawSeries(i,k,m,d,f,l,c,a);DygraphCanvasRenderer._drawPointsOnLine(i,b,n,a,d);if(j){p.uninstallPattern()}p.restore()};DygraphCanvasRenderer._drawSeries=function(v,t,m,h,p,s,g,q){var b=null;var w=null;var k=null;var j;var o;var l=[];var f=true;var n=v.drawingContext;n.beginPath();n.strokeStyle=q;n.lineWidth=m;var c=t.array_;var u=t.end_;var a=t.predicate_;for(var r=t.start_;r<u;r++){o=c[r];if(a){while(r<u&&!a(c,r)){r++}if(r==u){break}o=c[r]}if(o.canvasy===null||o.canvasy!=o.canvasy){if(g&&b!==null){n.moveTo(b,w);n.lineTo(o.canvasx,w)}b=w=null}else{j=false;if(s||!b){t.nextIdx_=r;t.next();k=t.hasNext?t.peek.canvasy:null;var d=k===null||k!=k;j=(!b&&d);if(s){if((!f&&!b)||(t.hasNext&&d)){j=true}}}if(b!==null){if(m){if(g){n.moveTo(b,w);n.lineTo(o.canvasx,w)}n.lineTo(o.canvasx,o.canvasy)}}else{n.moveTo(o.canvasx,o.canvasy)}if(p||j){l.push([o.canvasx,o.canvasy,o.idx])}b=o.canvasx;w=o.canvasy}f=false}n.stroke();return l};DygraphCanvasRenderer._drawPointsOnLine=function(h,i,f,d,g){var c=h.drawingContext;for(var b=0;b<i.length;b++){var a=i[b];c.save();f(h.dygraph,h.setName,c,a[0],a[1],d,g,a[2]);c.restore()}};DygraphCanvasRenderer.prototype._updatePoints=function(){var e=this.layout.points;for(var c=e.length;c--;){var d=e[c];for(var b=d.length;b--;){var a=d[b];a.canvasx=this.area.w*a.x+this.area.x;a.canvasy=this.area.h*a.y+this.area.y}}};DygraphCanvasRenderer.prototype._renderLineChart=function(g,u){var h=u||this.elementContext;var n;var a=this.layout.points;var s=this.layout.setNames;var b;this.colors=this.dygraph_.colorsMap_;var o=this.attr_("plotter");var f=o;if(!Dygraph.isArrayLike(f)){f=[f]}var c={};for(n=0;n<s.length;n++){b=s[n];var t=this.attr_("plotter",b);if(t==o){continue}c[b]=t}for(n=0;n<f.length;n++){var r=f[n];var q=(n==f.length-1);for(var l=0;l<a.length;l++){b=s[l];if(g&&b!=g){continue}var m=a[l];var e=r;if(b in c){if(q){e=c[b]}else{continue}}var k=this.colors[b];var d=this.dygraph_.getOption("strokeWidth",b);h.save();h.strokeStyle=k;h.lineWidth=d;e({points:m,setName:b,drawingContext:h,color:k,strokeWidth:d,dygraph:this.dygraph_,axis:this.dygraph_.axisPropertiesForSeries(b),plotArea:this.area,seriesIndex:l,seriesCount:a.length,singleSeriesName:g,allSeriesPoints:a});h.restore()}}};DygraphCanvasRenderer._Plotters={linePlotter:function(a){DygraphCanvasRenderer._linePlotter(a)},fillPlotter:function(a){DygraphCanvasRenderer._fillPlotter(a)},errorPlotter:function(a){DygraphCanvasRenderer._errorPlotter(a)}};DygraphCanvasRenderer._linePlotter=function(f){var d=f.dygraph;var h=f.setName;var i=f.strokeWidth;var a=d.getOption("strokeBorderWidth",h);var j=d.getOption("drawPointCallback",h)||Dygraph.Circles.DEFAULT;var k=d.getOption("strokePattern",h);var c=d.getOption("drawPoints",h);var b=d.getOption("pointSize",h);if(a&&i){DygraphCanvasRenderer._drawStyledLine(f,d.getOption("strokeBorderColor",h),i+2*a,k,c,j,b)}DygraphCanvasRenderer._drawStyledLine(f,f.color,i,k,c,j,b)};DygraphCanvasRenderer._errorPlotter=function(s){var r=s.dygraph;var f=s.setName;var t=r.getOption("errorBars")||r.getOption("customBars");if(!t){return}var k=r.getOption("fillGraph",f);if(k){r.warn("Can't use fillGraph option with error bars")}var m=s.drawingContext;var n=s.color;var o=r.getOption("fillAlpha",f);var i=r.getOption("stepPlot",f);var p=s.points;var q=Dygraph.createIterator(p,0,p.length,DygraphCanvasRenderer._getIteratorPredicate(r.getOption("connectSeparatedPoints")));var j;var h=NaN;var c=NaN;var d=[-1,-1];var a=new RGBColorParser(n);var u="rgba("+a.r+","+a.g+","+a.b+","+o+")";m.fillStyle=u;m.beginPath();var b=function(e){return(e===null||e===undefined||isNaN(e))};while(q.hasNext){var l=q.next();if((!i&&b(l.y))||(i&&!isNaN(c)&&b(c))){h=NaN;continue}if(i){j=[l.y_bottom,l.y_top];c=l.y}else{j=[l.y_bottom,l.y_top]}j[0]=s.plotArea.h*j[0]+s.plotArea.y;j[1]=s.plotArea.h*j[1]+s.plotArea.y;if(!isNaN(h)){if(i){m.moveTo(h,d[0]);m.lineTo(l.canvasx,d[0]);m.lineTo(l.canvasx,d[1])}else{m.moveTo(h,d[0]);m.lineTo(l.canvasx,j[0]);m.lineTo(l.canvasx,j[1])}m.lineTo(h,d[1]);m.closePath()}d=j;h=l.canvasx}m.fill()};DygraphCanvasRenderer._fillPlotter=function(F){if(F.singleSeriesName){return}if(F.seriesIndex!==0){return}var D=F.dygraph;var I=D.getLabels().slice(1);for(var C=I.length;C>=0;C--){if(!D.visibility()[C]){I.splice(C,1)}}var h=(function(){for(var e=0;e<I.length;e++){if(D.getOption("fillGraph",I[e])){return true}}return false})();if(!h){return}var w=F.drawingContext;var E=F.plotArea;var c=F.allSeriesPoints;var z=c.length;var y=D.getOption("fillAlpha");var k=D.getOption("stackedGraph");var q=D.getColors();var s={};var a;var r;for(var u=z-1;u>=0;u--){var n=I[u];if(!D.getOption("fillGraph",n)){continue}var p=D.getOption("stepPlot",n);var x=q[u];var f=D.axisPropertiesForSeries(n);var d=1+f.minyval*f.yscale;if(d<0){d=0}else{if(d>1){d=1}}d=E.h*d+E.y;var B=c[u];var A=Dygraph.createIterator(B,0,B.length,DygraphCanvasRenderer._getIteratorPredicate(D.getOption("connectSeparatedPoints")));var m=NaN;var l=[-1,-1];var t;var b=new RGBColorParser(x);var H="rgba("+b.r+","+b.g+","+b.b+","+y+")";w.fillStyle=H;w.beginPath();var j,G=true;while(A.hasNext){var v=A.next();if(!Dygraph.isOK(v.y)){m=NaN;if(v.y_stacked!==null&&!isNaN(v.y_stacked)){s[v.canvasx]=E.h*v.y_stacked+E.y}continue}if(k){if(!G&&j==v.xval){continue}else{G=false;j=v.xval}a=s[v.canvasx];var o;if(a===undefined){o=d}else{if(r){o=a[0]}else{o=a}}t=[v.canvasy,o];if(p){if(l[0]===-1){s[v.canvasx]=[v.canvasy,d]}else{s[v.canvasx]=[v.canvasy,l[0]]}}else{s[v.canvasx]=v.canvasy}}else{t=[v.canvasy,d]}if(!isNaN(m)){w.moveTo(m,l[0]);if(p){w.lineTo(v.canvasx,l[0])}else{w.lineTo(v.canvasx,t[0])}if(r&&a){w.lineTo(v.canvasx,a[1])}else{w.lineTo(v.canvasx,t[1])}w.lineTo(m,l[1]);w.closePath()}l=t;m=v.canvasx}r=p;w.fill()}};"use strict";var Dygraph=function(d,c,b,a){this.is_initial_draw_=true;this.readyFns_=[];if(a!==undefined){this.warn("Using deprecated four-argument dygraph constructor");this.__old_init__(d,c,b,a)}else{this.__init__(d,c,b)}};Dygraph.NAME="Dygraph";Dygraph.VERSION="1.0.0";Dygraph.__repr__=function(){return"["+this.NAME+" "+this.VERSION+"]"};Dygraph.toString=function(){return this.__repr__()};Dygraph.DEFAULT_ROLL_PERIOD=1;Dygraph.DEFAULT_WIDTH=480;Dygraph.DEFAULT_HEIGHT=320;Dygraph.ANIMATION_STEPS=12;Dygraph.ANIMATION_DURATION=200;Dygraph.KMB_LABELS=["K","M","B","T","Q"];Dygraph.KMG2_BIG_LABELS=["k","M","G","T","P","E","Z","Y"];Dygraph.KMG2_SMALL_LABELS=["m","u","n","p","f","a","z","y"];Dygraph.numberValueFormatter=function(s,a,u,l){var e=a("sigFigs");if(e!==null){return Dygraph.floatFormat(s,e)}var c=a("digitsAfterDecimal");var o=a("maxNumberWidth");var b=a("labelsKMB");var r=a("labelsKMG2");var q;if(s!==0&&(Math.abs(s)>=Math.pow(10,o)||Math.abs(s)<Math.pow(10,-c))){q=s.toExponential(c)}else{q=""+Dygraph.round_(s,c)}if(b||r){var f;var t=[];var m=[];if(b){f=1000;t=Dygraph.KMB_LABELS}if(r){if(b){Dygraph.warn("Setting both labelsKMB and labelsKMG2. Pick one!")}f=1024;t=Dygraph.KMG2_BIG_LABELS;m=Dygraph.KMG2_SMALL_LABELS}var p=Math.abs(s);var d=Dygraph.pow(f,t.length);for(var h=t.length-1;h>=0;h--,d/=f){if(p>=d){q=Dygraph.round_(s/d,c)+t[h];break}}if(r){var i=String(s.toExponential()).split("e-");if(i.length===2&&i[1]>=3&&i[1]<=24){if(i[1]%3>0){q=Dygraph.round_(i[0]/Dygraph.pow(10,(i[1]%3)),c)}else{q=Number(i[0]).toFixed(2)}q+=m[Math.floor(i[1]/3)-1]}}}return q};Dygraph.numberAxisLabelFormatter=function(a,d,c,b){return Dygraph.numberValueFormatter(a,c,b)};Dygraph.dateString_=function(e){var i=Dygraph.zeropad;var h=new Date(e);var f=""+h.getFullYear();var g=i(h.getMonth()+1);var a=i(h.getDate());var c="";var b=h.getHours()*3600+h.getMinutes()*60+h.getSeconds();if(b){c=" "+Dygraph.hmsString_(e)}return f+"/"+g+"/"+a+c};Dygraph.dateAxisFormatter=function(b,c){if(c>=Dygraph.DECADAL){return b.strftime("%Y")}else{if(c>=Dygraph.MONTHLY){return b.strftime("%b %y")}else{var a=b.getHours()*3600+b.getMinutes()*60+b.getSeconds()+b.getMilliseconds();if(a===0||c>=Dygraph.DAILY){return new Date(b.getTime()+3600*1000).strftime("%d%b")}else{return Dygraph.hmsString_(b.getTime())}}}};Dygraph.Plotters=DygraphCanvasRenderer._Plotters;Dygraph.DEFAULT_ATTRS={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:0.5,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:false,labelsShowZeroValues:true,labelsKMB:false,labelsKMG2:false,showLabelsOnHighlight:true,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,xAxisLabelWidth:50,yAxisLabelWidth:50,rightGap:5,showRoller:false,xValueParser:Dygraph.dateParser,delimiter:",",sigma:2,errorBars:false,fractions:false,wilsonInterval:true,customBars:false,fillGraph:false,fillAlpha:0.15,connectSeparatedPoints:false,stackedGraph:false,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:true,legend:"onmouseover",stepPlot:false,avoidMinZero:false,xRangePad:0,yRangePad:null,drawAxesAtZero:false,titleHeight:28,xLabelHeight:18,yLabelWidth:18,drawXAxis:true,drawYAxis:true,axisLineColor:"black",axisLineWidth:0.3,gridLineWidth:0.3,axisLabelColor:"black",axisLabelFont:"Arial",axisLabelWidth:50,drawYGrid:true,drawXGrid:true,gridLineColor:"rgb(128,128,128)",interactionModel:null,animatedZooms:false,showRangeSelector:false,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillColor:"#A7B1C4",plotter:[Dygraph.Plotters.fillPlotter,Dygraph.Plotters.errorPlotter,Dygraph.Plotters.linePlotter],plugins:[],axes:{x:{pixelsPerLabel:60,axisLabelFormatter:Dygraph.dateAxisFormatter,valueFormatter:Dygraph.dateString_,drawGrid:true,independentTicks:true,ticker:null},y:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:true,independentTicks:true,ticker:null},y2:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:false,independentTicks:false,ticker:null}}};Dygraph.HORIZONTAL=1;Dygraph.VERTICAL=2;Dygraph.PLUGINS=[];Dygraph.addedAnnotationCSS=false;Dygraph.prototype.__old_init__=function(f,d,e,b){if(e!==null){var a=["Date"];for(var c=0;c<e.length;c++){a.push(e[c])}Dygraph.update(b,{labels:a})}this.__init__(f,d,b)};Dygraph.prototype.__init__=function(a,c,l){if(/MSIE/.test(navigator.userAgent)&&!window.opera&&typeof(G_vmlCanvasManager)!="undefined"&&document.readyState!="complete"){var o=this;setTimeout(function(){o.__init__(a,c,l)},100);return}if(l===null||l===undefined){l={}}l=Dygraph.mapLegacyOptions_(l);if(typeof(a)=="string"){a=document.getElementById(a)}if(!a){Dygraph.error("Constructing dygraph with a non-existent div!");return}this.isUsingExcanvas_=typeof(G_vmlCanvasManager)!="undefined";this.maindiv_=a;this.file_=c;this.rollPeriod_=l.rollPeriod||Dygraph.DEFAULT_ROLL_PERIOD;this.previousVerticalX_=-1;this.fractions_=l.fractions||false;this.dateWindow_=l.dateWindow||null;this.annotations_=[];this.zoomed_x_=false;this.zoomed_y_=false;a.innerHTML="";if(a.style.width===""&&l.width){a.style.width=l.width+"px"}if(a.style.height===""&&l.height){a.style.height=l.height+"px"}if(a.style.height===""&&a.clientHeight===0){a.style.height=Dygraph.DEFAULT_HEIGHT+"px";if(a.style.width===""){a.style.width=Dygraph.DEFAULT_WIDTH+"px"}}this.width_=a.clientWidth||l.width||0;this.height_=a.clientHeight||l.height||0;if(l.stackedGraph){l.fillGraph=true}this.user_attrs_={};Dygraph.update(this.user_attrs_,l);this.attrs_={};Dygraph.updateDeep(this.attrs_,Dygraph.DEFAULT_ATTRS);this.boundaryIds_=[];this.setIndexByName_={};this.datasetIndex_=[];this.registeredEvents_=[];this.eventListeners_={};this.attributes_=new DygraphOptions(this);this.createInterface_();this.plugins_=[];var d=Dygraph.PLUGINS.concat(this.getOption("plugins"));for(var g=0;g<d.length;g++){var k=d[g];var f=new k();var j={plugin:f,events:{},options:{},pluginOptions:{}};var b=f.activate(this);for(var h in b){j.events[h]=b[h]}this.plugins_.push(j)}for(var g=0;g<this.plugins_.length;g++){var n=this.plugins_[g];for(var h in n.events){if(!n.events.hasOwnProperty(h)){continue}var m=n.events[h];var e=[n.plugin,m];if(!(h in this.eventListeners_)){this.eventListeners_[h]=[e]}else{this.eventListeners_[h].push(e)}}}this.createDragInterface_();this.start_()};Dygraph.prototype.cascadeEvents_=function(c,b){if(!(c in this.eventListeners_)){return true}var g={dygraph:this,cancelable:false,defaultPrevented:false,preventDefault:function(){if(!g.cancelable){throw"Cannot call preventDefault on non-cancelable event."}g.defaultPrevented=true},propagationStopped:false,stopPropagation:function(){g.propagationStopped=true}};Dygraph.update(g,b);var a=this.eventListeners_[c];if(a){for(var d=a.length-1;d>=0;d--){var f=a[d][0];var h=a[d][1];h.call(f,g);if(g.propagationStopped){break}}}return g.defaultPrevented};Dygraph.prototype.isZoomed=function(a){if(a===null||a===undefined){return this.zoomed_x_||this.zoomed_y_}if(a==="x"){return this.zoomed_x_}if(a==="y"){return this.zoomed_y_}throw"axis parameter is ["+a+"] must be null, 'x' or 'y'."};Dygraph.prototype.toString=function(){var a=this.maindiv_;var b=(a&&a.id)?a.id:a;return"[Dygraph "+b+"]"};Dygraph.prototype.attr_=function(b,a){return a?this.attributes_.getForSeries(b,a):this.attributes_.get(b)};Dygraph.prototype.getOption=function(a,b){return this.attr_(a,b)};Dygraph.prototype.getOptionForAxis=function(a,b){return this.attributes_.getForAxis(a,b)};Dygraph.prototype.optionsViewForAxis_=function(b){var a=this;return function(c){var d=a.user_attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(typeof(a.user_attrs_[c])!="undefined"){return a.user_attrs_[c]}d=a.attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(b=="y"&&a.axes_[0].hasOwnProperty(c)){return a.axes_[0][c]}else{if(b=="y2"&&a.axes_[1].hasOwnProperty(c)){return a.axes_[1][c]}}return a.attr_(c)}};Dygraph.prototype.rollPeriod=function(){return this.rollPeriod_};Dygraph.prototype.xAxisRange=function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes()};Dygraph.prototype.xAxisExtremes=function(){var d=this.attr_("xRangePad")/this.plotter_.area.w;if(this.numRows()===0){return[0-d,1+d]}var c=this.rawData_[0][0];var b=this.rawData_[this.rawData_.length-1][0];if(d){var a=b-c;c-=a*d;b+=a*d}return[c,b]};Dygraph.prototype.yAxisRange=function(a){if(typeof(a)=="undefined"){a=0}if(a<0||a>=this.axes_.length){return null}var b=this.axes_[a];return[b.computedValueRange[0],b.computedValueRange[1]]};Dygraph.prototype.yAxisRanges=function(){var a=[];for(var b=0;b<this.axes_.length;b++){a.push(this.yAxisRange(b))}return a};Dygraph.prototype.toDomCoords=function(a,c,b){return[this.toDomXCoord(a),this.toDomYCoord(c,b)]};Dygraph.prototype.toDomXCoord=function(b){if(b===null){return null}var c=this.plotter_.area;var a=this.xAxisRange();return c.x+(b-a[0])/(a[1]-a[0])*c.w};Dygraph.prototype.toDomYCoord=function(d,a){var c=this.toPercentYCoord(d,a);if(c===null){return null}var b=this.plotter_.area;return b.y+c*b.h};Dygraph.prototype.toDataCoords=function(a,c,b){return[this.toDataXCoord(a),this.toDataYCoord(c,b)]};Dygraph.prototype.toDataXCoord=function(b){if(b===null){return null}var c=this.plotter_.area;var a=this.xAxisRange();return a[0]+(b-c.x)/c.w*(a[1]-a[0])};Dygraph.prototype.toDataYCoord=function(h,b){if(h===null){return null}var c=this.plotter_.area;var g=this.yAxisRange(b);if(typeof(b)=="undefined"){b=0}if(!this.axes_[b].logscale){return g[0]+(c.y+c.h-h)/c.h*(g[1]-g[0])}else{var f=(h-c.y)/c.h;var a=Dygraph.log10(g[1]);var e=a-(f*(a-Dygraph.log10(g[0])));var d=Math.pow(Dygraph.LOG_SCALE,e);return d}};Dygraph.prototype.toPercentYCoord=function(f,c){if(f===null){return null}if(typeof(c)=="undefined"){c=0}var e=this.yAxisRange(c);var d;var b=this.attributes_.getForAxis("logscale",c);if(!b){d=(e[1]-f)/(e[1]-e[0])}else{var a=Dygraph.log10(e[1]);d=(a-Dygraph.log10(f))/(a-Dygraph.log10(e[0]))}return d};Dygraph.prototype.toPercentXCoord=function(b){if(b===null){return null}var a=this.xAxisRange();return(b-a[0])/(a[1]-a[0])};Dygraph.prototype.numColumns=function(){if(!this.rawData_){return 0}return this.rawData_[0]?this.rawData_[0].length:this.attr_("labels").length};Dygraph.prototype.numRows=function(){if(!this.rawData_){return 0}return this.rawData_.length};Dygraph.prototype.getValue=function(b,a){if(b<0||b>this.rawData_.length){return null}if(a<0||a>this.rawData_[b].length){return null}return this.rawData_[b][a]};Dygraph.prototype.createInterface_=function(){var a=this.maindiv_;this.graphDiv=document.createElement("div");this.graphDiv.style.textAlign="left";a.appendChild(this.graphDiv);this.canvas_=Dygraph.createCanvas();this.canvas_.style.position="absolute";this.hidden_=this.createPlotKitCanvas_(this.canvas_);this.resizeElements_();this.canvas_ctx_=Dygraph.getContext(this.canvas_);this.hidden_ctx_=Dygraph.getContext(this.hidden_);this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_=this.createMouseEventElement_();this.layout_=new DygraphLayout(this);var b=this;this.mouseMoveHandler_=function(c){b.mouseMove_(c)};this.mouseOutHandler_=function(f){var d=f.target||f.fromElement;var c=f.relatedTarget||f.toElement;if(Dygraph.isNodeContainedBy(d,b.graphDiv)&&!Dygraph.isNodeContainedBy(c,b.graphDiv)){b.mouseOut_(f)}};this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_);this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);if(!this.resizeHandler_){this.resizeHandler_=function(c){b.resize()};this.addAndTrackEvent(window,"resize",this.resizeHandler_)}};Dygraph.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px";this.graphDiv.style.height=this.height_+"px";this.canvas_.width=this.width_;this.canvas_.height=this.height_;this.canvas_.style.width=this.width_+"px";this.canvas_.style.height=this.height_+"px";this.hidden_.width=this.width_;this.hidden_.height=this.height_;this.hidden_.style.width=this.width_+"px";this.hidden_.style.height=this.height_+"px"};Dygraph.prototype.destroy=function(){this.canvas_ctx_.restore();this.hidden_ctx_.restore();var a=function(c){while(c.hasChildNodes()){a(c.firstChild);c.removeChild(c.firstChild)}};this.removeTrackedEvents_();Dygraph.removeEvent(window,"mouseout",this.mouseOutHandler_);Dygraph.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);Dygraph.removeEvent(window,"resize",this.resizeHandler_);this.resizeHandler_=null;a(this.maindiv_);var b=function(c){for(var d in c){if(typeof(c[d])==="object"){c[d]=null}}};b(this.layout_);b(this.plotter_);b(this)};Dygraph.prototype.createPlotKitCanvas_=function(a){var b=Dygraph.createCanvas();b.style.position="absolute";b.style.top=a.style.top;b.style.left=a.style.left;b.width=this.width_;b.height=this.height_;b.style.width=this.width_+"px";b.style.height=this.height_+"px";return b};Dygraph.prototype.createMouseEventElement_=function(){if(this.isUsingExcanvas_){var a=document.createElement("div");a.style.position="absolute";a.style.backgroundColor="white";a.style.filter="alpha(opacity=0)";a.style.width=this.width_+"px";a.style.height=this.height_+"px";this.graphDiv.appendChild(a);return a}else{return this.canvas_}};Dygraph.prototype.setColors_=function(){var g=this.getLabels();var e=g.length-1;this.colors_=[];this.colorsMap_={};var a=this.attr_("colors");var d;if(!a){var c=this.attr_("colorSaturation")||1;var b=this.attr_("colorValue")||0.5;var k=Math.ceil(e/2);for(d=1;d<=e;d++){if(!this.visibility()[d-1]){continue}var h=d%2?Math.ceil(d/2):(k+d/2);var f=(1*h/(1+e));var j=Dygraph.hsvToRGB(f,c,b);this.colors_.push(j);this.colorsMap_[g[d]]=j}}else{for(d=0;d<e;d++){if(!this.visibility()[d]){continue}var j=a[d%a.length];this.colors_.push(j);this.colorsMap_[g[1+d]]=j}}};Dygraph.prototype.getColors=function(){return this.colors_};Dygraph.prototype.getPropertiesForSeries=function(c){var a=-1;var d=this.getLabels();for(var b=1;b<d.length;b++){if(d[b]==c){a=b;break}}if(a==-1){return null}return{name:c,column:a,visible:this.visibility()[a-1],color:this.colorsMap_[c],axis:1+this.attributes_.axisForSeries(c)}};Dygraph.prototype.createRollInterface_=function(){if(!this.roller_){this.roller_=document.createElement("input");this.roller_.type="text";this.roller_.style.display="none";this.graphDiv.appendChild(this.roller_)}var e=this.attr_("showRoller")?"block":"none";var d=this.plotter_.area;var b={position:"absolute",zIndex:10,top:(d.y+d.h-25)+"px",left:(d.x+1)+"px",display:e};this.roller_.size="2";this.roller_.value=this.rollPeriod_;for(var a in b){if(b.hasOwnProperty(a)){this.roller_.style[a]=b[a]}}var c=this;this.roller_.onchange=function(){c.adjustRoll(c.roller_.value)}};Dygraph.prototype.dragGetX_=function(b,a){return Dygraph.pageX(b)-a.px};Dygraph.prototype.dragGetY_=function(b,a){return Dygraph.pageY(b)-a.py};Dygraph.prototype.createDragInterface_=function(){var c={isZooming:false,isPanning:false,is2DPan:false,dragStartX:null,dragStartY:null,dragEndX:null,dragEndY:null,dragDirection:null,prevEndX:null,prevEndY:null,prevDragDirection:null,cancelNextDblclick:false,initialLeftmostDate:null,xUnitsPerPixel:null,dateRange:null,px:0,py:0,boundedDates:null,boundedValues:null,tarp:new Dygraph.IFrameTarp(),initializeMouseDown:function(j,i,h){if(j.preventDefault){j.preventDefault()}else{j.returnValue=false;j.cancelBubble=true}h.px=Dygraph.findPosX(i.canvas_);h.py=Dygraph.findPosY(i.canvas_);h.dragStartX=i.dragGetX_(j,h);h.dragStartY=i.dragGetY_(j,h);h.cancelNextDblclick=false;h.tarp.cover()}};var f=this.attr_("interactionModel");var b=this;var e=function(g){return function(h){g(h,b,c)}};for(var a in f){if(!f.hasOwnProperty(a)){continue}this.addAndTrackEvent(this.mouseEventElement_,a,e(f[a]))}var d=function(h){if(c.isZooming||c.isPanning){c.isZooming=false;c.dragStartX=null;c.dragStartY=null}if(c.isPanning){c.isPanning=false;c.draggingDate=null;c.dateRange=null;for(var g=0;g<b.axes_.length;g++){delete b.axes_[g].draggingValue;delete b.axes_[g].dragValueRange}}c.tarp.uncover()};this.addAndTrackEvent(document,"mouseup",d)};Dygraph.prototype.drawZoomRect_=function(e,c,i,b,g,a,f,d){var h=this.canvas_ctx_;if(a==Dygraph.HORIZONTAL){h.clearRect(Math.min(c,f),this.layout_.getPlotArea().y,Math.abs(c-f),this.layout_.getPlotArea().h)}else{if(a==Dygraph.VERTICAL){h.clearRect(this.layout_.getPlotArea().x,Math.min(b,d),this.layout_.getPlotArea().w,Math.abs(b-d))}}if(e==Dygraph.HORIZONTAL){if(i&&c){h.fillStyle="rgba(128,128,128,0.33)";h.fillRect(Math.min(c,i),this.layout_.getPlotArea().y,Math.abs(i-c),this.layout_.getPlotArea().h)}}else{if(e==Dygraph.VERTICAL){if(g&&b){h.fillStyle="rgba(128,128,128,0.33)";h.fillRect(this.layout_.getPlotArea().x,Math.min(b,g),this.layout_.getPlotArea().w,Math.abs(g-b))}}}if(this.isUsingExcanvas_){this.currentZoomRectArgs_=[e,c,i,b,g,0,0,0]}};Dygraph.prototype.clearZoomRect_=function(){this.currentZoomRectArgs_=null;this.canvas_ctx_.clearRect(0,0,this.canvas_.width,this.canvas_.height)};Dygraph.prototype.doZoomX_=function(c,a){this.currentZoomRectArgs_=null;var b=this.toDataXCoord(c);var d=this.toDataXCoord(a);this.doZoomXDates_(b,d)};Dygraph.zoomAnimationFunction=function(c,b){var a=1.5;return(1-Math.pow(a,-c))/(1-Math.pow(a,-b))};Dygraph.prototype.doZoomXDates_=function(c,e){var a=this.xAxisRange();var d=[c,e];this.zoomed_x_=true;var b=this;this.doAnimatedZoom(a,d,null,null,function(){if(b.attr_("zoomCallback")){b.attr_("zoomCallback")(c,e,b.yAxisRanges())}})};Dygraph.prototype.doZoomY_=function(h,f){this.currentZoomRectArgs_=null;var c=this.yAxisRanges();var b=[];for(var e=0;e<this.axes_.length;e++){var d=this.toDataYCoord(h,e);var a=this.toDataYCoord(f,e);b.push([a,d])}this.zoomed_y_=true;var g=this;this.doAnimatedZoom(null,null,c,b,function(){if(g.attr_("zoomCallback")){var i=g.xAxisRange();g.attr_("zoomCallback")(i[0],i[1],g.yAxisRanges())}})};Dygraph.prototype.resetZoom=function(){var c=false,d=false,a=false;if(this.dateWindow_!==null){c=true;d=true}for(var g=0;g<this.axes_.length;g++){if(typeof(this.axes_[g].valueWindow)!=="undefined"&&this.axes_[g].valueWindow!==null){c=true;a=true}}this.clearSelection();if(c){this.zoomed_x_=false;this.zoomed_y_=false;var f=this.rawData_[0][0];var b=this.rawData_[this.rawData_.length-1][0];if(!this.attr_("animatedZooms")){this.dateWindow_=null;for(g=0;g<this.axes_.length;g++){if(this.axes_[g].valueWindow!==null){delete this.axes_[g].valueWindow}}this.drawGraph_();if(this.attr_("zoomCallback")){this.attr_("zoomCallback")(f,b,this.yAxisRanges())}return}var l=null,m=null,k=null,h=null;if(d){l=this.xAxisRange();m=[f,b]}if(a){k=this.yAxisRanges();var n=this.gatherDatasets_(this.rolledSeries_,null);var o=n.extremes;this.computeYAxisRanges_(o);h=[];for(g=0;g<this.axes_.length;g++){var e=this.axes_[g];h.push((e.valueRange!==null&&e.valueRange!==undefined)?e.valueRange:e.extremeRange)}}var j=this;this.doAnimatedZoom(l,m,k,h,function(){j.dateWindow_=null;for(var p=0;p<j.axes_.length;p++){if(j.axes_[p].valueWindow!==null){delete j.axes_[p].valueWindow}}if(j.attr_("zoomCallback")){j.attr_("zoomCallback")(f,b,j.yAxisRanges())}})}};Dygraph.prototype.doAnimatedZoom=function(a,e,b,c,m){var i=this.attr_("animatedZooms")?Dygraph.ANIMATION_STEPS:1;var l=[];var k=[];var f,d;if(a!==null&&e!==null){for(f=1;f<=i;f++){d=Dygraph.zoomAnimationFunction(f,i);l[f-1]=[a[0]*(1-d)+d*e[0],a[1]*(1-d)+d*e[1]]}}if(b!==null&&c!==null){for(f=1;f<=i;f++){d=Dygraph.zoomAnimationFunction(f,i);var n=[];for(var g=0;g<this.axes_.length;g++){n.push([b[g][0]*(1-d)+d*c[g][0],b[g][1]*(1-d)+d*c[g][1]])}k[f-1]=n}}var h=this;Dygraph.repeatAndCleanup(function(p){if(k.length){for(var o=0;o<h.axes_.length;o++){var j=k[p][o];h.axes_[o].valueWindow=[j[0],j[1]]}}if(l.length){h.dateWindow_=l[p]}h.drawGraph_()},i,Dygraph.ANIMATION_DURATION/i,m)};Dygraph.prototype.getArea=function(){return this.plotter_.area};Dygraph.prototype.eventToDomCoords=function(c){if(c.offsetX&&c.offsetY){return[c.offsetX,c.offsetY]}else{var b=Dygraph.pageX(c)-Dygraph.findPosX(this.mouseEventElement_);var a=Dygraph.pageY(c)-Dygraph.findPosY(this.mouseEventElement_);return[b,a]}};Dygraph.prototype.findClosestRow=function(a){var g=Infinity;var h=-1;var e=this.layout_.points;for(var c=0;c<e.length;c++){var l=e[c];var d=l.length;for(var b=0;b<d;b++){var k=l[b];if(!Dygraph.isValidPoint(k,true)){continue}var f=Math.abs(k.canvasx-a);if(f<g){g=f;h=k.idx}}}return h};Dygraph.prototype.findClosestPoint=function(f,e){var k=Infinity;var h,o,n,l,d,c,j;for(var b=this.layout_.points.length-1;b>=0;--b){var m=this.layout_.points[b];for(var g=0;g<m.length;++g){l=m[g];if(!Dygraph.isValidPoint(l)){continue}o=l.canvasx-f;n=l.canvasy-e;h=o*o+n*n;if(h<k){k=h;d=l;c=b;j=l.idx}}}var a=this.layout_.setNames[c];return{row:j,seriesName:a,point:d}};Dygraph.prototype.findStackedPoint=function(i,h){var p=this.findClosestRow(i);var f,c;for(var d=0;d<this.layout_.points.length;++d){var g=this.getLeftBoundary_(d);var e=p-g;var l=this.layout_.points[d];if(e>=l.length){continue}var m=l[e];if(!Dygraph.isValidPoint(m)){continue}var j=m.canvasy;if(i>m.canvasx&&e+1<l.length){var k=l[e+1];if(Dygraph.isValidPoint(k)){var o=k.canvasx-m.canvasx;if(o>0){var a=(i-m.canvasx)/o;j+=a*(k.canvasy-m.canvasy)}}}else{if(i<m.canvasx&&e>0){var n=l[e-1];if(Dygraph.isValidPoint(n)){var o=m.canvasx-n.canvasx;if(o>0){var a=(m.canvasx-i)/o;j+=a*(n.canvasy-m.canvasy)}}}}if(d===0||j<h){f=m;c=d}}var b=this.layout_.setNames[c];return{row:p,seriesName:b,point:f}};Dygraph.prototype.mouseMove_=function(b){var h=this.layout_.points;if(h===undefined||h===null){return}var e=this.eventToDomCoords(b);var a=e[0];var j=e[1];var f=this.attr_("highlightSeriesOpts");var d=false;if(f&&!this.isSeriesLocked()){var c;if(this.attr_("stackedGraph")){c=this.findStackedPoint(a,j)}else{c=this.findClosestPoint(a,j)}d=this.setSelection(c.row,c.seriesName)}else{var g=this.findClosestRow(a);d=this.setSelection(g)}var i=this.attr_("highlightCallback");if(i&&d){i(b,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_)}};Dygraph.prototype.getLeftBoundary_=function(b){if(this.boundaryIds_[b]){return this.boundaryIds_[b][0]}else{for(var a=0;a<this.boundaryIds_.length;a++){if(this.boundaryIds_[a]!==undefined){return this.boundaryIds_[a][0]}}return 0}};Dygraph.prototype.animateSelection_=function(f){var e=10;var c=30;if(this.fadeLevel===undefined){this.fadeLevel=0}if(this.animateId===undefined){this.animateId=0}var g=this.fadeLevel;var b=f<0?g:e-g;if(b<=0){if(this.fadeLevel){this.updateSelection_(1)}return}var a=++this.animateId;var d=this;Dygraph.repeatAndCleanup(function(h){if(d.animateId!=a){return}d.fadeLevel+=f;if(d.fadeLevel===0){d.clearSelection()}else{d.updateSelection_(d.fadeLevel/e)}},b,c,function(){})};Dygraph.prototype.updateSelection_=function(d){this.cascadeEvents_("select",{selectedX:this.lastx_,selectedPoints:this.selPoints_});var h;var n=this.canvas_ctx_;if(this.attr_("highlightSeriesOpts")){n.clearRect(0,0,this.width_,this.height_);var f=1-this.attr_("highlightSeriesBackgroundAlpha");if(f){var g=true;if(g){if(d===undefined){this.animateSelection_(1);return}f*=d}n.fillStyle="rgba(255,255,255,"+f+")";n.fillRect(0,0,this.width_,this.height_)}this.plotter_._renderLineChart(this.highlightSet_,n)}else{if(this.previousVerticalX_>=0){var j=0;var k=this.attr_("labels");for(h=1;h<k.length;h++){var c=this.attr_("highlightCircleSize",k[h]);if(c>j){j=c}}var l=this.previousVerticalX_;n.clearRect(l-j-1,0,2*j+2,this.height_)}}if(this.isUsingExcanvas_&&this.currentZoomRectArgs_){Dygraph.prototype.drawZoomRect_.apply(this,this.currentZoomRectArgs_)}if(this.selPoints_.length>0){var b=this.selPoints_[0].canvasx;n.save();for(h=0;h<this.selPoints_.length;h++){var o=this.selPoints_[h];if(!Dygraph.isOK(o.canvasy)){continue}var a=this.attr_("highlightCircleSize",o.name);var m=this.attr_("drawHighlightPointCallback",o.name);var e=this.plotter_.colors[o.name];if(!m){m=Dygraph.Circles.DEFAULT}n.lineWidth=this.attr_("strokeWidth",o.name);n.strokeStyle=e;n.fillStyle=e;m(this.g,o.name,n,b,o.canvasy,e,a,o.idx)}n.restore();this.previousVerticalX_=b}};Dygraph.prototype.setSelection=function(f,h,g){this.selPoints_=[];var e=false;if(f!==false&&f>=0){if(f!=this.lastRow_){e=true}this.lastRow_=f;for(var d=0;d<this.layout_.points.length;++d){var b=this.layout_.points[d];var c=f-this.getLeftBoundary_(d);if(c<b.length){var a=b[c];if(a.yval!==null){this.selPoints_.push(a)}}}}else{if(this.lastRow_>=0){e=true}this.lastRow_=-1}if(this.selPoints_.length){this.lastx_=this.selPoints_[0].xval}else{this.lastx_=-1}if(h!==undefined){if(this.highlightSet_!==h){e=true}this.highlightSet_=h}if(g!==undefined){this.lockedSet_=g}if(e){this.updateSelection_(undefined)}return e};Dygraph.prototype.mouseOut_=function(a){if(this.attr_("unhighlightCallback")){this.attr_("unhighlightCallback")(a)}if(this.attr_("hideOverlayOnMouseOut")&&!this.lockedSet_){this.clearSelection()}};Dygraph.prototype.clearSelection=function(){this.cascadeEvents_("deselect",{});this.lockedSet_=false;if(this.fadeLevel){this.animateSelection_(-1);return}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel=0;this.selPoints_=[];this.lastx_=-1;this.lastRow_=-1;this.highlightSet_=null};Dygraph.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1){return -1}for(var c=0;c<this.layout_.points.length;c++){var a=this.layout_.points[c];for(var b=0;b<a.length;b++){if(a[b].x==this.selPoints_[0].x){return a[b].idx}}}return -1};Dygraph.prototype.getHighlightSeries=function(){return this.highlightSet_};Dygraph.prototype.isSeriesLocked=function(){return this.lockedSet_};Dygraph.prototype.loadedEvent_=function(a){this.rawData_=this.parseCSV_(a);this.predraw_()};Dygraph.prototype.addXTicks_=function(){var a;if(this.dateWindow_){a=[this.dateWindow_[0],this.dateWindow_[1]]}else{a=this.xAxisExtremes()}var c=this.optionsViewForAxis_("x");var b=c("ticker")(a[0],a[1],this.width_,c,this);this.layout_.setXTicks(b)};Dygraph.prototype.extremeValues_=function(d){var h=null,f=null,c,g;var b=this.attr_("errorBars")||this.attr_("customBars");if(b){for(c=0;c<d.length;c++){g=d[c][1][0];if(g===null||isNaN(g)){continue}var a=g-d[c][1][1];var e=g+d[c][1][2];if(a>g){a=g}if(e<g){e=g}if(f===null||e>f){f=e}if(h===null||a<h){h=a}}}else{for(c=0;c<d.length;c++){g=d[c][1];if(g===null||isNaN(g)){continue}if(f===null||g>f){f=g}if(h===null||g<h){h=g}}}return[h,f]};Dygraph.prototype.predraw_=function(){var e=new Date();this.layout_.computePlotArea();this.computeYAxes_();if(this.plotter_){this.cascadeEvents_("clearChart");this.plotter_.clear()}if(!this.is_initial_draw_){this.canvas_ctx_.restore();this.hidden_ctx_.restore()}this.canvas_ctx_.save();this.hidden_ctx_.save();this.plotter_=new DygraphCanvasRenderer(this,this.hidden_,this.hidden_ctx_,this.layout_);this.createRollInterface_();this.cascadeEvents_("predraw");this.rolledSeries_=[null];for(var c=1;c<this.numColumns();c++){var d=this.attr_("logscale");var b=this.extractSeries_(this.rawData_,c,d);b=this.rollingAverage(b,this.rollPeriod_);this.rolledSeries_.push(b)}this.drawGraph_();var a=new Date();this.drawingTimeMs_=(a-e)};Dygraph.PointType=undefined;Dygraph.seriesToPoints_=function(b,j,d,f){var h=[];for(var c=0;c<b.length;++c){var k=b[c];var a=j?k[1][0]:k[1];var e=a===null?null:DygraphLayout.parseFloat_(a);var g={x:NaN,y:NaN,xval:DygraphLayout.parseFloat_(k[0]),yval:e,name:d,idx:c+f};if(j){g.y_top=NaN;g.y_bottom=NaN;g.yval_minus=DygraphLayout.parseFloat_(k[1][1]);g.yval_plus=DygraphLayout.parseFloat_(k[1][2])}h.push(g)}return h};Dygraph.stackPoints_=function(m,l,o,f){var h=null;var d=null;var g=null;var c=-1;var k=function(i){if(c>=i){return}for(var p=i;p<m.length;++p){g=null;if(!isNaN(m[p].yval)&&m[p].yval!==null){c=p;g=m[p];break}}};for(var b=0;b<m.length;++b){var j=m[b];var n=j.xval;if(l[n]===undefined){l[n]=0}var e=j.yval;if(isNaN(e)||e===null){k(b);if(d&&g&&f!="none"){e=d.yval+(g.yval-d.yval)*((n-d.xval)/(g.xval-d.xval))}else{if(d&&f=="all"){e=d.yval}else{if(g&&f=="all"){e=g.yval}else{e=0}}}}else{d=j}var a=l[n];if(h!=n){a+=e;l[n]=a}h=n;j.yval_stacked=a;if(a>o[1]){o[1]=a}if(a<o[0]){o[0]=a}}};Dygraph.prototype.gatherDatasets_=function(w,f){var s=[];var t=[];var q=[];var a={};var u,r;var x=this.attr_("errorBars");var h=this.attr_("customBars");var p=x||h;var b=function(i){if(!p){return i[1]===null}else{return h?i[1][1]===null:x?i[1][0]===null:false}};var n=w.length-1;var l;for(u=n;u>=1;u--){if(!this.visibility()[u-1]){continue}if(f){l=w[u];var z=f[0];var j=f[1];var g=null,y=null;for(r=0;r<l.length;r++){if(l[r][0]>=z&&g===null){g=r}if(l[r][0]<=j){y=r}}if(g===null){g=0}var e=g;var d=true;while(d&&e>0){e--;d=b(l[e])}if(y===null){y=l.length-1}var c=y;d=true;while(d&&c<l.length-1){c++;d=b(l[c])}if(e!==g){g=e}if(c!==y){y=c}s[u-1]=[g,y];l=l.slice(g,y+1)}else{l=w[u];s[u-1]=[0,l.length-1]}var v=this.attr_("labels")[u];var o=this.extremeValues_(l);var m=Dygraph.seriesToPoints_(l,p,v,s[u-1][0]);if(this.attr_("stackedGraph")){Dygraph.stackPoints_(m,q,o,this.attr_("stackedGraphNaNFill"))}a[v]=o;t[u]=m}return{points:t,extremes:a,boundaryIds:s}};Dygraph.prototype.drawGraph_=function(){var a=new Date();var d=this.is_initial_draw_;this.is_initial_draw_=false;this.layout_.removeAllDatasets();this.setColors_();this.attrs_.pointSize=0.5*this.attr_("highlightCircleSize");var j=this.gatherDatasets_(this.rolledSeries_,this.dateWindow_);var h=j.points;var k=j.extremes;this.boundaryIds_=j.boundaryIds;this.setIndexByName_={};var g=this.attr_("labels");if(g.length>0){this.setIndexByName_[g[0]]=0}var e=0;for(var f=1;f<h.length;f++){this.setIndexByName_[g[f]]=f;if(!this.visibility()[f-1]){continue}this.layout_.addDataset(g[f],h[f]);this.datasetIndex_[f]=e++}this.computeYAxisRanges_(k);this.layout_.setYAxes(this.axes_);this.addXTicks_();var b=this.zoomed_x_;this.zoomed_x_=b;this.layout_.evaluate();this.renderGraph_(d);if(this.attr_("timingName")){var c=new Date();Dygraph.info(this.attr_("timingName")+" - drawGraph: "+(c-a)+"ms")}};Dygraph.prototype.renderGraph_=function(b){this.cascadeEvents_("clearChart");this.plotter_.clear();if(this.attr_("underlayCallback")){this.attr_("underlayCallback")(this.hidden_ctx_,this.layout_.getPlotArea(),this,this)}var c={canvas:this.hidden_,drawingContext:this.hidden_ctx_};this.cascadeEvents_("willDrawChart",c);this.plotter_.render();this.cascadeEvents_("didDrawChart",c);this.lastRow_=-1;this.canvas_.getContext("2d").clearRect(0,0,this.canvas_.width,this.canvas_.height);if(this.attr_("drawCallback")!==null){this.attr_("drawCallback")(this,b)}if(b){this.readyFired_=true;while(this.readyFns_.length>0){var a=this.readyFns_.pop();a(this)}}};Dygraph.prototype.computeYAxes_=function(){var b,d,c,f,a;if(this.axes_!==undefined&&this.user_attrs_.hasOwnProperty("valueRange")===false){b=[];for(c=0;c<this.axes_.length;c++){b.push(this.axes_[c].valueWindow)}}this.axes_=[];for(d=0;d<this.attributes_.numAxes();d++){f={g:this};Dygraph.update(f,this.attributes_.axisOptions(d));this.axes_[d]=f}a=this.attr_("valueRange");if(a){this.axes_[0].valueRange=a}if(b!==undefined){var e=Math.min(b.length,this.axes_.length);for(c=0;c<e;c++){this.axes_[c].valueWindow=b[c]}}for(d=0;d<this.axes_.length;d++){if(d===0){f=this.optionsViewForAxis_("y"+(d?"2":""));a=f("valueRange");if(a){this.axes_[d].valueRange=a}}else{var g=this.user_attrs_.axes;if(g&&g.y2){a=g.y2.valueRange;if(a){this.axes_[d].valueRange=a}}}}};Dygraph.prototype.numAxes=function(){return this.attributes_.numAxes()};Dygraph.prototype.axisPropertiesForSeries=function(a){return this.axes_[this.attributes_.axisForSeries(a)]};Dygraph.prototype.computeYAxisRanges_=function(a){var g=function(i){return isNaN(parseFloat(i))};var q=this.attributes_.numAxes();var b,x,o,B;var p;for(var y=0;y<q;y++){var c=this.axes_[y];var C=this.attributes_.getForAxis("logscale",y);var G=this.attributes_.getForAxis("includeZero",y);var l=this.attributes_.getForAxis("independentTicks",y);o=this.attributes_.seriesForAxis(y);b=true;B=0.1;if(this.attr_("yRangePad")!==null){b=false;B=this.attr_("yRangePad")/this.plotter_.area.h}if(o.length===0){c.extremeRange=[0,1]}else{var D=Infinity;var A=-Infinity;var t,s;for(var w=0;w<o.length;w++){if(!a.hasOwnProperty(o[w])){continue}t=a[o[w]][0];if(t!==null){D=Math.min(t,D)}s=a[o[w]][1];if(s!==null){A=Math.max(s,A)}}if(G&&!C){if(D>0){D=0}if(A<0){A=0}}if(D==Infinity){D=0}if(A==-Infinity){A=1}x=A-D;if(x===0){if(A!==0){x=Math.abs(A)}else{A=1;x=1}}var h,H;if(C){if(b){h=A+B*x;H=D}else{var E=Math.exp(Math.log(x)*B);h=A*E;H=D/E}}else{h=A+B*x;H=D-B*x;if(b&&!this.attr_("avoidMinZero")){if(H<0&&D>=0){H=0}if(h>0&&A<=0){h=0}}}c.extremeRange=[H,h]}if(c.valueWindow){c.computedValueRange=[c.valueWindow[0],c.valueWindow[1]]}else{if(c.valueRange){var e=g(c.valueRange[0])?c.extremeRange[0]:c.valueRange[0];var d=g(c.valueRange[1])?c.extremeRange[1]:c.valueRange[1];if(!b){if(c.logscale){var E=Math.exp(Math.log(x)*B);e*=E;d/=E}else{x=d-e;e-=x*B;d+=x*B}}c.computedValueRange=[e,d]}else{c.computedValueRange=c.extremeRange}}if(l){c.independentTicks=l;var r=this.optionsViewForAxis_("y"+(y?"2":""));var F=r("ticker");c.ticks=F(c.computedValueRange[0],c.computedValueRange[1],this.height_,r,this);if(!p){p=c}}}if(p===undefined){throw ('Configuration Error: At least one axis has to have the "independentTicks" option activated.')}for(var y=0;y<q;y++){var c=this.axes_[y];if(!c.independentTicks){var r=this.optionsViewForAxis_("y"+(y?"2":""));var F=r("ticker");var m=p.ticks;var n=p.computedValueRange[1]-p.computedValueRange[0];var I=c.computedValueRange[1]-c.computedValueRange[0];var f=[];for(var v=0;v<m.length;v++){var u=(m[v].v-p.computedValueRange[0])/n;var z=c.computedValueRange[0]+u*I;f.push(z)}c.ticks=F(c.computedValueRange[0],c.computedValueRange[1],this.height_,r,this,f)}}};Dygraph.prototype.extractSeries_=function(a,f,c){var g=[];var h=this.attr_("errorBars");var e=this.attr_("customBars");for(var d=0;d<a.length;d++){var l=a[d][0];var m=a[d][f];if(c){if(h||e){for(var b=0;b<m.length;b++){if(m[b]<=0){m=null;break}}}else{if(m<=0){m=null}}}if(m!==null){g.push([l,m])}else{g.push([l,h?[null,null]:e?[null,null,null]:m])}}return g};Dygraph.prototype.rollingAverage=function(l,d){d=Math.min(d,l.length);var b=[];var t=this.attr_("sigma");var G,o,z,x,m,c,F,A;if(this.fractions_){var k=0;var h=0;var e=100;for(z=0;z<l.length;z++){k+=l[z][1][0];h+=l[z][1][1];if(z-d>=0){k-=l[z-d][1][0];h-=l[z-d][1][1]}var C=l[z][0];var w=h?k/h:0;if(this.attr_("errorBars")){if(this.attr_("wilsonInterval")){if(h){var s=w<0?0:w,u=h;var B=t*Math.sqrt(s*(1-s)/u+t*t/(4*u*u));var a=1+t*t/h;G=(s+t*t/(2*h)-B)/a;o=(s+t*t/(2*h)+B)/a;b[z]=[C,[s*e,(s-G)*e,(o-s)*e]]}else{b[z]=[C,[0,0,0]]}}else{A=h?t*Math.sqrt(w*(1-w)/h):1;b[z]=[C,[e*w,e*A,e*A]]}}else{b[z]=[C,e*w]}}}else{if(this.attr_("customBars")){G=0;var D=0;o=0;var g=0;for(z=0;z<l.length;z++){var E=l[z][1];m=E[1];b[z]=[l[z][0],[m,m-E[0],E[2]-m]];if(m!==null&&!isNaN(m)){G+=E[0];D+=m;o+=E[2];g+=1}if(z-d>=0){var r=l[z-d];if(r[1][1]!==null&&!isNaN(r[1][1])){G-=r[1][0];D-=r[1][1];o-=r[1][2];g-=1}}if(g){b[z]=[l[z][0],[1*D/g,1*(D-G)/g,1*(o-D)/g]]}else{b[z]=[l[z][0],[null,null,null]]}}}else{if(!this.attr_("errorBars")){if(d==1){return l}for(z=0;z<l.length;z++){c=0;F=0;for(x=Math.max(0,z-d+1);x<z+1;x++){m=l[x][1];if(m===null||isNaN(m)){continue}F++;c+=l[x][1]}if(F){b[z]=[l[z][0],c/F]}else{b[z]=[l[z][0],null]}}}else{for(z=0;z<l.length;z++){c=0;var f=0;F=0;for(x=Math.max(0,z-d+1);x<z+1;x++){m=l[x][1][0];if(m===null||isNaN(m)){continue}F++;c+=l[x][1][0];f+=Math.pow(l[x][1][1],2)}if(F){A=Math.sqrt(f)/F;b[z]=[l[z][0],[c/F,t*A,t*A]]}else{var q=(d==1)?l[z][1][0]:null;b[z]=[l[z][0],[q,q,q]]}}}}}return b};Dygraph.prototype.detectTypeFromString_=function(b){var a=false;var c=b.indexOf("-");if((c>0&&(b[c-1]!="e"&&b[c-1]!="E"))||b.indexOf("/")>=0||isNaN(parseFloat(b))){a=true}else{if(b.length==8&&b>"19700101"&&b<"20371231"){a=true}}this.setXAxisOptions_(a)};Dygraph.prototype.setXAxisOptions_=function(a){if(a){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{this.attrs_.xValueParser=function(b){return parseFloat(b)};this.attrs_.axes.x.valueFormatter=function(b){return b};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}};Dygraph.prototype.parseFloat_=function(a,c,b){var e=parseFloat(a);if(!isNaN(e)){return e}if(/^ *$/.test(a)){return null}if(/^ *nan *$/i.test(a)){return NaN}var d="Unable to parse '"+a+"' as a number";if(b!==null&&c!==null){d+=" on line "+(1+c)+" ('"+b+"') of CSV."}this.error(d);return null};Dygraph.prototype.parseCSV_=function(t){var r=[];var s=Dygraph.detectLineDelimiter(t);var a=t.split(s||"\n");var g,k;var p=this.attr_("delimiter");if(a[0].indexOf(p)==-1&&a[0].indexOf("\t")>=0){p="\t"}var b=0;if(!("labels" in this.user_attrs_)){b=1;this.attrs_.labels=a[0].split(p);this.attributes_.reparseSeries()}var o=0;var m;var q=false;var c=this.attr_("labels").length;var f=false;for(var l=b;l<a.length;l++){var e=a[l];o=l;if(e.length===0){continue}if(e[0]=="#"){continue}var d=e.split(p);if(d.length<2){continue}var h=[];if(!q){this.detectTypeFromString_(d[0]);m=this.attr_("xValueParser");q=true}h[0]=m(d[0],this);if(this.fractions_){for(k=1;k<d.length;k++){g=d[k].split("/");if(g.length!=2){this.error('Expected fractional "num/den" values in CSV data but found a value \''+d[k]+"' on line "+(1+l)+" ('"+e+"') which is not of this form.");h[k]=[0,0]}else{h[k]=[this.parseFloat_(g[0],l,e),this.parseFloat_(g[1],l,e)]}}}else{if(this.attr_("errorBars")){if(d.length%2!=1){this.error("Expected alternating (value, stdev.) pairs in CSV data but line "+(1+l)+" has an odd number of values ("+(d.length-1)+"): '"+e+"'")}for(k=1;k<d.length;k+=2){h[(k+1)/2]=[this.parseFloat_(d[k],l,e),this.parseFloat_(d[k+1],l,e)]}}else{if(this.attr_("customBars")){for(k=1;k<d.length;k++){var u=d[k];if(/^ *$/.test(u)){h[k]=[null,null,null]}else{g=u.split(";");if(g.length==3){h[k]=[this.parseFloat_(g[0],l,e),this.parseFloat_(g[1],l,e),this.parseFloat_(g[2],l,e)]}else{this.warn('When using customBars, values must be either blank or "low;center;high" tuples (got "'+u+'" on line '+(1+l))}}}}else{for(k=1;k<d.length;k++){h[k]=this.parseFloat_(d[k],l,e)}}}}if(r.length>0&&h[0]<r[r.length-1][0]){f=true}if(h.length!=c){this.error("Number of columns in line "+l+" ("+h.length+") does not agree with number of labels ("+c+") "+e)}if(l===0&&this.attr_("labels")){var n=true;for(k=0;n&&k<h.length;k++){if(h[k]){n=false}}if(n){this.warn("The dygraphs 'labels' option is set, but the first row of CSV data ('"+e+"') appears to also contain labels. Will drop the CSV labels and use the option labels.");continue}}r.push(h)}if(f){this.warn("CSV is out of order; order it correctly to speed loading.");r.sort(function(j,i){return j[0]-i[0]})}return r};Dygraph.prototype.parseArray_=function(c){if(c.length===0){this.error("Can't plot empty data set");return null}if(c[0].length===0){this.error("Data set cannot contain an empty row");return null}var a;if(this.attr_("labels")===null){this.warn("Using default labels. Set labels explicitly via 'labels' in the options parameter");this.attrs_.labels=["X"];for(a=1;a<c[0].length;a++){this.attrs_.labels.push("Y"+a)}this.attributes_.reparseSeries()}else{var b=this.attr_("labels");if(b.length!=c[0].length){this.error("Mismatch between number of labels ("+b+") and number of columns in array ("+c[0].length+")");return null}}if(Dygraph.isDateLike(c[0][0])){this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter;var d=Dygraph.clone(c);for(a=0;a<c.length;a++){if(d[a].length===0){this.error("Row "+(1+a)+" of data is empty");return null}if(d[a][0]===null||typeof(d[a][0].getTime)!="function"||isNaN(d[a][0].getTime())){this.error("x value in row "+(1+a)+" is not a Date");return null}d[a][0]=d[a][0].getTime()}return d}else{this.attrs_.axes.x.valueFormatter=function(e){return e};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=Dygraph.numberAxisLabelFormatter;return c}};Dygraph.prototype.parseDataTable_=function(w){var d=function(i){var j=String.fromCharCode(65+i%26);i=Math.floor(i/26);while(i>0){j=String.fromCharCode(65+(i-1)%26)+j.toLowerCase();i=Math.floor((i-1)/26)}return j};var h=w.getNumberOfColumns();var g=w.getNumberOfRows();var f=w.getColumnType(0);if(f=="date"||f=="datetime"){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{if(f=="number"){this.attrs_.xValueParser=function(i){return parseFloat(i)};this.attrs_.axes.x.valueFormatter=function(i){return i};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}else{this.error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+f+"')");return null}}var m=[];var t={};var s=false;var q,o;for(q=1;q<h;q++){var b=w.getColumnType(q);if(b=="number"){m.push(q)}else{if(b=="string"&&this.attr_("displayAnnotations")){var r=m[m.length-1];if(!t.hasOwnProperty(r)){t[r]=[q]}else{t[r].push(q)}s=true}else{this.error("Only 'number' is supported as a dependent type with Gviz. 'string' is only supported if displayAnnotations is true")}}}var u=[w.getColumnLabel(0)];for(q=0;q<m.length;q++){u.push(w.getColumnLabel(m[q]));if(this.attr_("errorBars")){q+=1}}this.attrs_.labels=u;h=u.length;var v=[];var l=false;var a=[];for(q=0;q<g;q++){var e=[];if(typeof(w.getValue(q,0))==="undefined"||w.getValue(q,0)===null){this.warn("Ignoring row "+q+" of DataTable because of undefined or null first column.");continue}if(f=="date"||f=="datetime"){e.push(w.getValue(q,0).getTime())}else{e.push(w.getValue(q,0))}if(!this.attr_("errorBars")){for(o=0;o<m.length;o++){var c=m[o];e.push(w.getValue(q,c));if(s&&t.hasOwnProperty(c)&&w.getValue(q,t[c][0])!==null){var p={};p.series=w.getColumnLabel(c);p.xval=e[0];p.shortText=d(a.length);p.text="";for(var n=0;n<t[c].length;n++){if(n){p.text+="\n"}p.text+=w.getValue(q,t[c][n])}a.push(p)}}for(o=0;o<e.length;o++){if(!isFinite(e[o])){e[o]=null}}}else{for(o=0;o<h-1;o++){e.push([w.getValue(q,1+2*o),w.getValue(q,2+2*o)])}}if(v.length>0&&e[0]<v[v.length-1][0]){l=true}v.push(e)}if(l){this.warn("DataTable is out of order; order it correctly to speed loading.");v.sort(function(j,i){return j[0]-i[0]})}this.rawData_=v;if(a.length>0){this.setAnnotations(a,true)}this.attributes_.reparseSeries()};Dygraph.prototype.start_=function(){var d=this.file_;if(typeof d=="function"){d=d()}if(Dygraph.isArrayLike(d)){this.rawData_=this.parseArray_(d);this.predraw_()}else{if(typeof d=="object"&&typeof d.getColumnRange=="function"){this.parseDataTable_(d);this.predraw_()}else{if(typeof d=="string"){var c=Dygraph.detectLineDelimiter(d);if(c){this.loadedEvent_(d)}else{var b;if(window.XMLHttpRequest){b=new XMLHttpRequest()}else{b=new ActiveXObject("Microsoft.XMLHTTP")}var a=this;b.onreadystatechange=function(){if(b.readyState==4){if(b.status===200||b.status===0){a.loadedEvent_(b.responseText)}}};b.open("GET",d,true);b.send(null)}}else{this.error("Unknown data format: "+(typeof d))}}}};Dygraph.prototype.updateOptions=function(e,b){if(typeof(b)=="undefined"){b=false}var d=e.file;var c=Dygraph.mapLegacyOptions_(e);if("rollPeriod" in c){this.rollPeriod_=c.rollPeriod}if("dateWindow" in c){this.dateWindow_=c.dateWindow;if(!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_x_=(c.dateWindow!==null)}}if("valueRange" in c&&!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_y_=(c.valueRange!==null)}var a=Dygraph.isPixelChangingOptionList(this.attr_("labels"),c);Dygraph.updateDeep(this.user_attrs_,c);this.attributes_.reparseSeries();if(d){this.file_=d;if(!b){this.start_()}}else{if(!b){if(a){this.predraw_()}else{this.renderGraph_(false)}}}};Dygraph.mapLegacyOptions_=function(c){var a={};for(var b in c){if(b=="file"){continue}if(c.hasOwnProperty(b)){a[b]=c[b]}}var e=function(g,f,h){if(!a.axes){a.axes={}}if(!a.axes[g]){a.axes[g]={}}a.axes[g][f]=h};var d=function(f,g,h){if(typeof(c[f])!="undefined"){Dygraph.warn("Option "+f+" is deprecated. Use the "+h+" option for the "+g+" axis instead. (e.g. { axes : { "+g+" : { "+h+" : ... } } } (see http://dygraphs.com/per-axis.html for more information.");e(g,h,c[f]);delete a[f]}};d("xValueFormatter","x","valueFormatter");d("pixelsPerXLabel","x","pixelsPerLabel");d("xAxisLabelFormatter","x","axisLabelFormatter");d("xTicker","x","ticker");d("yValueFormatter","y","valueFormatter");d("pixelsPerYLabel","y","pixelsPerLabel");d("yAxisLabelFormatter","y","axisLabelFormatter");d("yTicker","y","ticker");return a};Dygraph.prototype.resize=function(d,b){if(this.resize_lock){return}this.resize_lock=true;if((d===null)!=(b===null)){this.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero.");d=b=null}var a=this.width_;var c=this.height_;if(d){this.maindiv_.style.width=d+"px";this.maindiv_.style.height=b+"px";this.width_=d;this.height_=b}else{this.width_=this.maindiv_.clientWidth;this.height_=this.maindiv_.clientHeight}if(a!=this.width_||c!=this.height_){this.resizeElements_();this.predraw_()}this.resize_lock=false};Dygraph.prototype.adjustRoll=function(a){this.rollPeriod_=a;this.predraw_()};Dygraph.prototype.visibility=function(){if(!this.attr_("visibility")){this.attrs_.visibility=[]}while(this.attr_("visibility").length<this.numColumns()-1){this.attrs_.visibility.push(true)}return this.attr_("visibility")};Dygraph.prototype.setVisibility=function(b,c){var a=this.visibility();if(b<0||b>=a.length){this.warn("invalid series number in setVisibility: "+b)}else{a[b]=c;this.predraw_()}};Dygraph.prototype.size=function(){return{width:this.width_,height:this.height_}};Dygraph.prototype.setAnnotations=function(b,a){Dygraph.addAnnotationRule();this.annotations_=b;if(!this.layout_){this.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html");return}this.layout_.setAnnotations(this.annotations_);if(!a){this.predraw_()}};Dygraph.prototype.annotations=function(){return this.annotations_};Dygraph.prototype.getLabels=function(){var a=this.attr_("labels");return a?a.slice():null};Dygraph.prototype.indexFromSetName=function(a){return this.setIndexByName_[a]};Dygraph.prototype.ready=function(a){if(this.is_initial_draw_){this.readyFns_.push(a)}else{a(this)}};Dygraph.addAnnotationRule=function(){if(Dygraph.addedAnnotationCSS){return}var f="border: 1px solid black; background-color: white; text-align: center;";var e=document.createElement("style");e.type="text/css";document.getElementsByTagName("head")[0].appendChild(e);for(var b=0;b<document.styleSheets.length;b++){if(document.styleSheets[b].disabled){continue}var d=document.styleSheets[b];try{if(d.insertRule){var a=d.cssRules?d.cssRules.length:0;d.insertRule(".dygraphDefaultAnnotation { "+f+" }",a)}else{if(d.addRule){d.addRule(".dygraphDefaultAnnotation",f)}}Dygraph.addedAnnotationCSS=true;return}catch(c){}}this.warn("Unable to add default annotation CSS rule; display may be off.")};var DateGraph=Dygraph;"use strict";Dygraph.LOG_SCALE=10;Dygraph.LN_TEN=Math.log(Dygraph.LOG_SCALE);Dygraph.log10=function(a){return Math.log(a)/Dygraph.LN_TEN};Dygraph.DEBUG=1;Dygraph.INFO=2;Dygraph.WARNING=3;Dygraph.ERROR=3;Dygraph.LOG_STACK_TRACES=false;Dygraph.DOTTED_LINE=[2,2];Dygraph.DASHED_LINE=[7,3];Dygraph.DOT_DASH_LINE=[7,2,2,2];Dygraph.log=function(c,g){var b;if(typeof(printStackTrace)!="undefined"){try{b=printStackTrace({guess:false});while(b[0].indexOf("stacktrace")!=-1){b.splice(0,1)}b.splice(0,2);for(var d=0;d<b.length;d++){b[d]=b[d].replace(/\([^)]*\/(.*)\)/,"@$1").replace(/\@.*\/([^\/]*)/,"@$1").replace("[object Object].","")}var j=b.splice(0,1)[0];g+=" ("+j.replace(/^.*@ ?/,"")+")"}catch(h){}}if(typeof(window.console)!="undefined"){var a=window.console;var f=function(e,k,i){if(k&&typeof(k)=="function"){k.call(e,i)}else{e.log(i)}};switch(c){case Dygraph.DEBUG:f(a,a.debug,"dygraphs: "+g);break;case Dygraph.INFO:f(a,a.info,"dygraphs: "+g);break;case Dygraph.WARNING:f(a,a.warn,"dygraphs: "+g);break;case Dygraph.ERROR:f(a,a.error,"dygraphs: "+g);break}}if(Dygraph.LOG_STACK_TRACES){window.console.log(b.join("\n"))}};Dygraph.info=function(a){Dygraph.log(Dygraph.INFO,a)};Dygraph.prototype.info=Dygraph.info;Dygraph.warn=function(a){Dygraph.log(Dygraph.WARNING,a)};Dygraph.prototype.warn=Dygraph.warn;Dygraph.error=function(a){Dygraph.log(Dygraph.ERROR,a)};Dygraph.prototype.error=Dygraph.error;Dygraph.getContext=function(a){return(a.getContext("2d"))};Dygraph.addEvent=function addEvent(c,b,a){if(c.addEventListener){c.addEventListener(b,a,false)}else{c[b+a]=function(){a(window.event)};c.attachEvent("on"+b,c[b+a])}};Dygraph.prototype.addAndTrackEvent=function(c,b,a){Dygraph.addEvent(c,b,a);this.registeredEvents_.push({elem:c,type:b,fn:a})};Dygraph.removeEvent=function(c,b,a){if(c.removeEventListener){c.removeEventListener(b,a,false)}else{try{c.detachEvent("on"+b,c[b+a])}catch(d){}c[b+a]=null}};Dygraph.prototype.removeTrackedEvents_=function(){if(this.registeredEvents_){for(var a=0;a<this.registeredEvents_.length;a++){var b=this.registeredEvents_[a];Dygraph.removeEvent(b.elem,b.type,b.fn)}}this.registeredEvents_=[]};Dygraph.cancelEvent=function(a){a=a?a:window.event;if(a.stopPropagation){a.stopPropagation()}if(a.preventDefault){a.preventDefault()}a.cancelBubble=true;a.cancel=true;a.returnValue=false;return false};Dygraph.hsvToRGB=function(h,g,k){var c;var d;var l;if(g===0){c=k;d=k;l=k}else{var e=Math.floor(h*6);var j=(h*6)-e;var b=k*(1-g);var a=k*(1-(g*j));var m=k*(1-(g*(1-j)));switch(e){case 1:c=a;d=k;l=b;break;case 2:c=b;d=k;l=m;break;case 3:c=b;d=a;l=k;break;case 4:c=m;d=b;l=k;break;case 5:c=k;d=b;l=a;break;case 6:case 0:c=k;d=m;l=b;break}}c=Math.floor(255*c+0.5);d=Math.floor(255*d+0.5);l=Math.floor(255*l+0.5);return"rgb("+c+","+d+","+l+")"};Dygraph.findPosX=function(c){var d=0;if(c.offsetParent){var a=c;while(1){var b="0";if(window.getComputedStyle){b=window.getComputedStyle(a,null).borderLeft||"0"}d+=parseInt(b,10);d+=a.offsetLeft;if(!a.offsetParent){break}a=a.offsetParent}}else{if(c.x){d+=c.x}}while(c&&c!=document.body){d-=c.scrollLeft;c=c.parentNode}return d};Dygraph.findPosY=function(c){var b=0;if(c.offsetParent){var a=c;while(1){var d="0";if(window.getComputedStyle){d=window.getComputedStyle(a,null).borderTop||"0"}b+=parseInt(d,10);b+=a.offsetTop;if(!a.offsetParent){break}a=a.offsetParent}}else{if(c.y){b+=c.y}}while(c&&c!=document.body){b-=c.scrollTop;c=c.parentNode}return b};Dygraph.pageX=function(c){if(c.pageX){return(!c.pageX||c.pageX<0)?0:c.pageX}else{var d=document.documentElement;var a=document.body;return c.clientX+(d.scrollLeft||a.scrollLeft)-(d.clientLeft||0)}};Dygraph.pageY=function(c){if(c.pageY){return(!c.pageY||c.pageY<0)?0:c.pageY}else{var d=document.documentElement;var a=document.body;return c.clientY+(d.scrollTop||a.scrollTop)-(d.clientTop||0)}};Dygraph.isOK=function(a){return !!a&&!isNaN(a)};Dygraph.isValidPoint=function(b,a){if(!b){return false}if(b.yval===null){return false}if(b.x===null||b.x===undefined){return false}if(b.y===null||b.y===undefined){return false}if(isNaN(b.x)||(!a&&isNaN(b.y))){return false}return true};Dygraph.floatFormat=function(a,b){var c=Math.min(Math.max(1,b||2),21);return(Math.abs(a)<0.001&&a!==0)?a.toExponential(c-1):a.toPrecision(c)};Dygraph.zeropad=function(a){if(a<10){return"0"+a}else{return""+a}};Dygraph.hmsString_=function(a){var c=Dygraph.zeropad;var b=new Date(a);if(b.getSeconds()){return c(b.getHours())+":"+c(b.getMinutes())+":"+c(b.getSeconds())}else{return c(b.getHours())+":"+c(b.getMinutes())}};Dygraph.round_=function(c,b){var a=Math.pow(10,b);return Math.round(c*a)/a};Dygraph.binarySearch=function(a,d,i,e,b){if(e===null||e===undefined||b===null||b===undefined){e=0;b=d.length-1}if(e>b){return -1}if(i===null||i===undefined){i=0}var h=function(j){return j>=0&&j<d.length};var g=parseInt((e+b)/2,10);var c=d[g];var f;if(c==a){return g}else{if(c>a){if(i>0){f=g-1;if(h(f)&&d[f]<a){return g}}return Dygraph.binarySearch(a,d,i,e,g-1)}else{if(c<a){if(i<0){f=g+1;if(h(f)&&d[f]>a){return g}}return Dygraph.binarySearch(a,d,i,g+1,b)}}}return -1};Dygraph.dateParser=function(a){var b;var c;if(a.search("-")==-1||a.search("T")!=-1||a.search("Z")!=-1){c=Dygraph.dateStrToMillis(a);if(c&&!isNaN(c)){return c}}if(a.search("-")!=-1){b=a.replace("-","/","g");while(b.search("-")!=-1){b=b.replace("-","/")}c=Dygraph.dateStrToMillis(b)}else{if(a.length==8){b=a.substr(0,4)+"/"+a.substr(4,2)+"/"+a.substr(6,2);c=Dygraph.dateStrToMillis(b)}else{c=Dygraph.dateStrToMillis(a)}}if(!c||isNaN(c)){Dygraph.error("Couldn't parse "+a+" as a date")}return c};Dygraph.dateStrToMillis=function(a){return new Date(a).getTime()};Dygraph.update=function(b,c){if(typeof(c)!="undefined"&&c!==null){for(var a in c){if(c.hasOwnProperty(a)){b[a]=c[a]}}}return b};Dygraph.updateDeep=function(b,d){function c(e){return(typeof Node==="object"?e instanceof Node:typeof e==="object"&&typeof e.nodeType==="number"&&typeof e.nodeName==="string")}if(typeof(d)!="undefined"&&d!==null){for(var a in d){if(d.hasOwnProperty(a)){if(d[a]===null){b[a]=null}else{if(Dygraph.isArrayLike(d[a])){b[a]=d[a].slice()}else{if(c(d[a])){b[a]=d[a]}else{if(typeof(d[a])=="object"){if(typeof(b[a])!="object"||b[a]===null){b[a]={}}Dygraph.updateDeep(b[a],d[a])}else{b[a]=d[a]}}}}}}}return b};Dygraph.isArrayLike=function(b){var a=typeof(b);if((a!="object"&&!(a=="function"&&typeof(b.item)=="function"))||b===null||typeof(b.length)!="number"||b.nodeType===3){return false}return true};Dygraph.isDateLike=function(a){if(typeof(a)!="object"||a===null||typeof(a.getTime)!="function"){return false}return true};Dygraph.clone=function(c){var b=[];for(var a=0;a<c.length;a++){if(Dygraph.isArrayLike(c[a])){b.push(Dygraph.clone(c[a]))}else{b.push(c[a])}}return b};Dygraph.createCanvas=function(){var a=document.createElement("canvas");var b=(/MSIE/.test(navigator.userAgent)&&!window.opera);if(b&&(typeof(G_vmlCanvasManager)!="undefined")){a=G_vmlCanvasManager.initElement((a))}return a};Dygraph.isAndroid=function(){return(/Android/).test(navigator.userAgent)};Dygraph.Iterator=function(d,c,b,a){c=c||0;b=b||d.length;this.hasNext=true;this.peek=null;this.start_=c;this.array_=d;this.predicate_=a;this.end_=Math.min(d.length,c+b);this.nextIdx_=c-1;this.next()};Dygraph.Iterator.prototype.next=function(){if(!this.hasNext){return null}var c=this.peek;var b=this.nextIdx_+1;var a=false;while(b<this.end_){if(!this.predicate_||this.predicate_(this.array_,b)){this.peek=this.array_[b];a=true;break}b++}this.nextIdx_=b;if(!a){this.hasNext=false;this.peek=null}return c};Dygraph.createIterator=function(d,c,b,a){return new Dygraph.Iterator(d,c,b,a)};Dygraph.requestAnimFrame=(function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1000/60)}})();Dygraph.repeatAndCleanup=function(h,g,f,a){var i=0;var d;var b=new Date().getTime();h(i);if(g==1){a();return}var e=g-1;(function c(){if(i>=g){return}Dygraph.requestAnimFrame.call(window,function(){var l=new Date().getTime();var j=l-b;d=i;i=Math.floor(j/f);var k=i-d;var m=(i+k)>e;if(m||(i>=e)){h(e);a()}else{if(k!==0){h(i)}c()}})})()};Dygraph.isPixelChangingOptionList=function(h,e){var d={annotationClickHandler:true,annotationDblClickHandler:true,annotationMouseOutHandler:true,annotationMouseOverHandler:true,axisLabelColor:true,axisLineColor:true,axisLineWidth:true,clickCallback:true,digitsAfterDecimal:true,drawCallback:true,drawHighlightPointCallback:true,drawPoints:true,drawPointCallback:true,drawXGrid:true,drawYGrid:true,fillAlpha:true,gridLineColor:true,gridLineWidth:true,hideOverlayOnMouseOut:true,highlightCallback:true,highlightCircleSize:true,interactionModel:true,isZoomedIgnoreProgrammaticZoom:true,labelsDiv:true,labelsDivStyles:true,labelsDivWidth:true,labelsKMB:true,labelsKMG2:true,labelsSeparateLines:true,labelsShowZeroValues:true,legend:true,maxNumberWidth:true,panEdgeFraction:true,pixelsPerYLabel:true,pointClickCallback:true,pointSize:true,rangeSelectorPlotFillColor:true,rangeSelectorPlotStrokeColor:true,showLabelsOnHighlight:true,showRoller:true,sigFigs:true,strokeWidth:true,underlayCallback:true,unhighlightCallback:true,xAxisLabelFormatter:true,xTicker:true,xValueFormatter:true,yAxisLabelFormatter:true,yValueFormatter:true,zoomCallback:true};var a=false;var b={};if(h){for(var f=1;f<h.length;f++){b[h[f]]=true}}for(var g in e){if(a){break}if(e.hasOwnProperty(g)){if(b[g]){for(var c in e[g]){if(a){break}if(e[g].hasOwnProperty(c)&&!d[c]){a=true}}}else{if(!d[g]){a=true}}}}return a};Dygraph.compareArrays=function(c,b){if(!Dygraph.isArrayLike(c)||!Dygraph.isArrayLike(b)){return false}if(c.length!==b.length){return false}for(var a=0;a<c.length;a++){if(c[a]!==b[a]){return false}}return true};Dygraph.regularShape_=function(o,c,i,f,e,a,n){a=a||0;n=n||Math.PI*2/c;o.beginPath();var g=a;var d=g;var h=function(){var p=f+(Math.sin(d)*i);var q=e+(-Math.cos(d)*i);return[p,q]};var b=h();var l=b[0];var j=b[1];o.moveTo(l,j);for(var m=0;m<c;m++){d=(m==c-1)?g:(d+n);var k=h();o.lineTo(k[0],k[1])}o.fill();o.stroke()};Dygraph.shapeFunction_=function(b,a,c){return function(j,i,f,e,k,h,d){f.strokeStyle=h;f.fillStyle="white";Dygraph.regularShape_(f,b,d,e,k,a,c)}};Dygraph.Circles={DEFAULT:function(h,f,b,e,d,c,a){b.beginPath();b.fillStyle=c;b.arc(e,d,a,0,2*Math.PI,false);b.fill()},TRIANGLE:Dygraph.shapeFunction_(3),SQUARE:Dygraph.shapeFunction_(4,Math.PI/4),DIAMOND:Dygraph.shapeFunction_(4),PENTAGON:Dygraph.shapeFunction_(5),HEXAGON:Dygraph.shapeFunction_(6),CIRCLE:function(f,e,c,b,h,d,a){c.beginPath();c.strokeStyle=d;c.fillStyle="white";c.arc(b,h,a,0,2*Math.PI,false);c.fill();c.stroke()},STAR:Dygraph.shapeFunction_(5,0,4*Math.PI/5),PLUS:function(f,e,c,b,h,d,a){c.strokeStyle=d;c.beginPath();c.moveTo(b+a,h);c.lineTo(b-a,h);c.closePath();c.stroke();c.beginPath();c.moveTo(b,h+a);c.lineTo(b,h-a);c.closePath();c.stroke()},EX:function(f,e,c,b,h,d,a){c.strokeStyle=d;c.beginPath();c.moveTo(b+a,h+a);c.lineTo(b-a,h-a);c.closePath();c.stroke();c.beginPath();c.moveTo(b+a,h-a);c.lineTo(b-a,h+a);c.closePath();c.stroke()}};Dygraph.IFrameTarp=function(){this.tarps=[]};Dygraph.IFrameTarp.prototype.cover=function(){var f=document.getElementsByTagName("iframe");for(var c=0;c<f.length;c++){var e=f[c];var b=Dygraph.findPosX(e),h=Dygraph.findPosY(e),d=e.offsetWidth,a=e.offsetHeight;var g=document.createElement("div");g.style.position="absolute";g.style.left=b+"px";g.style.top=h+"px";g.style.width=d+"px";g.style.height=a+"px";g.style.zIndex=999;document.body.appendChild(g);this.tarps.push(g)}};Dygraph.IFrameTarp.prototype.uncover=function(){for(var a=0;a<this.tarps.length;a++){this.tarps[a].parentNode.removeChild(this.tarps[a])}this.tarps=[]};Dygraph.detectLineDelimiter=function(c){for(var a=0;a<c.length;a++){var b=c.charAt(a);if(b==="\r"){if(((a+1)<c.length)&&(c.charAt(a+1)==="\n")){return"\r\n"}return b}if(b==="\n"){if(((a+1)<c.length)&&(c.charAt(a+1)==="\r")){return"\n\r"}return b}}return null};Dygraph.isNodeContainedBy=function(b,a){if(a===null||b===null){return false}var c=(b);while(c&&c!==a){c=c.parentNode}return(c===a)};Dygraph.pow=function(a,b){if(b<0){return 1/Math.pow(a,-b)}return Math.pow(a,b)};Dygraph.dateSetters={ms:Date.prototype.setMilliseconds,s:Date.prototype.setSeconds,m:Date.prototype.setMinutes,h:Date.prototype.setHours};Dygraph.setDateSameTZ=function(c,b){var f=c.getTimezoneOffset();for(var a in b){if(!b.hasOwnProperty(a)){continue}var e=Dygraph.dateSetters[a];if(!e){throw"Invalid setter: "+a}e.call(c,b[a]);if(c.getTimezoneOffset()!=f){c.setTime(c.getTime()+(f-c.getTimezoneOffset())*60*1000)}}};"use strict";Dygraph.GVizChart=function(a){this.container=a};Dygraph.GVizChart.prototype.draw=function(b,a){this.container.innerHTML="";if(typeof(this.date_graph)!="undefined"){this.date_graph.destroy()}this.date_graph=new Dygraph(this.container,b,a)};Dygraph.GVizChart.prototype.setSelection=function(b){var a=false;if(b.length){a=b[0].row}this.date_graph.setSelection(a)};Dygraph.GVizChart.prototype.getSelection=function(){var b=[];var d=this.date_graph.getSelection();if(d<0){return b}var a=this.date_graph.layout_.points;for(var c=0;c<a.length;++c){b.push({row:d,column:c+1})}return b};"use strict";Dygraph.Interaction={};Dygraph.Interaction.startPan=function(o,t,c){var r,b;c.isPanning=true;var k=t.xAxisRange();c.dateRange=k[1]-k[0];c.initialLeftmostDate=k[0];c.xUnitsPerPixel=c.dateRange/(t.plotter_.area.w-1);if(t.attr_("panEdgeFraction")){var x=t.width_*t.attr_("panEdgeFraction");var d=t.xAxisExtremes();var j=t.toDomXCoord(d[0])-x;var l=t.toDomXCoord(d[1])+x;var u=t.toDataXCoord(j);var w=t.toDataXCoord(l);c.boundedDates=[u,w];var f=[];var a=t.height_*t.attr_("panEdgeFraction");for(r=0;r<t.axes_.length;r++){b=t.axes_[r];var p=b.extremeRange;var q=t.toDomYCoord(p[0],r)+a;var s=t.toDomYCoord(p[1],r)-a;var n=t.toDataYCoord(q,r);var e=t.toDataYCoord(s,r);f[r]=[n,e]}c.boundedValues=f}c.is2DPan=false;c.axes=[];for(r=0;r<t.axes_.length;r++){b=t.axes_[r];var h={};var m=t.yAxisRange(r);var v=t.attributes_.getForAxis("logscale",r);if(v){h.initialTopValue=Dygraph.log10(m[1]);h.dragValueRange=Dygraph.log10(m[1])-Dygraph.log10(m[0])}else{h.initialTopValue=m[1];h.dragValueRange=m[1]-m[0]}h.unitsPerPixel=h.dragValueRange/(t.plotter_.area.h-1);c.axes.push(h);if(b.valueWindow||b.valueRange){c.is2DPan=true}}};Dygraph.Interaction.movePan=function(b,k,c){c.dragEndX=k.dragGetX_(b,c);c.dragEndY=k.dragGetY_(b,c);var h=c.initialLeftmostDate-(c.dragEndX-c.dragStartX)*c.xUnitsPerPixel;if(c.boundedDates){h=Math.max(h,c.boundedDates[0])}var a=h+c.dateRange;if(c.boundedDates){if(a>c.boundedDates[1]){h=h-(a-c.boundedDates[1]);a=h+c.dateRange}}k.dateWindow_=[h,a];if(c.is2DPan){var d=c.dragEndY-c.dragStartY;for(var j=0;j<k.axes_.length;j++){var e=k.axes_[j];var o=c.axes[j];var p=d*o.unitsPerPixel;var f=c.boundedValues?c.boundedValues[j]:null;var l=o.initialTopValue+p;if(f){l=Math.min(l,f[1])}var n=l-o.dragValueRange;if(f){if(n<f[0]){l=l-(n-f[0]);n=l-o.dragValueRange}}var m=k.attributes_.getForAxis("logscale",j);if(m){e.valueWindow=[Math.pow(Dygraph.LOG_SCALE,n),Math.pow(Dygraph.LOG_SCALE,l)]}else{e.valueWindow=[n,l]}}}k.drawGraph_(false)};Dygraph.Interaction.endPan=function(c,b,a){a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragEndX-a.dragStartX);var d=Math.abs(a.dragEndY-a.dragStartY);if(e<2&&d<2&&b.lastx_!==undefined&&b.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(b,c,a)}a.isPanning=false;a.is2DPan=false;a.initialLeftmostDate=null;a.dateRange=null;a.valueRange=null;a.boundedDates=null;a.boundedValues=null;a.axes=null};Dygraph.Interaction.startZoom=function(c,b,a){a.isZooming=true;a.zoomMoved=false};Dygraph.Interaction.moveZoom=function(c,b,a){a.zoomMoved=true;a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragStartX-a.dragEndX);var d=Math.abs(a.dragStartY-a.dragEndY);a.dragDirection=(e<d/2)?Dygraph.VERTICAL:Dygraph.HORIZONTAL;b.drawZoomRect_(a.dragDirection,a.dragStartX,a.dragEndX,a.dragStartY,a.dragEndY,a.prevDragDirection,a.prevEndX,a.prevEndY);a.prevEndX=a.dragEndX;a.prevEndY=a.dragEndY;a.prevDragDirection=a.dragDirection};Dygraph.Interaction.treatMouseOpAsClick=function(f,b,d){var k=f.attr_("clickCallback");var n=f.attr_("pointClickCallback");var j=null;if(n){var l=-1;var m=Number.MAX_VALUE;for(var e=0;e<f.selPoints_.length;e++){var c=f.selPoints_[e];var a=Math.pow(c.canvasx-d.dragEndX,2)+Math.pow(c.canvasy-d.dragEndY,2);if(!isNaN(a)&&(l==-1||a<m)){m=a;l=e}}var h=f.attr_("highlightCircleSize")+2;if(m<=h*h){j=f.selPoints_[l]}}if(j){n(b,j)}if(k){k(b,f.lastx_,f.selPoints_)}};Dygraph.Interaction.endZoom=function(c,i,e){e.isZooming=false;e.dragEndX=i.dragGetX_(c,e);e.dragEndY=i.dragGetY_(c,e);var h=Math.abs(e.dragEndX-e.dragStartX);var d=Math.abs(e.dragEndY-e.dragStartY);if(h<2&&d<2&&i.lastx_!==undefined&&i.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(i,c,e)}var b=i.getArea();if(h>=10&&e.dragDirection==Dygraph.HORIZONTAL){var f=Math.min(e.dragStartX,e.dragEndX),k=Math.max(e.dragStartX,e.dragEndX);f=Math.max(f,b.x);k=Math.min(k,b.x+b.w);if(f<k){i.doZoomX_(f,k)}e.cancelNextDblclick=true}else{if(d>=10&&e.dragDirection==Dygraph.VERTICAL){var j=Math.min(e.dragStartY,e.dragEndY),a=Math.max(e.dragStartY,e.dragEndY);j=Math.max(j,b.y);a=Math.min(a,b.y+b.h);if(j<a){i.doZoomY_(j,a)}e.cancelNextDblclick=true}else{if(e.zoomMoved){i.clearZoomRect_()}}}e.dragStartX=null;e.dragStartY=null};Dygraph.Interaction.startTouch=function(f,e,d){f.preventDefault();if(f.touches.length>1){d.startTimeForDoubleTapMs=null}var h=[];for(var c=0;c<f.touches.length;c++){var b=f.touches[c];h.push({pageX:b.pageX,pageY:b.pageY,dataX:e.toDataXCoord(b.pageX),dataY:e.toDataYCoord(b.pageY)})}d.initialTouches=h;if(h.length==1){d.initialPinchCenter=h[0];d.touchDirections={x:true,y:true}}else{if(h.length>=2){d.initialPinchCenter={pageX:0.5*(h[0].pageX+h[1].pageX),pageY:0.5*(h[0].pageY+h[1].pageY),dataX:0.5*(h[0].dataX+h[1].dataX),dataY:0.5*(h[0].dataY+h[1].dataY)};var a=180/Math.PI*Math.atan2(d.initialPinchCenter.pageY-h[0].pageY,h[0].pageX-d.initialPinchCenter.pageX);a=Math.abs(a);if(a>90){a=90-a}d.touchDirections={x:(a<(90-45/2)),y:(a>45/2)}}}d.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}};Dygraph.Interaction.moveTouch=function(n,q,d){d.startTimeForDoubleTapMs=null;var p,l=[];for(p=0;p<n.touches.length;p++){var k=n.touches[p];l.push({pageX:k.pageX,pageY:k.pageY})}var a=d.initialTouches;var h;var j=d.initialPinchCenter;if(l.length==1){h=l[0]}else{h={pageX:0.5*(l[0].pageX+l[1].pageX),pageY:0.5*(l[0].pageY+l[1].pageY)}}var m={pageX:h.pageX-j.pageX,pageY:h.pageY-j.pageY};var f=d.initialRange.x[1]-d.initialRange.x[0];var o=d.initialRange.y[0]-d.initialRange.y[1];m.dataX=(m.pageX/q.plotter_.area.w)*f;m.dataY=(m.pageY/q.plotter_.area.h)*o;var w,c;if(l.length==1){w=1;c=1}else{if(l.length>=2){var e=(a[1].pageX-j.pageX);w=(l[1].pageX-h.pageX)/e;var v=(a[1].pageY-j.pageY);c=(l[1].pageY-h.pageY)/v}}w=Math.min(8,Math.max(0.125,w));c=Math.min(8,Math.max(0.125,c));var u=false;if(d.touchDirections.x){q.dateWindow_=[j.dataX-m.dataX+(d.initialRange.x[0]-j.dataX)/w,j.dataX-m.dataX+(d.initialRange.x[1]-j.dataX)/w];u=true}if(d.touchDirections.y){for(p=0;p<1;p++){var b=q.axes_[p];var s=q.attributes_.getForAxis("logscale",p);if(s){}else{b.valueWindow=[j.dataY-m.dataY+(d.initialRange.y[0]-j.dataY)/c,j.dataY-m.dataY+(d.initialRange.y[1]-j.dataY)/c];u=true}}}q.drawGraph_(false);if(u&&l.length>1&&q.attr_("zoomCallback")){var r=q.xAxisRange();q.attr_("zoomCallback")(r[0],r[1],q.yAxisRanges())}};Dygraph.Interaction.endTouch=function(e,d,c){if(e.touches.length!==0){Dygraph.Interaction.startTouch(e,d,c)}else{if(e.changedTouches.length==1){var a=new Date().getTime();var b=e.changedTouches[0];if(c.startTimeForDoubleTapMs&&a-c.startTimeForDoubleTapMs<500&&c.doubleTapX&&Math.abs(c.doubleTapX-b.screenX)<50&&c.doubleTapY&&Math.abs(c.doubleTapY-b.screenY)<50){d.resetZoom()}else{c.startTimeForDoubleTapMs=a;c.doubleTapX=b.screenX;c.doubleTapY=b.screenY}}}};Dygraph.Interaction.defaultModel={mousedown:function(c,b,a){if(c.button&&c.button==2){return}a.initializeMouseDown(c,b,a);if(c.altKey||c.shiftKey){Dygraph.startPan(c,b,a)}else{Dygraph.startZoom(c,b,a)}},mousemove:function(c,b,a){if(a.isZooming){Dygraph.moveZoom(c,b,a)}else{if(a.isPanning){Dygraph.movePan(c,b,a)}}},mouseup:function(c,b,a){if(a.isZooming){Dygraph.endZoom(c,b,a)}else{if(a.isPanning){Dygraph.endPan(c,b,a)}}},touchstart:function(c,b,a){Dygraph.Interaction.startTouch(c,b,a)},touchmove:function(c,b,a){Dygraph.Interaction.moveTouch(c,b,a)},touchend:function(c,b,a){Dygraph.Interaction.endTouch(c,b,a)},mouseout:function(c,b,a){if(a.isZooming){a.dragEndX=null;a.dragEndY=null;b.clearZoomRect_()}},dblclick:function(c,b,a){if(a.cancelNextDblclick){a.cancelNextDblclick=false;return}if(c.altKey||c.shiftKey){return}b.resetZoom()}};Dygraph.DEFAULT_ATTRS.interactionModel=Dygraph.Interaction.defaultModel;Dygraph.defaultInteractionModel=Dygraph.Interaction.defaultModel;Dygraph.endZoom=Dygraph.Interaction.endZoom;Dygraph.moveZoom=Dygraph.Interaction.moveZoom;Dygraph.startZoom=Dygraph.Interaction.startZoom;Dygraph.endPan=Dygraph.Interaction.endPan;Dygraph.movePan=Dygraph.Interaction.movePan;Dygraph.startPan=Dygraph.Interaction.startPan;Dygraph.Interaction.nonInteractiveModel_={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a)},mouseup:function(c,b,a){a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragEndX-a.dragStartX);var d=Math.abs(a.dragEndY-a.dragStartY);if(e<2&&d<2&&b.lastx_!==undefined&&b.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(b,c,a)}}};Dygraph.Interaction.dragIsPanInteractionModel={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a);Dygraph.startPan(c,b,a)},mousemove:function(c,b,a){if(a.isPanning){Dygraph.movePan(c,b,a)}},mouseup:function(c,b,a){if(a.isPanning){Dygraph.endPan(c,b,a)}}};"use strict";Dygraph.TickList=undefined;Dygraph.Ticker=undefined;Dygraph.numericLinearTicks=function(d,c,i,g,f,h){var e=function(a){if(a==="logscale"){return false}return g(a)};return Dygraph.numericTicks(d,c,i,e,f,h)};Dygraph.numericTicks=function(F,E,u,p,d,q){var z=(p("pixelsPerLabel"));var G=[];var C,A,t,y;if(q){for(C=0;C<q.length;C++){G.push({v:q[C]})}}else{if(p("logscale")){y=Math.floor(u/z);var l=Dygraph.binarySearch(F,Dygraph.PREFERRED_LOG_TICK_VALUES,1);var H=Dygraph.binarySearch(E,Dygraph.PREFERRED_LOG_TICK_VALUES,-1);if(l==-1){l=0}if(H==-1){H=Dygraph.PREFERRED_LOG_TICK_VALUES.length-1}var s=null;if(H-l>=y/4){for(var r=H;r>=l;r--){var m=Dygraph.PREFERRED_LOG_TICK_VALUES[r];var k=Math.log(m/F)/Math.log(E/F)*u;var D={v:m};if(s===null){s={tickValue:m,pixel_coord:k}}else{if(Math.abs(k-s.pixel_coord)>=z){s={tickValue:m,pixel_coord:k}}else{D.label=""}}G.push(D)}G.reverse()}}if(G.length===0){var g=p("labelsKMG2");var n,h;if(g){n=[1,2,4,8,16,32,64,128,256];h=16}else{n=[1,2,5,10,20,50,100];h=10}var w=Math.ceil(u/z);var o=Math.abs(E-F)/w;var v=Math.floor(Math.log(o)/Math.log(h));var f=Math.pow(h,v);var I,x,c,e;for(A=0;A<n.length;A++){I=f*n[A];x=Math.floor(F/I)*I;c=Math.ceil(E/I)*I;y=Math.abs(c-x)/I;e=u/y;if(e>z){break}}if(x>c){I*=-1}for(C=0;C<y;C++){t=x+C*I;G.push({v:t})}}}var B=(p("axisLabelFormatter"));for(C=0;C<G.length;C++){if(G[C].label!==undefined){continue}G[C].label=B(G[C].v,0,p,d)}return G};Dygraph.dateTicker=function(e,c,i,g,f,h){var d=Dygraph.pickDateTickGranularity(e,c,i,g);if(d>=0){return Dygraph.getDateAxis(e,c,d,g,f)}else{return[]}};Dygraph.SECONDLY=0;Dygraph.TWO_SECONDLY=1;Dygraph.FIVE_SECONDLY=2;Dygraph.TEN_SECONDLY=3;Dygraph.THIRTY_SECONDLY=4;Dygraph.MINUTELY=5;Dygraph.TWO_MINUTELY=6;Dygraph.FIVE_MINUTELY=7;Dygraph.TEN_MINUTELY=8;Dygraph.THIRTY_MINUTELY=9;Dygraph.HOURLY=10;Dygraph.TWO_HOURLY=11;Dygraph.SIX_HOURLY=12;Dygraph.DAILY=13;Dygraph.WEEKLY=14;Dygraph.MONTHLY=15;Dygraph.QUARTERLY=16;Dygraph.BIANNUAL=17;Dygraph.ANNUAL=18;Dygraph.DECADAL=19;Dygraph.CENTENNIAL=20;Dygraph.NUM_GRANULARITIES=21;Dygraph.SHORT_SPACINGS=[];Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]=1000*1;Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY]=1000*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY]=1000*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY]=1000*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY]=1000*30;Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY]=1000*60;Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY]=1000*60*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY]=1000*60*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY]=1000*60*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY]=1000*60*30;Dygraph.SHORT_SPACINGS[Dygraph.HOURLY]=1000*3600;Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]=1000*3600*2;Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY]=1000*3600*6;Dygraph.SHORT_SPACINGS[Dygraph.DAILY]=1000*86400;Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]=1000*604800;Dygraph.LONG_TICK_PLACEMENTS=[];Dygraph.LONG_TICK_PLACEMENTS[Dygraph.MONTHLY]={months:[0,1,2,3,4,5,6,7,8,9,10,11],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.QUARTERLY]={months:[0,3,6,9],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.BIANNUAL]={months:[0,6],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.ANNUAL]={months:[0],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.DECADAL]={months:[0],year_mod:10};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.CENTENNIAL]={months:[0],year_mod:100};Dygraph.PREFERRED_LOG_TICK_VALUES=function(){var c=[];for(var b=-39;b<=39;b++){var a=Math.pow(10,b);for(var d=1;d<=9;d++){var e=a*d;c.push(e)}}return c}();Dygraph.pickDateTickGranularity=function(d,c,j,h){var g=(h("pixelsPerLabel"));for(var f=0;f<Dygraph.NUM_GRANULARITIES;f++){var e=Dygraph.numDateTicks(d,c,f);if(j/e>=g){return f}}return -1};Dygraph.numDateTicks=function(e,b,f){if(f<Dygraph.MONTHLY){var g=Dygraph.SHORT_SPACINGS[f];return Math.floor(0.5+1*(b-e)/g)}else{var d=Dygraph.LONG_TICK_PLACEMENTS[f];var c=365.2524*24*3600*1000;var a=1*(b-e)/c;return Math.floor(0.5+1*a*d.months.length/d.year_mod)}};Dygraph.getDateAxis=function(p,l,a,n,z){var w=(n("axisLabelFormatter"));var C=[];var m;if(a<Dygraph.MONTHLY){var c=Dygraph.SHORT_SPACINGS[a];var y=c/1000;var A=new Date(p);Dygraph.setDateSameTZ(A,{ms:0});var h;if(y<=60){h=A.getSeconds();Dygraph.setDateSameTZ(A,{s:h-h%y})}else{Dygraph.setDateSameTZ(A,{s:0});y/=60;if(y<=60){h=A.getMinutes();Dygraph.setDateSameTZ(A,{m:h-h%y})}else{Dygraph.setDateSameTZ(A,{m:0});y/=60;if(y<=24){h=A.getHours();A.setHours(h-h%y)}else{A.setHours(0);y/=24;if(y==7){A.setDate(A.getDate()-A.getDay())}}}}p=A.getTime();var B=new Date(p).getTimezoneOffset();var e=(c>=Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]);for(m=p;m<=l;m+=c){A=new Date(m);if(e&&A.getTimezoneOffset()!=B){var k=A.getTimezoneOffset()-B;m+=k*60*1000;A=new Date(m);B=A.getTimezoneOffset();if(new Date(m+c).getTimezoneOffset()!=B){m+=c;A=new Date(m);B=A.getTimezoneOffset()}}C.push({v:m,label:w(A,a,n,z)})}}else{var f;var r=1;if(a<Dygraph.NUM_GRANULARITIES){f=Dygraph.LONG_TICK_PLACEMENTS[a].months;r=Dygraph.LONG_TICK_PLACEMENTS[a].year_mod}else{Dygraph.warn("Span of dates is too long")}var v=new Date(p).getFullYear();var q=new Date(l).getFullYear();var b=Dygraph.zeropad;for(var u=v;u<=q;u++){if(u%r!==0){continue}for(var s=0;s<f.length;s++){var o=u+"/"+b(1+f[s])+"/01";m=Dygraph.dateStrToMillis(o);if(m<p||m>l){continue}C.push({v:m,label:w(new Date(m),a,n,z)})}}}return C};if(Dygraph&&Dygraph.DEFAULT_ATTRS&&Dygraph.DEFAULT_ATTRS.axes&&Dygraph.DEFAULT_ATTRS.axes["x"]&&Dygraph.DEFAULT_ATTRS.axes["y"]&&Dygraph.DEFAULT_ATTRS.axes["y2"]){Dygraph.DEFAULT_ATTRS.axes["x"]["ticker"]=Dygraph.dateTicker;Dygraph.DEFAULT_ATTRS.axes["y"]["ticker"]=Dygraph.numericTicks;Dygraph.DEFAULT_ATTRS.axes["y2"]["ticker"]=Dygraph.numericTicks}Dygraph.Plugins={};Dygraph.Plugins.Annotations=(function(){var a=function(){this.annotations_=[]};a.prototype.toString=function(){return"Annotations Plugin"};a.prototype.activate=function(b){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}};a.prototype.detachLabels=function(){for(var c=0;c<this.annotations_.length;c++){var b=this.annotations_[c];if(b.parentNode){b.parentNode.removeChild(b)}this.annotations_[c]=null}this.annotations_=[]};a.prototype.clearChart=function(b){this.detachLabels()};a.prototype.didDrawChart=function(v){var t=v.dygraph;var r=t.layout_.annotated_points;if(!r||r.length===0){return}var h=v.canvas.parentNode;var x={position:"absolute",fontSize:t.getOption("axisLabelFontSize")+"px",zIndex:10,overflow:"hidden"};var b=function(e,g,i){return function(y){var p=i.annotation;if(p.hasOwnProperty(e)){p[e](p,i,t,y)}else{if(t.getOption(g)){t.getOption(g)(p,i,t,y)}}}};var u=v.dygraph.plotter_.area;var q={};for(var s=0;s<r.length;s++){var l=r[s];if(l.canvasx<u.x||l.canvasx>u.x+u.w||l.canvasy<u.y||l.canvasy>u.y+u.h){continue}var w=l.annotation;var n=6;if(w.hasOwnProperty("tickHeight")){n=w.tickHeight}var j=document.createElement("div");for(var A in x){if(x.hasOwnProperty(A)){j.style[A]=x[A]}}if(!w.hasOwnProperty("icon")){j.className="dygraphDefaultAnnotation"}if(w.hasOwnProperty("cssClass")){j.className+=" "+w.cssClass}var m=w.hasOwnProperty("width")?w.width:16;var k=w.hasOwnProperty("height")?w.height:16;if(w.hasOwnProperty("icon")){var z=document.createElement("img");z.src=w.icon;z.width=m;z.height=k;j.appendChild(z)}else{if(l.annotation.hasOwnProperty("shortText")){j.appendChild(document.createTextNode(l.annotation.shortText))}}var c=l.canvasx-m/2;j.style.left=c+"px";var f=0;if(w.attachAtBottom){var d=(u.y+u.h-k-n);if(q[c]){d-=q[c]}else{q[c]=0}q[c]+=(n+k);f=d}else{f=l.canvasy-k-n}j.style.top=f+"px";j.style.width=m+"px";j.style.height=k+"px";j.title=l.annotation.text;j.style.color=t.colorsMap_[l.name];j.style.borderColor=t.colorsMap_[l.name];w.div=j;t.addAndTrackEvent(j,"click",b("clickHandler","annotationClickHandler",l,this));t.addAndTrackEvent(j,"mouseover",b("mouseOverHandler","annotationMouseOverHandler",l,this));t.addAndTrackEvent(j,"mouseout",b("mouseOutHandler","annotationMouseOutHandler",l,this));t.addAndTrackEvent(j,"dblclick",b("dblClickHandler","annotationDblClickHandler",l,this));h.appendChild(j);this.annotations_.push(j);var o=v.drawingContext;o.save();o.strokeStyle=t.colorsMap_[l.name];o.beginPath();if(!w.attachAtBottom){o.moveTo(l.canvasx,l.canvasy);o.lineTo(l.canvasx,l.canvasy-2-n)}else{var d=f+k;o.moveTo(l.canvasx,d);o.lineTo(l.canvasx,d+n)}o.closePath();o.stroke();o.restore()}};a.prototype.destroy=function(){this.detachLabels()};return a})();Dygraph.Plugins.Axes=(function(){var a=function(){this.xlabels_=[];this.ylabels_=[]};a.prototype.toString=function(){return"Axes Plugin"};a.prototype.activate=function(b){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}};a.prototype.layout=function(f){var d=f.dygraph;if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceLeft(b)}if(d.getOption("drawXAxis")){var c;if(d.getOption("xAxisHeight")){c=d.getOption("xAxisHeight")}else{c=d.getOptionForAxis("axisLabelFontSize","x")+2*d.getOption("axisTickSize")}f.reserveSpaceBottom(c)}if(d.numAxes()==2){if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceRight(b)}}else{if(d.numAxes()>2){d.error("Only two y-axes are supported at this time. (Trying to use "+d.numAxes()+")")}}};a.prototype.detachLabels=function(){function b(d){for(var c=0;c<d.length;c++){var e=d[c];if(e.parentNode){e.parentNode.removeChild(e)}}}b(this.xlabels_);b(this.ylabels_);this.xlabels_=[];this.ylabels_=[]};a.prototype.clearChart=function(b){this.detachLabels()};a.prototype.willDrawChart=function(H){var F=H.dygraph;if(!F.getOption("drawXAxis")&&!F.getOption("drawYAxis")){return}function B(e){return Math.round(e)+0.5}function A(e){return Math.round(e)-0.5}var j=H.drawingContext;var v=H.canvas.parentNode;var J=H.canvas.width;var d=H.canvas.height;var s,u,t,E,D;var C=function(e){return{position:"absolute",fontSize:F.getOptionForAxis("axisLabelFontSize",e)+"px",zIndex:10,color:F.getOptionForAxis("axisLabelColor",e),width:F.getOption("axisLabelWidth")+"px",lineHeight:"normal",overflow:"hidden"}};var p={x:C("x"),y:C("y"),y2:C("y2")};var m=function(g,x,y){var K=document.createElement("div");var e=p[y=="y2"?"y2":x];for(var r in e){if(e.hasOwnProperty(r)){K.style[r]=e[r]}}var i=document.createElement("div");i.className="dygraph-axis-label dygraph-axis-label-"+x+(y?" dygraph-axis-label-"+y:"");i.innerHTML=g;K.appendChild(i);return K};j.save();var I=F.layout_;var G=H.dygraph.plotter_.area;if(F.getOption("drawYAxis")){if(I.yticks&&I.yticks.length>0){var h=F.numAxes();for(D=0;D<I.yticks.length;D++){E=I.yticks[D];if(typeof(E)=="function"){return}u=G.x;var o=1;var f="y1";if(E[0]==1){u=G.x+G.w;o=-1;f="y2"}var k=F.getOptionForAxis("axisLabelFontSize",f);t=G.y+E[1]*G.h;s=m(E[2],"y",h==2?f:null);var z=(t-k/2);if(z<0){z=0}if(z+k+3>d){s.style.bottom="0px"}else{s.style.top=z+"px"}if(E[0]===0){s.style.left=(G.x-F.getOption("yAxisLabelWidth")-F.getOption("axisTickSize"))+"px";s.style.textAlign="right"}else{if(E[0]==1){s.style.left=(G.x+G.w+F.getOption("axisTickSize"))+"px";s.style.textAlign="left"}}s.style.width=F.getOption("yAxisLabelWidth")+"px";v.appendChild(s);this.ylabels_.push(s)}var n=this.ylabels_[0];var k=F.getOptionForAxis("axisLabelFontSize","y");var q=parseInt(n.style.top,10)+k;if(q>d-k){n.style.top=(parseInt(n.style.top,10)-k/2)+"px"}}var c;if(F.getOption("drawAxesAtZero")){var w=F.toPercentXCoord(0);if(w>1||w<0||isNaN(w)){w=0}c=B(G.x+w*G.w)}else{c=B(G.x)}j.strokeStyle=F.getOptionForAxis("axisLineColor","y");j.lineWidth=F.getOptionForAxis("axisLineWidth","y");j.beginPath();j.moveTo(c,A(G.y));j.lineTo(c,A(G.y+G.h));j.closePath();j.stroke();if(F.numAxes()==2){j.strokeStyle=F.getOptionForAxis("axisLineColor","y2");j.lineWidth=F.getOptionForAxis("axisLineWidth","y2");j.beginPath();j.moveTo(A(G.x+G.w),A(G.y));j.lineTo(A(G.x+G.w),A(G.y+G.h));j.closePath();j.stroke()}}if(F.getOption("drawXAxis")){if(I.xticks){for(D=0;D<I.xticks.length;D++){E=I.xticks[D];u=G.x+E[0]*G.w;t=G.y+G.h;s=m(E[1],"x");s.style.textAlign="center";s.style.top=(t+F.getOption("axisTickSize"))+"px";var l=(u-F.getOption("axisLabelWidth")/2);if(l+F.getOption("axisLabelWidth")>J){l=J-F.getOption("xAxisLabelWidth");s.style.textAlign="right"}if(l<0){l=0;s.style.textAlign="left"}s.style.left=l+"px";s.style.width=F.getOption("xAxisLabelWidth")+"px";v.appendChild(s);this.xlabels_.push(s)}}j.strokeStyle=F.getOptionForAxis("axisLineColor","x");j.lineWidth=F.getOptionForAxis("axisLineWidth","x");j.beginPath();var b;if(F.getOption("drawAxesAtZero")){var w=F.toPercentYCoord(0,0);if(w>1||w<0){w=1}b=A(G.y+w*G.h)}else{b=A(G.y+G.h)}j.moveTo(B(G.x),b);j.lineTo(B(G.x+G.w),b);j.closePath();j.stroke()}j.restore()};return a})();Dygraph.Plugins.ChartLabels=(function(){var c=function(){this.title_div_=null;this.xlabel_div_=null;this.ylabel_div_=null;this.y2label_div_=null};c.prototype.toString=function(){return"ChartLabels Plugin"};c.prototype.activate=function(d){return{layout:this.layout,didDrawChart:this.didDrawChart}};var b=function(d){var e=document.createElement("div");e.style.position="absolute";e.style.left=d.x+"px";e.style.top=d.y+"px";e.style.width=d.w+"px";e.style.height=d.h+"px";return e};c.prototype.detachLabels_=function(){var e=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_];for(var d=0;d<e.length;d++){var f=e[d];if(!f){continue}if(f.parentNode){f.parentNode.removeChild(f)}}this.title_div_=null;this.xlabel_div_=null;this.ylabel_div_=null;this.y2label_div_=null};var a=function(l,i,f,h,j){var d=document.createElement("div");d.style.position="absolute";if(f==1){d.style.left="0px"}else{d.style.left=i.x+"px"}d.style.top=i.y+"px";d.style.width=i.w+"px";d.style.height=i.h+"px";d.style.fontSize=(l.getOption("yLabelWidth")-2)+"px";var m=document.createElement("div");m.style.position="absolute";m.style.width=i.h+"px";m.style.height=i.w+"px";m.style.top=(i.h/2-i.w/2)+"px";m.style.left=(i.w/2-i.h/2)+"px";m.style.textAlign="center";var e="rotate("+(f==1?"-":"")+"90deg)";m.style.transform=e;m.style.WebkitTransform=e;m.style.MozTransform=e;m.style.OTransform=e;m.style.msTransform=e;if(typeof(document.documentMode)!=="undefined"&&document.documentMode<9){m.style.filter="progid:DXImageTransform.Microsoft.BasicImage(rotation="+(f==1?"3":"1")+")";m.style.left="0px";m.style.top="0px"}var k=document.createElement("div");k.className=h;k.innerHTML=j;m.appendChild(k);d.appendChild(m);return d};c.prototype.layout=function(k){this.detachLabels_();var i=k.dygraph;var m=k.chart_div;if(i.getOption("title")){var d=k.reserveSpaceTop(i.getOption("titleHeight"));this.title_div_=b(d);this.title_div_.style.textAlign="center";this.title_div_.style.fontSize=(i.getOption("titleHeight")-8)+"px";this.title_div_.style.fontWeight="bold";this.title_div_.style.zIndex=10;var f=document.createElement("div");f.className="dygraph-label dygraph-title";f.innerHTML=i.getOption("title");this.title_div_.appendChild(f);m.appendChild(this.title_div_)}if(i.getOption("xlabel")){var j=k.reserveSpaceBottom(i.getOption("xLabelHeight"));this.xlabel_div_=b(j);this.xlabel_div_.style.textAlign="center";this.xlabel_div_.style.fontSize=(i.getOption("xLabelHeight")-2)+"px";var f=document.createElement("div");f.className="dygraph-label dygraph-xlabel";f.innerHTML=i.getOption("xlabel");this.xlabel_div_.appendChild(f);m.appendChild(this.xlabel_div_)}if(i.getOption("ylabel")){var h=k.reserveSpaceLeft(0);this.ylabel_div_=a(i,h,1,"dygraph-label dygraph-ylabel",i.getOption("ylabel"));m.appendChild(this.ylabel_div_)}if(i.getOption("y2label")&&i.numAxes()==2){var l=k.reserveSpaceRight(0);this.y2label_div_=a(i,l,2,"dygraph-label dygraph-y2label",i.getOption("y2label"));m.appendChild(this.y2label_div_)}};c.prototype.didDrawChart=function(f){var d=f.dygraph;if(this.title_div_){this.title_div_.children[0].innerHTML=d.getOption("title")}if(this.xlabel_div_){this.xlabel_div_.children[0].innerHTML=d.getOption("xlabel")}if(this.ylabel_div_){this.ylabel_div_.children[0].children[0].innerHTML=d.getOption("ylabel")}if(this.y2label_div_){this.y2label_div_.children[0].children[0].innerHTML=d.getOption("y2label")}};c.prototype.clearChart=function(){};c.prototype.destroy=function(){this.detachLabels_()};return c})();Dygraph.Plugins.Grid=(function(){var a=function(){};a.prototype.toString=function(){return"Gridline Plugin"};a.prototype.activate=function(b){return{willDrawChart:this.willDrawChart}};a.prototype.willDrawChart=function(s){var q=s.dygraph;var l=s.drawingContext;var t=q.layout_;var r=s.dygraph.plotter_.area;function k(e){return Math.round(e)+0.5}function j(e){return Math.round(e)-0.5}var h,f,p,u;if(q.getOption("drawYGrid")){var o=["y","y2"];var m=[],v=[],b=[],n=[],d=[];for(var p=0;p<o.length;p++){b[p]=q.getOptionForAxis("drawGrid",o[p]);if(b[p]){m[p]=q.getOptionForAxis("gridLineColor",o[p]);v[p]=q.getOptionForAxis("gridLineWidth",o[p]);d[p]=q.getOptionForAxis("gridLinePattern",o[p]);n[p]=d[p]&&(d[p].length>=2)}}u=t.yticks;l.save();for(p=0;p<u.length;p++){var c=u[p][0];if(b[c]){if(n[c]){l.installPattern(d[c])}l.strokeStyle=m[c];l.lineWidth=v[c];h=k(r.x);f=j(r.y+u[p][1]*r.h);l.beginPath();l.moveTo(h,f);l.lineTo(h+r.w,f);l.closePath();l.stroke();if(n[c]){l.uninstallPattern()}}}l.restore()}if(q.getOption("drawXGrid")&&q.getOptionForAxis("drawGrid","x")){u=t.xticks;l.save();var d=q.getOptionForAxis("gridLinePattern","x");var n=d&&(d.length>=2);if(n){l.installPattern(d)}l.strokeStyle=q.getOptionForAxis("gridLineColor","x");l.lineWidth=q.getOptionForAxis("gridLineWidth","x");for(p=0;p<u.length;p++){h=k(r.x+u[p][0]*r.w);f=j(r.y+r.h);l.beginPath();l.moveTo(h,f);l.lineTo(h,r.y);l.closePath();l.stroke()}if(n){l.uninstallPattern()}l.restore()}};a.prototype.destroy=function(){};return a})();Dygraph.Plugins.Legend=(function(){var c=function(){this.legend_div_=null;this.is_generated_div_=false};c.prototype.toString=function(){return"Legend Plugin"};var a,d;c.prototype.activate=function(j){var m;var f=j.getOption("labelsDivWidth");var l=j.getOption("labelsDiv");if(l&&null!==l){if(typeof(l)=="string"||l instanceof String){m=document.getElementById(l)}else{m=l}}else{var i={position:"absolute",fontSize:"14px",zIndex:10,width:f+"px",top:"0px",left:(j.size().width-f-2)+"px",background:"white",lineHeight:"normal",textAlign:"left",overflow:"hidden"};Dygraph.update(i,j.getOption("labelsDivStyles"));m=document.createElement("div");m.className="dygraph-legend";for(var h in i){if(!i.hasOwnProperty(h)){continue}try{m.style[h]=i[h]}catch(k){this.warn("You are using unsupported css properties for your browser in labelsDivStyles")}}j.graphDiv.appendChild(m);this.is_generated_div_=true}this.legend_div_=m;this.one_em_width_=10;return{select:this.select,deselect:this.deselect,predraw:this.predraw,didDrawChart:this.didDrawChart}};var b=function(g){var f=document.createElement("span");f.setAttribute("style","margin: 0; padding: 0 0 0 1em; border: 0;");g.appendChild(f);var e=f.offsetWidth;g.removeChild(f);return e};c.prototype.select=function(i){var h=i.selectedX;var g=i.selectedPoints;var f=a(i.dygraph,h,g,this.one_em_width_);this.legend_div_.innerHTML=f};c.prototype.deselect=function(h){var f=b(this.legend_div_);this.one_em_width_=f;var g=a(h.dygraph,undefined,undefined,f);this.legend_div_.innerHTML=g};c.prototype.didDrawChart=function(f){this.deselect(f)};c.prototype.predraw=function(h){if(!this.is_generated_div_){return}h.dygraph.graphDiv.appendChild(this.legend_div_);var g=h.dygraph.plotter_.area;var f=h.dygraph.getOption("labelsDivWidth");this.legend_div_.style.left=g.x+g.w-f-1+"px";this.legend_div_.style.top=g.y+"px";this.legend_div_.style.width=f+"px"};c.prototype.destroy=function(){this.legend_div_=null};a=function(w,p,l,f){if(w.getOption("showLabelsOnHighlight")!==true){return""}var r,C,u,s,m;var z=w.getLabels();if(typeof(p)==="undefined"){if(w.getOption("legend")!="always"){return""}C=w.getOption("labelsSeparateLines");r="";for(u=1;u<z.length;u++){var q=w.getPropertiesForSeries(z[u]);if(!q.visible){continue}if(r!==""){r+=(C?"<br/>":" ")}m=w.getOption("strokePattern",z[u]);s=d(m,q.color,f);r+="<span style='font-weight: bold; color: "+q.color+";'>"+s+" "+z[u]+"</span>"}return r}var A=w.optionsViewForAxis_("x");var o=A("valueFormatter");r=o(p,A,z[0],w);if(r!==""){r+=":"}var v=[];var j=w.numAxes();for(u=0;u<j;u++){v[u]=w.optionsViewForAxis_("y"+(u?1+u:""))}var k=w.getOption("labelsShowZeroValues");C=w.getOption("labelsSeparateLines");var B=w.getHighlightSeries();for(u=0;u<l.length;u++){var t=l[u];if(t.yval===0&&!k){continue}if(!Dygraph.isOK(t.canvasy)){continue}if(C){r+="<br/>"}var q=w.getPropertiesForSeries(t.name);var n=v[q.axis-1];var y=n("valueFormatter");var e=y(t.yval,n,t.name,w);var h=(t.name==B)?" class='highlight'":"";r+="<span"+h+"> <b><span style='color: "+q.color+";'>"+t.name+"</span></b>: "+e+"</span>"}return r};d=function(s,h,r){var e=(/MSIE/.test(navigator.userAgent)&&!window.opera);if(e){return"—"}if(!s||s.length<=1){return'<div style="display: inline-block; position: relative; bottom: .5ex; padding-left: 1em; height: 1px; border-bottom: 2px solid '+h+';"></div>'}var l,k,f,o;var g=0,q=0;var p=[];var n;for(l=0;l<=s.length;l++){g+=s[l%s.length]}n=Math.floor(r/(g-s[0]));if(n>1){for(l=0;l<s.length;l++){p[l]=s[l]/r}q=p.length}else{n=1;for(l=0;l<s.length;l++){p[l]=s[l]/g}q=p.length+1}var m="";for(k=0;k<n;k++){for(l=0;l<q;l+=2){f=p[l%p.length];if(l<s.length){o=p[(l+1)%p.length]}else{o=0}m+='<div style="display: inline-block; position: relative; bottom: .5ex; margin-right: '+o+"em; padding-left: "+f+"em; height: 1px; border-bottom: 2px solid "+h+';"></div>'}}return m};return c})();Dygraph.Plugins.RangeSelector=(function(){var a=function(){this.isIE_=/MSIE/.test(navigator.userAgent)&&!window.opera;this.hasTouchInterface_=typeof(TouchEvent)!="undefined";this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion);this.interfaceCreated_=false};a.prototype.toString=function(){return"RangeSelector Plugin"};a.prototype.activate=function(b){this.dygraph_=b;this.isUsingExcanvas_=b.isUsingExcanvas_;if(this.getOption_("showRangeSelector")){this.createInterface_()}return{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}};a.prototype.destroy=function(){this.bgcanvas_=null;this.fgcanvas_=null;this.leftZoomHandle_=null;this.rightZoomHandle_=null;this.iePanOverlay_=null};a.prototype.getOption_=function(b){return this.dygraph_.getOption(b)};a.prototype.setDefaultOption_=function(b,c){return this.dygraph_.attrs_[b]=c};a.prototype.createInterface_=function(){this.createCanvases_();if(this.isUsingExcanvas_){this.createIEPanOverlay_()}this.createZoomHandles_();this.initInteraction_();if(this.getOption_("animatedZooms")){this.dygraph_.warn("Animated zooms and range selector are not compatible; disabling animatedZooms.");this.dygraph_.updateOptions({animatedZooms:false},true)}this.interfaceCreated_=true;this.addToGraph_()};a.prototype.addToGraph_=function(){var b=this.graphDiv_=this.dygraph_.graphDiv;b.appendChild(this.bgcanvas_);b.appendChild(this.fgcanvas_);b.appendChild(this.leftZoomHandle_);b.appendChild(this.rightZoomHandle_)};a.prototype.removeFromGraph_=function(){var b=this.graphDiv_;b.removeChild(this.bgcanvas_);b.removeChild(this.fgcanvas_);b.removeChild(this.leftZoomHandle_);b.removeChild(this.rightZoomHandle_);this.graphDiv_=null};a.prototype.reserveSpace_=function(b){if(this.getOption_("showRangeSelector")){b.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)}};a.prototype.renderStaticLayer_=function(){if(!this.updateVisibility_()){return}this.resize_();this.drawStaticLayer_()};a.prototype.renderInteractiveLayer_=function(){if(!this.updateVisibility_()||this.isChangingRange_){return}this.placeZoomHandles_();this.drawInteractiveLayer_()};a.prototype.updateVisibility_=function(){var b=this.getOption_("showRangeSelector");if(b){if(!this.interfaceCreated_){this.createInterface_()}else{if(!this.graphDiv_||!this.graphDiv_.parentNode){this.addToGraph_()}}}else{if(this.graphDiv_){this.removeFromGraph_();var c=this.dygraph_;setTimeout(function(){c.width_=0;c.resize()},1)}}return b};a.prototype.resize_=function(){function d(e,f){e.style.top=f.y+"px";e.style.left=f.x+"px";e.width=f.w;e.height=f.h;e.style.width=e.width+"px";e.style.height=e.height+"px"}var c=this.dygraph_.layout_.getPlotArea();var b=0;if(this.getOption_("drawXAxis")){b=this.getOption_("xAxisHeight")||(this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize"))}this.canvasRect_={x:c.x,y:c.y+c.h+b+4,w:c.w,h:this.getOption_("rangeSelectorHeight")};d(this.bgcanvas_,this.canvasRect_);d(this.fgcanvas_,this.canvasRect_)};a.prototype.createCanvases_=function(){this.bgcanvas_=Dygraph.createCanvas();this.bgcanvas_.className="dygraph-rangesel-bgcanvas";this.bgcanvas_.style.position="absolute";this.bgcanvas_.style.zIndex=9;this.bgcanvas_ctx_=Dygraph.getContext(this.bgcanvas_);this.fgcanvas_=Dygraph.createCanvas();this.fgcanvas_.className="dygraph-rangesel-fgcanvas";this.fgcanvas_.style.position="absolute";this.fgcanvas_.style.zIndex=9;this.fgcanvas_.style.cursor="default";this.fgcanvas_ctx_=Dygraph.getContext(this.fgcanvas_)};a.prototype.createIEPanOverlay_=function(){this.iePanOverlay_=document.createElement("div");this.iePanOverlay_.style.position="absolute";this.iePanOverlay_.style.backgroundColor="white";this.iePanOverlay_.style.filter="alpha(opacity=0)";this.iePanOverlay_.style.display="none";this.iePanOverlay_.style.cursor="move";this.fgcanvas_.appendChild(this.iePanOverlay_)};a.prototype.createZoomHandles_=function(){var b=new Image();b.className="dygraph-rangesel-zoomhandle";b.style.position="absolute";b.style.zIndex=10;b.style.visibility="hidden";b.style.cursor="col-resize";if(/MSIE 7/.test(navigator.userAgent)){b.width=7;b.height=14;b.style.backgroundColor="white";b.style.border="1px solid #333333"}else{b.width=9;b.height=16;b.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII="}if(this.isMobileDevice_){b.width*=2;b.height*=2}this.leftZoomHandle_=b;this.rightZoomHandle_=b.cloneNode(false)};a.prototype.initInteraction_=function(){var o=this;var i=this.isIE_?document:window;var u=0;var v=null;var s=false;var d=false;var g=!this.isMobileDevice_&&!this.isUsingExcanvas_;var k=new Dygraph.IFrameTarp();var p,f,r,j,w,h,x,t,q,c,l;var e,n,m;p=function(C){var B=o.dygraph_.xAxisExtremes();var z=(B[1]-B[0])/o.canvasRect_.w;var A=B[0]+(C.leftHandlePos-o.canvasRect_.x)*z;var y=B[0]+(C.rightHandlePos-o.canvasRect_.x)*z;return[A,y]};f=function(y){Dygraph.cancelEvent(y);s=true;u=y.clientX;v=y.target?y.target:y.srcElement;if(y.type==="mousedown"||y.type==="dragstart"){Dygraph.addEvent(i,"mousemove",r);Dygraph.addEvent(i,"mouseup",j)}o.fgcanvas_.style.cursor="col-resize";k.cover();return true};r=function(C){if(!s){return false}Dygraph.cancelEvent(C);var z=C.clientX-u;if(Math.abs(z)<4){return true}u=C.clientX;var B=o.getZoomHandleStatus_();var y;if(v==o.leftZoomHandle_){y=B.leftHandlePos+z;y=Math.min(y,B.rightHandlePos-v.width-3);y=Math.max(y,o.canvasRect_.x)}else{y=B.rightHandlePos+z;y=Math.min(y,o.canvasRect_.x+o.canvasRect_.w);y=Math.max(y,B.leftHandlePos+v.width+3)}var A=v.width/2;v.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){w()}return true};j=function(y){if(!s){return false}s=false;k.uncover();Dygraph.removeEvent(i,"mousemove",r);Dygraph.removeEvent(i,"mouseup",j);o.fgcanvas_.style.cursor="default";if(!g){w()}return true};w=function(){try{var z=o.getZoomHandleStatus_();o.isChangingRange_=true;if(!z.isZoomed){o.dygraph_.resetZoom()}else{var y=p(z);o.dygraph_.doZoomXDates_(y[0],y[1])}}finally{o.isChangingRange_=false}};h=function(A){if(o.isUsingExcanvas_){return A.srcElement==o.iePanOverlay_}else{var z=o.leftZoomHandle_.getBoundingClientRect();var y=z.left+z.width/2;z=o.rightZoomHandle_.getBoundingClientRect();var B=z.left+z.width/2;return(A.clientX>y&&A.clientX<B)}};x=function(y){if(!d&&h(y)&&o.getZoomHandleStatus_().isZoomed){Dygraph.cancelEvent(y);d=true;u=y.clientX;if(y.type==="mousedown"){Dygraph.addEvent(i,"mousemove",t);Dygraph.addEvent(i,"mouseup",q)}return true}return false};t=function(C){if(!d){return false}Dygraph.cancelEvent(C);var z=C.clientX-u;if(Math.abs(z)<4){return true}u=C.clientX;var B=o.getZoomHandleStatus_();var E=B.leftHandlePos;var y=B.rightHandlePos;var D=y-E;if(E+z<=o.canvasRect_.x){E=o.canvasRect_.x;y=E+D}else{if(y+z>=o.canvasRect_.x+o.canvasRect_.w){y=o.canvasRect_.x+o.canvasRect_.w;E=y-D}else{E+=z;y+=z}}var A=o.leftZoomHandle_.width/2;o.leftZoomHandle_.style.left=(E-A)+"px";o.rightZoomHandle_.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){c()}return true};q=function(y){if(!d){return false}d=false;Dygraph.removeEvent(i,"mousemove",t);Dygraph.removeEvent(i,"mouseup",q);if(!g){c()}return true};c=function(){try{o.isChangingRange_=true;o.dygraph_.dateWindow_=p(o.getZoomHandleStatus_());o.dygraph_.drawGraph_(false)}finally{o.isChangingRange_=false}};l=function(y){if(s||d){return}var z=h(y)?"move":"default";if(z!=o.fgcanvas_.style.cursor){o.fgcanvas_.style.cursor=z}};e=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(f(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(r(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{j(y)}}};n=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(x(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(t(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{q(y)}}};m=function(B,A){var z=["touchstart","touchend","touchmove","touchcancel"];for(var y=0;y<z.length;y++){o.dygraph_.addAndTrackEvent(B,z[y],A)}};this.setDefaultOption_("interactionModel",Dygraph.Interaction.dragIsPanInteractionModel);this.setDefaultOption_("panEdgeFraction",0.0001);var b=window.opera?"mousedown":"dragstart";this.dygraph_.addAndTrackEvent(this.leftZoomHandle_,b,f);this.dygraph_.addAndTrackEvent(this.rightZoomHandle_,b,f);if(this.isUsingExcanvas_){this.dygraph_.addAndTrackEvent(this.iePanOverlay_,"mousedown",x)}else{this.dygraph_.addAndTrackEvent(this.fgcanvas_,"mousedown",x);this.dygraph_.addAndTrackEvent(this.fgcanvas_,"mousemove",l)}if(this.hasTouchInterface_){m(this.leftZoomHandle_,e);m(this.rightZoomHandle_,e);m(this.fgcanvas_,n)}};a.prototype.drawStaticLayer_=function(){var b=this.bgcanvas_ctx_;b.clearRect(0,0,this.canvasRect_.w,this.canvasRect_.h);try{this.drawMiniPlot_()}catch(c){Dygraph.warn(c)}var d=0.5;this.bgcanvas_ctx_.lineWidth=1;b.strokeStyle="gray";b.beginPath();b.moveTo(d,d);b.lineTo(d,this.canvasRect_.h-d);b.lineTo(this.canvasRect_.w-d,this.canvasRect_.h-d);b.lineTo(this.canvasRect_.w-d,d);b.stroke()};a.prototype.drawMiniPlot_=function(){var f=this.getOption_("rangeSelectorPlotFillColor");var r=this.getOption_("rangeSelectorPlotStrokeColor");if(!f&&!r){return}var j=this.getOption_("stepPlot");var v=this.computeCombinedSeriesAndLimits_();var q=v.yMax-v.yMin;var p=this.bgcanvas_ctx_;var n=0.5;var e=this.dygraph_.xAxisExtremes();var o=Math.max(e[1]-e[0],1e-30);var g=(this.canvasRect_.w-n)/o;var u=(this.canvasRect_.h-n)/q;var t=this.canvasRect_.w-n;var b=this.canvasRect_.h-n;var d=null,c=null;p.beginPath();p.moveTo(n,b);for(var s=0;s<v.data.length;s++){var h=v.data[s];var l=((h[0]!==null)?((h[0]-e[0])*g):NaN);var k=((h[1]!==null)?(b-(h[1]-v.yMin)*u):NaN);if(isFinite(l)&&isFinite(k)){if(d===null){p.lineTo(l,b)}else{if(j){p.lineTo(l,c)}}p.lineTo(l,k);d=l;c=k}else{if(d!==null){if(j){p.lineTo(l,c);p.lineTo(l,b)}else{p.lineTo(d,b)}}d=c=null}}p.lineTo(t,b);p.closePath();if(f){var m=this.bgcanvas_ctx_.createLinearGradient(0,0,0,b);m.addColorStop(0,"white");m.addColorStop(1,f);this.bgcanvas_ctx_.fillStyle=m;p.fill()}if(r){this.bgcanvas_ctx_.strokeStyle=r;this.bgcanvas_ctx_.lineWidth=1.5;p.stroke()}};a.prototype.computeCombinedSeriesAndLimits_=function(){var v=this.dygraph_.rawData_;var u=this.getOption_("logscale");var q=[];var d;var h;var m;var t,s,r;var e,g;for(t=0;t<v.length;t++){if(v[t].length>1&&v[t][1]!==null){m=typeof v[t][1]!="number";if(m){d=[];h=[];for(r=0;r<v[t][1].length;r++){d.push(0);h.push(0)}}break}}for(t=0;t<v.length;t++){var l=v[t];e=l[0];if(m){for(r=0;r<d.length;r++){d[r]=h[r]=0}}else{d=h=0}for(s=1;s<l.length;s++){if(this.dygraph_.visibility()[s-1]){var n;if(m){for(r=0;r<d.length;r++){n=l[s][r];if(n===null||isNaN(n)){continue}d[r]+=n;h[r]++}}else{n=l[s];if(n===null||isNaN(n)){continue}d+=n;h++}}}if(m){for(r=0;r<d.length;r++){d[r]/=h[r]}g=d.slice(0)}else{g=d/h}q.push([e,g])}q=this.dygraph_.rollingAverage(q,this.dygraph_.rollPeriod_);if(typeof q[0][1]!="number"){for(t=0;t<q.length;t++){g=q[t][1];q[t][1]=g[0]}}var b=Number.MAX_VALUE;var c=-Number.MAX_VALUE;for(t=0;t<q.length;t++){g=q[t][1];if(g!==null&&isFinite(g)&&(!u||g>0)){b=Math.min(b,g);c=Math.max(c,g)}}var o=0.25;if(u){c=Dygraph.log10(c);c+=c*o;b=Dygraph.log10(b);for(t=0;t<q.length;t++){q[t][1]=Dygraph.log10(q[t][1])}}else{var f;var p=c-b;if(p<=Number.MIN_VALUE){f=c*o}else{f=p*o}c+=f;b-=f}return{data:q,yMin:b,yMax:c}};a.prototype.placeZoomHandles_=function(){var h=this.dygraph_.xAxisExtremes();var b=this.dygraph_.xAxisRange();var c=h[1]-h[0];var j=Math.max(0,(b[0]-h[0])/c);var f=Math.max(0,(h[1]-b[1])/c);var i=this.canvasRect_.x+this.canvasRect_.w*j;var e=this.canvasRect_.x+this.canvasRect_.w*(1-f);var d=Math.max(this.canvasRect_.y,this.canvasRect_.y+(this.canvasRect_.h-this.leftZoomHandle_.height)/2);var g=this.leftZoomHandle_.width/2;this.leftZoomHandle_.style.left=(i-g)+"px";this.leftZoomHandle_.style.top=d+"px";this.rightZoomHandle_.style.left=(e-g)+"px";this.rightZoomHandle_.style.top=this.leftZoomHandle_.style.top;this.leftZoomHandle_.style.visibility="visible";this.rightZoomHandle_.style.visibility="visible"};a.prototype.drawInteractiveLayer_=function(){var c=this.fgcanvas_ctx_;c.clearRect(0,0,this.canvasRect_.w,this.canvasRect_.h);var f=1;var e=this.canvasRect_.w-f;var b=this.canvasRect_.h-f;var h=this.getZoomHandleStatus_();c.strokeStyle="black";if(!h.isZoomed){c.beginPath();c.moveTo(f,f);c.lineTo(f,b);c.lineTo(e,b);c.lineTo(e,f);c.stroke();if(this.iePanOverlay_){this.iePanOverlay_.style.display="none"}}else{var g=Math.max(f,h.leftHandlePos-this.canvasRect_.x);var d=Math.min(e,h.rightHandlePos-this.canvasRect_.x);c.fillStyle="rgba(240, 240, 240, 0.6)";c.fillRect(0,0,g,this.canvasRect_.h);c.fillRect(d,0,this.canvasRect_.w-d,this.canvasRect_.h);c.beginPath();c.moveTo(f,f);c.lineTo(g,f);c.lineTo(g,b);c.lineTo(d,b);c.lineTo(d,f);c.lineTo(e,f);c.stroke();if(this.isUsingExcanvas_){this.iePanOverlay_.style.width=(d-g)+"px";this.iePanOverlay_.style.left=g+"px";this.iePanOverlay_.style.height=b+"px";this.iePanOverlay_.style.display="inline"}}};a.prototype.getZoomHandleStatus_=function(){var c=this.leftZoomHandle_.width/2;var d=parseFloat(this.leftZoomHandle_.style.left)+c;var b=parseFloat(this.rightZoomHandle_.style.left)+c;return{leftHandlePos:d,rightHandlePos:b,isZoomed:(d-1>this.canvasRect_.x||b+1<this.canvasRect_.x+this.canvasRect_.w)}};return a})();Dygraph.PLUGINS.push(Dygraph.Plugins.Legend,Dygraph.Plugins.Axes,Dygraph.Plugins.RangeSelector,Dygraph.Plugins.ChartLabels,Dygraph.Plugins.Annotations,Dygraph.Plugins.Grid);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-combined_bak_color.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-combined_bak_color.js new file mode 100644 index 00000000..2f7b14b8 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-combined_bak_color.js @@ -0,0 +1,2 @@ +/*! @license Copyright 2011 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */ +Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(a,c,b){if(typeof(b)=="undefined"){b=10}for(;parseInt(a,10)<b&&b>1;b/=10){a=c.toString()+a}return a.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(a){return Date.ext.locales[a.locale].a[a.getDay()]},A:function(a){return Date.ext.locales[a.locale].A[a.getDay()]},b:function(a){return Date.ext.locales[a.locale].b[a.getMonth()]},B:function(a){return Date.ext.locales[a.locale].B[a.getMonth()]},c:"toLocaleString",C:function(a){return Date.ext.util.xPad(parseInt(a.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(a){return Date.ext.util.xPad(parseInt(Date.ext.util.G(a)/100,10),0)},G:function(c){var e=c.getFullYear();var b=parseInt(Date.ext.formats.V(c),10);var a=parseInt(Date.ext.formats.W(c),10);if(a>b){e++}else{if(a===0&&b>=52){e--}}return e},H:["getHours","0"],I:function(b){var a=b.getHours()%12;return Date.ext.util.xPad(a===0?12:a,0)},j:function(c){var a=c-new Date(""+c.getFullYear()+"/1/1 GMT");a+=c.getTimezoneOffset()*60000;var b=parseInt(a/60000/60/24,10)+1;return Date.ext.util.xPad(b,0,100)},m:function(a){return Date.ext.util.xPad(a.getMonth()+1,0)},M:["getMinutes","0"],p:function(a){return Date.ext.locales[a.locale].p[a.getHours()>=12?1:0]},P:function(a){return Date.ext.locales[a.locale].P[a.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(a){var b=a.getDay();return b===0?7:b},U:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=6-e.getDay();var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0)},V:function(e){var c=parseInt(Date.ext.formats.W(e),10);var a=(new Date(""+e.getFullYear()+"/1/1")).getDay();var b=c+(a>4||a<=1?0:1);if(b==53&&(new Date(""+e.getFullYear()+"/12/31")).getDay()<4){b=1}else{if(b===0){b=Date.ext.formats.V(new Date(""+(e.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(b,0)},w:"getDay",W:function(e){var a=parseInt(Date.ext.formats.j(e),10);var c=7-Date.ext.formats.u(e);var b=parseInt((a+c)/7,10);return Date.ext.util.xPad(b,0,10)},y:function(a){return Date.ext.util.xPad(a.getFullYear()%100,0)},Y:"getFullYear",z:function(c){var b=c.getTimezoneOffset();var a=Date.ext.util.xPad(parseInt(Math.abs(b/60),10),0);var e=Date.ext.util.xPad(b%60,0);return(b>0?"-":"+")+a+e},Z:function(a){return a.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(a){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(a){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var c=this;while(a.match(/%[cDhnrRtTxXzZ]/)){a=a.replace(/%([cDhnrRtTxXzZ])/g,function(e,d){var g=Date.ext.aggregates[d];return(g=="locale"?Date.ext.locales[c.locale][d]:g)})}var b=a.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(e,d){var g=Date.ext.formats[d];if(typeof(g)=="string"){return c[g]()}else{if(typeof(g)=="function"){return g.call(c,c)}else{if(typeof(g)=="object"&&typeof(g[0])=="string"){return Date.ext.util.xPad(c[g[0]](),g[1])}else{return d}}}});c=null;return b};"use strict";function RGBColorParser(f){this.ok=false;if(f.charAt(0)=="#"){f=f.substr(1,6)}f=f.replace(/ /g,"");f=f.toLowerCase();var b={dodgerBlue:"#1f77b4",vividOrange:"#ff7f0e",forestGreen:"#2ca02c",greenishRed:"#8c864b",desaturatedViolet:"#9467bd",darkModerateRed:"#8c564b",softPink:"#e377c2",darkGray:"#7f7f7f",strongYellow:"#bcbd22",strongCyan:"#17becf",vividRed:"#dc143c",darkMagenta:"#800080",blue:"#0000FF",darkLimeGreen:"#008000",reddishOrange:"#D2691E",red:"#FF0000",black:"#000000",pink:"#DB7093",pureMagenta:"#FF00FF",softBlue:"#7B68EE",strongBlue:"#1f77b6",verySoftCyan:"#9edae5",darkBlue:"#393b79",darkModerateBlue:"#5254a3",slightlyDesaturatedBlue:"#6b6ecf",verySoftBlue:"#9c9ede",darkGreen:"#637939",darkModerateGreen:"#8ca252",slightlyDesaturatedGreen:"#b5cf6b",desaturatedGreen:"#cedb9c",aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};for(var g in b){if(f==g){f=b[g]}}var e=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(i){return[parseInt(i[1]),parseInt(i[2]),parseInt(i[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(i){return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(i){return[parseInt(i[1]+i[1],16),parseInt(i[2]+i[2],16),parseInt(i[3]+i[3],16)]}}];for(var c=0;c<e.length;c++){var j=e[c].re;var a=e[c].process;var h=j.exec(f);if(h){var d=a(h);this.r=d[0];this.g=d[1];this.b=d[2];this.ok=true}}this.r=(this.r<0||isNaN(this.r))?0:((this.r>255)?255:this.r);this.g=(this.g<0||isNaN(this.g))?0:((this.g>255)?255:this.g);this.b=(this.b<0||isNaN(this.b))?0:((this.b>255)?255:this.b);this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var l=this.r.toString(16);var k=this.g.toString(16);var i=this.b.toString(16);if(l.length==1){l="0"+l}if(k.length==1){k="0"+k}if(i.length==1){i="0"+i}return"#"+l+k+i}}function printStackTrace(b){b=b||{guess:true};var c=b.e||null,e=!!b.guess;var d=new printStackTrace.implementation(),a=d.run(c);return(e)?d.guessAnonymousFunctions(a):a}printStackTrace.implementation=function(){};printStackTrace.implementation.prototype={run:function(a,b){a=a||this.createException();b=b||this.mode(a);if(b==="other"){return this.other(arguments.callee)}else{return this[b](a)}},createException:function(){try{this.undef()}catch(a){return a}},mode:function(a){if(a["arguments"]&&a.stack){return"chrome"}else{if(typeof a.message==="string"&&typeof window!=="undefined"&&window.opera){if(!a.stacktrace){return"opera9"}if(a.message.indexOf("\n")>-1&&a.message.split("\n").length>a.stacktrace.split("\n").length){return"opera9"}if(!a.stack){return"opera10a"}if(a.stacktrace.indexOf("called from line")<0){return"opera10b"}return"opera11"}else{if(a.stack){return"firefox"}}}return"other"},instrumentFunction:function(b,d,e){b=b||window;var a=b[d];b[d]=function c(){e.call(this,printStackTrace().slice(4));return b[d]._instrumented.apply(this,arguments)};b[d]._instrumented=a},deinstrumentFunction:function(a,b){if(a[b].constructor===Function&&a[b]._instrumented&&a[b]._instrumented.constructor===Function){a[b]=a[b]._instrumented}},chrome:function(b){var a=(b.stack+"\n").replace(/^\S[^\(]+?[\n$]/gm,"").replace(/^\s+at\s+/gm,"").replace(/^([^\(]+?)([\n$])/gm,"{anonymous}()@$1$2").replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm,"{anonymous}()@$1").split("\n");a.pop();return a},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^\(/gm,"{anonymous}(").split("\n")},opera11:function(g){var a="{anonymous}",h=/^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;var k=g.stacktrace.split("\n"),l=[];for(var c=0,f=k.length;c<f;c+=2){var d=h.exec(k[c]);if(d){var j=d[4]+":"+d[1]+":"+d[2];var b=d[3]||"global code";b=b.replace(/<anonymous function: (\S+)>/,"$1").replace(/<anonymous function>/,a);l.push(b+"@"+j+" -- "+k[c+1].replace(/^\s+/,""))}}return l},opera10b:function(g){var a="{anonymous}",h=/^(.*)@(.+):(\d+)$/;var j=g.stacktrace.split("\n"),k=[];for(var c=0,f=j.length;c<f;c++){var d=h.exec(j[c]);if(d){var b=d[1]?(d[1]+"()"):"global code";k.push(b+"@"+d[2]+":"+d[3])}}return k},opera10a:function(g){var a="{anonymous}",h=/Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;var j=g.stacktrace.split("\n"),k=[];for(var c=0,f=j.length;c<f;c+=2){var d=h.exec(j[c]);if(d){var b=d[3]||a;k.push(b+"()@"+d[2]+":"+d[1]+" -- "+j[c+1].replace(/^\s+/,""))}}return k},opera9:function(j){var d="{anonymous}",h=/Line (\d+).*script (?:in )?(\S+)/i;var c=j.message.split("\n"),b=[];for(var g=2,a=c.length;g<a;g+=2){var f=h.exec(c[g]);if(f){b.push(d+"()@"+f[2]+":"+f[1]+" -- "+c[g+1].replace(/^\s+/,""))}}return b},other:function(g){var b="{anonymous}",f=/function\s*([\w\-$]+)?\s*\(/i,a=[],d,c,e=10;while(g&&a.length<e){d=f.test(g.toString())?RegExp.$1||b:b;c=Array.prototype.slice.call(g["arguments"]||[]);a[a.length]=d+"("+this.stringifyArguments(c)+")";g=g.caller}return a},stringifyArguments:function(c){var b=[];var e=Array.prototype.slice;for(var d=0;d<c.length;++d){var a=c[d];if(a===undefined){b[d]="undefined"}else{if(a===null){b[d]="null"}else{if(a.constructor){if(a.constructor===Array){if(a.length<3){b[d]="["+this.stringifyArguments(a)+"]"}else{b[d]="["+this.stringifyArguments(e.call(a,0,1))+"..."+this.stringifyArguments(e.call(a,-1))+"]"}}else{if(a.constructor===Object){b[d]="#object"}else{if(a.constructor===Function){b[d]="#function"}else{if(a.constructor===String){b[d]='"'+a+'"'}else{if(a.constructor===Number){b[d]=a}}}}}}}}}return b.join(",")},sourceCache:{},ajax:function(a){var b=this.createXMLHTTPObject();if(b){try{b.open("GET",a,false);b.send(null);return b.responseText}catch(c){}}return""},createXMLHTTPObject:function(){var c,a=[function(){return new XMLHttpRequest()},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml3.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")}];for(var b=0;b<a.length;b++){try{c=a[b]();this.createXMLHTTPObject=a[b];return c}catch(d){}}},isSameDomain:function(a){return a.indexOf(location.hostname)!==-1},getSource:function(a){if(!(a in this.sourceCache)){this.sourceCache[a]=this.ajax(a).split("\n")}return this.sourceCache[a]},guessAnonymousFunctions:function(k){for(var g=0;g<k.length;++g){var f=/\{anonymous\}\(.*\)@(.*)/,l=/^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,b=k[g],c=f.exec(b);if(c){var e=l.exec(c[1]),d=e[1],a=e[2],j=e[3]||0;if(d&&this.isSameDomain(d)&&a){var h=this.guessAnonymousFunction(d,a,j);k[g]=b.replace("{anonymous}",h)}}}return k},guessAnonymousFunction:function(c,f,a){var b;try{b=this.findFunctionName(this.getSource(c),f)}catch(d){b="getSource failed with url: "+c+", exception: "+d.toString()}return b},findFunctionName:function(a,e){var g=/function\s+([^(]*?)\s*\(([^)]*)\)/;var k=/['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/;var h=/['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/;var b="",l,j=Math.min(e,20),d,c;for(var f=0;f<j;++f){l=a[e-f-1];c=l.indexOf("//");if(c>=0){l=l.substr(0,c)}if(l){b=l+b;d=k.exec(b);if(d&&d[1]){return d[1]}d=g.exec(b);if(d&&d[1]){return d[1]}d=h.exec(b);if(d&&d[1]){return d[1]}}}return"(?)"}};CanvasRenderingContext2D.prototype.installPattern=function(e){if(typeof(this.isPatternInstalled)!=="undefined"){throw"Must un-install old line pattern before installing a new one."}this.isPatternInstalled=true;var g=[0,0];var b=[];var f=this.beginPath;var d=this.lineTo;var c=this.moveTo;var a=this.stroke;this.uninstallPattern=function(){this.beginPath=f;this.lineTo=d;this.moveTo=c;this.stroke=a;this.uninstallPattern=undefined;this.isPatternInstalled=undefined};this.beginPath=function(){b=[];f.call(this)};this.moveTo=function(h,i){b.push([[h,i]]);c.call(this,h,i)};this.lineTo=function(h,j){var i=b[b.length-1];i.push([h,j])};this.stroke=function(){if(b.length===0){a.call(this);return}for(var p=0;p<b.length;p++){var o=b[p];var l=o[0][0],u=o[0][1];for(var n=1;n<o.length;n++){var h=o[n][0],s=o[n][1];this.save();var w=(h-l);var v=(s-u);var r=Math.sqrt(w*w+v*v);var k=Math.atan2(v,w);this.translate(l,u);c.call(this,0,0);this.rotate(k);var m=g[0];var t=0;while(r>t){var q=e[m];if(g[1]){t+=g[1]}else{t+=q}if(t>r){g=[m,t-r];t=r}else{g=[(m+1)%e.length,0]}if(m%2===0){d.call(this,t,0)}else{c.call(this,t,0)}m=(m+1)%e.length}this.restore();l=h,u=s}}a.call(this);b=[]}};CanvasRenderingContext2D.prototype.uninstallPattern=function(){throw"Must install a line pattern before uninstalling it."};var DygraphOptions=(function(){var a=function(b){this.dygraph_=b;this.yAxes_=[];this.xAxis_={};this.series_={};this.global_=this.dygraph_.attrs_;this.user_=this.dygraph_.user_attrs_||{};this.labels_=[];this.highlightSeries_=this.get("highlightSeriesOpts")||{};this.reparseSeries()};a.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1};a.axisToIndex_=function(b){if(typeof(b)=="string"){if(a.AXIS_STRING_MAPPINGS_.hasOwnProperty(b)){return a.AXIS_STRING_MAPPINGS_[b]}throw"Unknown axis : "+b}if(typeof(b)=="number"){if(b===0||b===1){return b}throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(b){throw"Unknown axis : "+b}return 0};a.prototype.reparseSeries=function(){var g=this.get("labels");if(!g){return}this.labels_=g.slice(1);this.yAxes_=[{series:[],options:{}}];this.xAxis_={options:{}};this.series_={};var h=!this.user_.series;if(h){var c=0;for(var j=0;j<this.labels_.length;j++){var i=this.labels_[j];var e=this.user_[i]||{};var b=0;var d=e.axis;if(typeof(d)=="object"){b=++c;this.yAxes_[b]={series:[i],options:d}}if(!d){this.yAxes_[0].series.push(i)}this.series_[i]={idx:j,yAxis:b,options:e}}for(var j=0;j<this.labels_.length;j++){var i=this.labels_[j];var e=this.series_[i]["options"];var d=e.axis;if(typeof(d)=="string"){if(!this.series_.hasOwnProperty(d)){Dygraph.error("Series "+i+" wants to share a y-axis with series "+d+", which does not define its own axis.");return}var b=this.series_[d].yAxis;this.series_[i].yAxis=b;this.yAxes_[b].series.push(i)}}}else{for(var j=0;j<this.labels_.length;j++){var i=this.labels_[j];var e=this.user_.series[i]||{};var b=a.axisToIndex_(e.axis);this.series_[i]={idx:j,yAxis:b,options:e};if(!this.yAxes_[b]){this.yAxes_[b]={series:[i],options:{}}}else{this.yAxes_[b].series.push(i)}}}var f=this.user_.axes||{};Dygraph.update(this.yAxes_[0].options,f.y||{});if(this.yAxes_.length>1){Dygraph.update(this.yAxes_[1].options,f.y2||{})}Dygraph.update(this.xAxis_.options,f.x||{})};a.prototype.get=function(c){var b=this.getGlobalUser_(c);if(b!==null){return b}return this.getGlobalDefault_(c)};a.prototype.getGlobalUser_=function(b){if(this.user_.hasOwnProperty(b)){return this.user_[b]}return null};a.prototype.getGlobalDefault_=function(b){if(this.global_.hasOwnProperty(b)){return this.global_[b]}if(Dygraph.DEFAULT_ATTRS.hasOwnProperty(b)){return Dygraph.DEFAULT_ATTRS[b]}return null};a.prototype.getForAxis=function(d,e){var f;var i;if(typeof(e)=="number"){f=e;i=f===0?"y":"y2"}else{if(e=="y1"){e="y"}if(e=="y"){f=0}else{if(e=="y2"){f=1}else{if(e=="x"){f=-1}else{throw"Unknown axis "+e}}}i=e}var g=(f==-1)?this.xAxis_:this.yAxes_[f];if(g){var h=g.options;if(h.hasOwnProperty(d)){return h[d]}}var c=this.getGlobalUser_(d);if(c!==null){return c}var b=Dygraph.DEFAULT_ATTRS.axes[i];if(b.hasOwnProperty(d)){return b[d]}return this.getGlobalDefault_(d)};a.prototype.getForSeries=function(c,e){if(e===this.dygraph_.getHighlightSeries()){if(this.highlightSeries_.hasOwnProperty(c)){return this.highlightSeries_[c]}}if(!this.series_.hasOwnProperty(e)){throw"Unknown series: "+e}var d=this.series_[e];var b=d.options;if(b.hasOwnProperty(c)){return b[c]}return this.getForAxis(c,d.yAxis)};a.prototype.numAxes=function(){return this.yAxes_.length};a.prototype.axisForSeries=function(b){return this.series_[b].yAxis};a.prototype.axisOptions=function(b){return this.yAxes_[b].options};a.prototype.seriesForAxis=function(b){return this.yAxes_[b].series};a.prototype.seriesNames=function(){return this.labels_};return a})();"use strict";var DygraphLayout=function(a){this.dygraph_=a;this.points=[];this.setNames=[];this.annotations=[];this.yAxes_=null;this.xTicks_=null;this.yTicks_=null};DygraphLayout.prototype.attr_=function(a){return this.dygraph_.attr_(a)};DygraphLayout.prototype.addDataset=function(a,b){this.points.push(b);this.setNames.push(a)};DygraphLayout.prototype.getPlotArea=function(){return this.area_};DygraphLayout.prototype.computePlotArea=function(){var a={x:0,y:0};a.w=this.dygraph_.width_-a.x-this.attr_("rightGap");a.h=this.dygraph_.height_;var b={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(c){var d={x:a.x,y:a.y,w:c,h:a.h};a.x+=c;a.w-=c;return d},reserveSpaceRight:function(c){var d={x:a.x+a.w-c,y:a.y,w:c,h:a.h};a.w-=c;return d},reserveSpaceTop:function(c){var d={x:a.x,y:a.y,w:a.w,h:c};a.y+=c;a.h-=c;return d},reserveSpaceBottom:function(c){var d={x:a.x,y:a.y+a.h-c,w:a.w,h:c};a.h-=c;return d},chartRect:function(){return{x:a.x,y:a.y,w:a.w,h:a.h}}};this.dygraph_.cascadeEvents_("layout",b);this.area_=a};DygraphLayout.prototype.setAnnotations=function(d){this.annotations=[];var e=this.attr_("xValueParser")||function(a){return a};for(var c=0;c<d.length;c++){var b={};if(!d[c].xval&&d[c].x===undefined){this.dygraph_.error("Annotations must have an 'x' property");return}if(d[c].icon&&!(d[c].hasOwnProperty("width")&&d[c].hasOwnProperty("height"))){this.dygraph_.error("Must set width and height when setting annotation.icon property");return}Dygraph.update(b,d[c]);if(!b.xval){b.xval=e(b.x)}this.annotations.push(b)}};DygraphLayout.prototype.setXTicks=function(a){this.xTicks_=a};DygraphLayout.prototype.setYAxes=function(a){this.yAxes_=a};DygraphLayout.prototype.evaluate=function(){this._evaluateLimits();this._evaluateLineCharts();this._evaluateLineTicks();this._evaluateAnnotations()};DygraphLayout.prototype._evaluateLimits=function(){var a=this.dygraph_.xAxisRange();this.minxval=a[0];this.maxxval=a[1];var d=a[1]-a[0];this.xscale=(d!==0?1/d:1);for(var b=0;b<this.yAxes_.length;b++){var c=this.yAxes_[b];c.minyval=c.computedValueRange[0];c.maxyval=c.computedValueRange[1];c.yrange=c.maxyval-c.minyval;c.yscale=(c.yrange!==0?1/c.yrange:1);if(c.g.attr_("logscale")){c.ylogrange=Dygraph.log10(c.maxyval)-Dygraph.log10(c.minyval);c.ylogscale=(c.ylogrange!==0?1/c.ylogrange:1);if(!isFinite(c.ylogrange)||isNaN(c.ylogrange)){c.g.error("axis "+b+" of graph at "+c.g+" can't be displayed in log scale for range ["+c.minyval+" - "+c.maxyval+"]")}}}};DygraphLayout._calcYNormal=function(b,c,a){if(a){return 1-((Dygraph.log10(c)-Dygraph.log10(b.minyval))*b.ylogscale)}else{return 1-((c-b.minyval)*b.yscale)}};DygraphLayout.prototype._evaluateLineCharts=function(){var c=this.attr_("connectSeparatedPoints");var k=this.attr_("stackedGraph");var e=this.attr_("errorBars")||this.attr_("customBars");for(var a=0;a<this.points.length;a++){var l=this.points[a];var f=this.setNames[a];var b=this.dygraph_.axisPropertiesForSeries(f);var g=this.dygraph_.attributes_.getForSeries("logscale",f);for(var d=0;d<l.length;d++){var i=l[d];i.x=(i.xval-this.minxval)*this.xscale;var h=i.yval;if(k){i.y_stacked=DygraphLayout._calcYNormal(b,i.yval_stacked,g);if(h!==null&&!isNaN(h)){h=i.yval_stacked}}if(h===null){h=NaN;if(!c){i.yval=NaN}}i.y=DygraphLayout._calcYNormal(b,h,g);if(e){i.y_top=DygraphLayout._calcYNormal(b,h-i.yval_minus,g);i.y_bottom=DygraphLayout._calcYNormal(b,h+i.yval_plus,g)}}}};DygraphLayout.parseFloat_=function(a){if(a===null){return NaN}return a};DygraphLayout.prototype._evaluateLineTicks=function(){var d,c,b,f;this.xticks=[];for(d=0;d<this.xTicks_.length;d++){c=this.xTicks_[d];b=c.label;f=this.xscale*(c.v-this.minxval);if((f>=0)&&(f<=1)){this.xticks.push([f,b])}}this.yticks=[];for(d=0;d<this.yAxes_.length;d++){var e=this.yAxes_[d];for(var a=0;a<e.ticks.length;a++){c=e.ticks[a];b=c.label;f=this.dygraph_.toPercentYCoord(c.v,d);if((f>=0)&&(f<=1)){this.yticks.push([d,f,b])}}}};DygraphLayout.prototype._evaluateAnnotations=function(){var d;var g={};for(d=0;d<this.annotations.length;d++){var b=this.annotations[d];g[b.xval+","+b.series]=b}this.annotated_points=[];if(!this.annotations||!this.annotations.length){return}for(var h=0;h<this.points.length;h++){var e=this.points[h];for(d=0;d<e.length;d++){var f=e[d];var c=f.xval+","+f.name;if(c in g){f.annotation=g[c];this.annotated_points.push(f)}}}};DygraphLayout.prototype.removeAllDatasets=function(){delete this.points;delete this.setNames;delete this.setPointsLengths;delete this.setPointsOffsets;this.points=[];this.setNames=[];this.setPointsLengths=[];this.setPointsOffsets=[]};"use strict";var DygraphCanvasRenderer=function(d,c,b,e){this.dygraph_=d;this.layout=e;this.element=c;this.elementContext=b;this.container=this.element.parentNode;this.height=this.element.height;this.width=this.element.width;if(!this.isIE&&!(DygraphCanvasRenderer.isSupported(this.element))){throw"Canvas is not supported."}this.area=e.getPlotArea();this.container.style.position="relative";this.container.style.width=this.width+"px";if(this.dygraph_.isUsingExcanvas_){this._createIEClipArea()}else{if(!Dygraph.isAndroid()){var a=this.dygraph_.canvas_ctx_;a.beginPath();a.rect(this.area.x,this.area.y,this.area.w,this.area.h);a.clip();a=this.dygraph_.hidden_ctx_;a.beginPath();a.rect(this.area.x,this.area.y,this.area.w,this.area.h);a.clip()}}};DygraphCanvasRenderer.prototype.attr_=function(a,b){return this.dygraph_.attr_(a,b)};DygraphCanvasRenderer.prototype.clear=function(){var a;if(this.isIE){try{if(this.clearDelay){this.clearDelay.cancel();this.clearDelay=null}a=this.elementContext}catch(b){return}}a=this.elementContext;a.clearRect(0,0,this.width,this.height)};DygraphCanvasRenderer.isSupported=function(f){var b=null;try{if(typeof(f)=="undefined"||f===null){b=document.createElement("canvas")}else{b=f}b.getContext("2d")}catch(c){var d=navigator.appVersion.match(/MSIE (\d\.\d)/);var a=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);if((!d)||(d[1]<6)||(a)){return false}return true}return true};DygraphCanvasRenderer.prototype.render=function(){this._updatePoints();this._renderLineChart()};DygraphCanvasRenderer.prototype._createIEClipArea=function(){var g="dygraph-clip-div";var f=this.dygraph_.graphDiv;for(var e=f.childNodes.length-1;e>=0;e--){if(f.childNodes[e].className==g){f.removeChild(f.childNodes[e])}}var c=document.bgColor;var d=this.dygraph_.graphDiv;while(d!=document){var a=d.currentStyle.backgroundColor;if(a&&a!="transparent"){c=a;break}d=d.parentNode}function b(j){if(j.w===0||j.h===0){return}var i=document.createElement("div");i.className=g;i.style.backgroundColor=c;i.style.position="absolute";i.style.left=j.x+"px";i.style.top=j.y+"px";i.style.width=j.w+"px";i.style.height=j.h+"px";f.appendChild(i)}var h=this.area;b({x:0,y:0,w:h.x,h:this.height});b({x:h.x,y:0,w:this.width-h.x,h:h.y});b({x:h.x+h.w,y:0,w:this.width-h.x-h.w,h:this.height});b({x:h.x,y:h.y+h.h,w:this.width-h.x,h:this.height-h.h-h.y})};DygraphCanvasRenderer._getIteratorPredicate=function(a){return a?DygraphCanvasRenderer._predicateThatSkipsEmptyPoints:null};DygraphCanvasRenderer._predicateThatSkipsEmptyPoints=function(b,a){return b[a].yval!==null};DygraphCanvasRenderer._drawStyledLine=function(i,a,m,q,f,n,d){var h=i.dygraph;var c=h.getOption("stepPlot",i.setName);if(!Dygraph.isArrayLike(q)){q=null}var l=h.getOption("drawGapEdgePoints",i.setName);var o=i.points;var k=Dygraph.createIterator(o,0,o.length,DygraphCanvasRenderer._getIteratorPredicate(h.getOption("connectSeparatedPoints")));var j=q&&(q.length>=2);var p=i.drawingContext;p.save();if(j){p.installPattern(q)}var b=DygraphCanvasRenderer._drawSeries(i,k,m,d,f,l,c,a);DygraphCanvasRenderer._drawPointsOnLine(i,b,n,a,d);if(j){p.uninstallPattern()}p.restore()};DygraphCanvasRenderer._drawSeries=function(v,t,m,h,p,s,g,q){var b=null;var w=null;var k=null;var j;var o;var l=[];var f=true;var n=v.drawingContext;n.beginPath();n.strokeStyle=q;n.lineWidth=m;var c=t.array_;var u=t.end_;var a=t.predicate_;for(var r=t.start_;r<u;r++){o=c[r];if(a){while(r<u&&!a(c,r)){r++}if(r==u){break}o=c[r]}if(o.canvasy===null||o.canvasy!=o.canvasy){if(g&&b!==null){n.moveTo(b,w);n.lineTo(o.canvasx,w)}b=w=null}else{j=false;if(s||!b){t.nextIdx_=r;t.next();k=t.hasNext?t.peek.canvasy:null;var d=k===null||k!=k;j=(!b&&d);if(s){if((!f&&!b)||(t.hasNext&&d)){j=true}}}if(b!==null){if(m){if(g){n.moveTo(b,w);n.lineTo(o.canvasx,w)}n.lineTo(o.canvasx,o.canvasy)}}else{n.moveTo(o.canvasx,o.canvasy)}if(p||j){l.push([o.canvasx,o.canvasy,o.idx])}b=o.canvasx;w=o.canvasy}f=false}n.stroke();return l};DygraphCanvasRenderer._drawPointsOnLine=function(h,i,f,d,g){var c=h.drawingContext;for(var b=0;b<i.length;b++){var a=i[b];c.save();f(h.dygraph,h.setName,c,a[0],a[1],d,g,a[2]);c.restore()}};DygraphCanvasRenderer.prototype._updatePoints=function(){var e=this.layout.points;for(var c=e.length;c--;){var d=e[c];for(var b=d.length;b--;){var a=d[b];a.canvasx=this.area.w*a.x+this.area.x;a.canvasy=this.area.h*a.y+this.area.y}}};DygraphCanvasRenderer.prototype._renderLineChart=function(g,u){var h=u||this.elementContext;var n;var a=this.layout.points;var s=this.layout.setNames;var b;this.colors=this.dygraph_.colorsMap_;var o=this.attr_("plotter");var f=o;if(!Dygraph.isArrayLike(f)){f=[f]}var c={};for(n=0;n<s.length;n++){b=s[n];var t=this.attr_("plotter",b);if(t==o){continue}c[b]=t}for(n=0;n<f.length;n++){var r=f[n];var q=(n==f.length-1);for(var l=0;l<a.length;l++){b=s[l];if(g&&b!=g){continue}var m=a[l];var e=r;if(b in c){if(q){e=c[b]}else{continue}}var k=this.colors[b];var d=this.dygraph_.getOption("strokeWidth",b);h.save();h.strokeStyle=k;h.lineWidth=d;e({points:m,setName:b,drawingContext:h,color:k,strokeWidth:d,dygraph:this.dygraph_,axis:this.dygraph_.axisPropertiesForSeries(b),plotArea:this.area,seriesIndex:l,seriesCount:a.length,singleSeriesName:g,allSeriesPoints:a});h.restore()}}};DygraphCanvasRenderer._Plotters={linePlotter:function(a){DygraphCanvasRenderer._linePlotter(a)},fillPlotter:function(a){DygraphCanvasRenderer._fillPlotter(a)},errorPlotter:function(a){DygraphCanvasRenderer._errorPlotter(a)}};DygraphCanvasRenderer._linePlotter=function(f){var d=f.dygraph;var h=f.setName;var i=f.strokeWidth;var a=d.getOption("strokeBorderWidth",h);var j=d.getOption("drawPointCallback",h)||Dygraph.Circles.DEFAULT;var k=d.getOption("strokePattern",h);var c=d.getOption("drawPoints",h);var b=d.getOption("pointSize",h);if(a&&i){DygraphCanvasRenderer._drawStyledLine(f,d.getOption("strokeBorderColor",h),i+2*a,k,c,j,b)}DygraphCanvasRenderer._drawStyledLine(f,f.color,i,k,c,j,b)};DygraphCanvasRenderer._errorPlotter=function(s){var r=s.dygraph;var f=s.setName;var t=r.getOption("errorBars")||r.getOption("customBars");if(!t){return}var k=r.getOption("fillGraph",f);if(k){r.warn("Can't use fillGraph option with error bars")}var m=s.drawingContext;var n=s.color;var o=r.getOption("fillAlpha",f);var i=r.getOption("stepPlot",f);var p=s.points;var q=Dygraph.createIterator(p,0,p.length,DygraphCanvasRenderer._getIteratorPredicate(r.getOption("connectSeparatedPoints")));var j;var h=NaN;var c=NaN;var d=[-1,-1];var a=new RGBColorParser(n);var u="rgba("+a.r+","+a.g+","+a.b+","+o+")";m.fillStyle=u;m.beginPath();var b=function(e){return(e===null||e===undefined||isNaN(e))};while(q.hasNext){var l=q.next();if((!i&&b(l.y))||(i&&!isNaN(c)&&b(c))){h=NaN;continue}if(i){j=[l.y_bottom,l.y_top];c=l.y}else{j=[l.y_bottom,l.y_top]}j[0]=s.plotArea.h*j[0]+s.plotArea.y;j[1]=s.plotArea.h*j[1]+s.plotArea.y;if(!isNaN(h)){if(i){m.moveTo(h,d[0]);m.lineTo(l.canvasx,d[0]);m.lineTo(l.canvasx,d[1])}else{m.moveTo(h,d[0]);m.lineTo(l.canvasx,j[0]);m.lineTo(l.canvasx,j[1])}m.lineTo(h,d[1]);m.closePath()}d=j;h=l.canvasx}m.fill()};DygraphCanvasRenderer._fillPlotter=function(F){if(F.singleSeriesName){return}if(F.seriesIndex!==0){return}var D=F.dygraph;var I=D.getLabels().slice(1);for(var C=I.length;C>=0;C--){if(!D.visibility()[C]){I.splice(C,1)}}var h=(function(){for(var e=0;e<I.length;e++){if(D.getOption("fillGraph",I[e])){return true}}return false})();if(!h){return}var w=F.drawingContext;var E=F.plotArea;var c=F.allSeriesPoints;var z=c.length;var y=D.getOption("fillAlpha");var k=D.getOption("stackedGraph");var q=D.getColors();var s={};var a;var r;for(var u=z-1;u>=0;u--){var n=I[u];if(!D.getOption("fillGraph",n)){continue}var p=D.getOption("stepPlot",n);var x=q[u];var f=D.axisPropertiesForSeries(n);var d=1+f.minyval*f.yscale;if(d<0){d=0}else{if(d>1){d=1}}d=E.h*d+E.y;var B=c[u];var A=Dygraph.createIterator(B,0,B.length,DygraphCanvasRenderer._getIteratorPredicate(D.getOption("connectSeparatedPoints")));var m=NaN;var l=[-1,-1];var t;var b=new RGBColorParser(x);var H="rgba("+b.r+","+b.g+","+b.b+","+y+")";w.fillStyle=H;w.beginPath();var j,G=true;while(A.hasNext){var v=A.next();if(!Dygraph.isOK(v.y)){m=NaN;if(v.y_stacked!==null&&!isNaN(v.y_stacked)){s[v.canvasx]=E.h*v.y_stacked+E.y}continue}if(k){if(!G&&j==v.xval){continue}else{G=false;j=v.xval}a=s[v.canvasx];var o;if(a===undefined){o=d}else{if(r){o=a[0]}else{o=a}}t=[v.canvasy,o];if(p){if(l[0]===-1){s[v.canvasx]=[v.canvasy,d]}else{s[v.canvasx]=[v.canvasy,l[0]]}}else{s[v.canvasx]=v.canvasy}}else{t=[v.canvasy,d]}if(!isNaN(m)){w.moveTo(m,l[0]);if(p){w.lineTo(v.canvasx,l[0])}else{w.lineTo(v.canvasx,t[0])}if(r&&a){w.lineTo(v.canvasx,a[1])}else{w.lineTo(v.canvasx,t[1])}w.lineTo(m,l[1]);w.closePath()}l=t;m=v.canvasx}r=p;w.fill()}};"use strict";var Dygraph=function(d,c,b,a){this.is_initial_draw_=true;this.readyFns_=[];if(a!==undefined){this.warn("Using deprecated four-argument dygraph constructor");this.__old_init__(d,c,b,a)}else{this.__init__(d,c,b)}};Dygraph.NAME="Dygraph";Dygraph.VERSION="1.0.0";Dygraph.__repr__=function(){return"["+this.NAME+" "+this.VERSION+"]"};Dygraph.toString=function(){return this.__repr__()};Dygraph.DEFAULT_ROLL_PERIOD=1;Dygraph.DEFAULT_WIDTH=480;Dygraph.DEFAULT_HEIGHT=320;Dygraph.ANIMATION_STEPS=12;Dygraph.ANIMATION_DURATION=200;Dygraph.KMB_LABELS=["K","M","B","T","Q"];Dygraph.KMG2_BIG_LABELS=["k","M","G","T","P","E","Z","Y"];Dygraph.KMG2_SMALL_LABELS=["m","u","n","p","f","a","z","y"];Dygraph.numberValueFormatter=function(s,a,u,l){var e=a("sigFigs");if(e!==null){return Dygraph.floatFormat(s,e)}var c=a("digitsAfterDecimal");var o=a("maxNumberWidth");var b=a("labelsKMB");var r=a("labelsKMG2");var q;if(s!==0&&(Math.abs(s)>=Math.pow(10,o)||Math.abs(s)<Math.pow(10,-c))){q=s.toExponential(c)}else{q=""+Dygraph.round_(s,c)}if(b||r){var f;var t=[];var m=[];if(b){f=1000;t=Dygraph.KMB_LABELS}if(r){if(b){Dygraph.warn("Setting both labelsKMB and labelsKMG2. Pick one!")}f=1024;t=Dygraph.KMG2_BIG_LABELS;m=Dygraph.KMG2_SMALL_LABELS}var p=Math.abs(s);var d=Dygraph.pow(f,t.length);for(var h=t.length-1;h>=0;h--,d/=f){if(p>=d){q=Dygraph.round_(s/d,c)+t[h];break}}if(r){var i=String(s.toExponential()).split("e-");if(i.length===2&&i[1]>=3&&i[1]<=24){if(i[1]%3>0){q=Dygraph.round_(i[0]/Dygraph.pow(10,(i[1]%3)),c)}else{q=Number(i[0]).toFixed(2)}q+=m[Math.floor(i[1]/3)-1]}}}return q};Dygraph.numberAxisLabelFormatter=function(a,d,c,b){return Dygraph.numberValueFormatter(a,c,b)};Dygraph.dateString_=function(e){var i=Dygraph.zeropad;var h=new Date(e);var f=""+h.getFullYear();var g=i(h.getMonth()+1);var a=i(h.getDate());var c="";var b=h.getHours()*3600+h.getMinutes()*60+h.getSeconds();if(b){c=" "+Dygraph.hmsString_(e)}return f+"/"+g+"/"+a+c};Dygraph.dateAxisFormatter=function(b,c){if(c>=Dygraph.DECADAL){return b.strftime("%Y")}else{if(c>=Dygraph.MONTHLY){return b.strftime("%b %y")}else{var a=b.getHours()*3600+b.getMinutes()*60+b.getSeconds()+b.getMilliseconds();if(a===0||c>=Dygraph.DAILY){return new Date(b.getTime()+3600*1000).strftime("%d%b")}else{return Dygraph.hmsString_(b.getTime())}}}};Dygraph.Plotters=DygraphCanvasRenderer._Plotters;Dygraph.DEFAULT_ATTRS={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:0.5,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:false,labelsShowZeroValues:true,labelsKMB:false,labelsKMG2:false,showLabelsOnHighlight:true,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,xAxisLabelWidth:50,yAxisLabelWidth:50,rightGap:5,showRoller:false,xValueParser:Dygraph.dateParser,delimiter:",",sigma:2,errorBars:false,fractions:false,wilsonInterval:true,customBars:false,fillGraph:false,fillAlpha:0.15,connectSeparatedPoints:false,stackedGraph:false,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:true,legend:"onmouseover",stepPlot:false,avoidMinZero:false,xRangePad:0,yRangePad:null,drawAxesAtZero:false,titleHeight:28,xLabelHeight:18,yLabelWidth:18,drawXAxis:true,drawYAxis:true,axisLineColor:"black",axisLineWidth:0.3,gridLineWidth:0.3,axisLabelColor:"black",axisLabelFont:"Arial",axisLabelWidth:50,drawYGrid:true,drawXGrid:true,gridLineColor:"rgb(128,128,128)",interactionModel:null,animatedZooms:false,showRangeSelector:false,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillColor:"#A7B1C4",plotter:[Dygraph.Plotters.fillPlotter,Dygraph.Plotters.errorPlotter,Dygraph.Plotters.linePlotter],plugins:[],axes:{x:{pixelsPerLabel:60,axisLabelFormatter:Dygraph.dateAxisFormatter,valueFormatter:Dygraph.dateString_,drawGrid:true,independentTicks:true,ticker:null},y:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:true,independentTicks:true,ticker:null},y2:{pixelsPerLabel:30,valueFormatter:Dygraph.numberValueFormatter,axisLabelFormatter:Dygraph.numberAxisLabelFormatter,drawGrid:false,independentTicks:false,ticker:null}}};Dygraph.HORIZONTAL=1;Dygraph.VERTICAL=2;Dygraph.PLUGINS=[];Dygraph.addedAnnotationCSS=false;Dygraph.prototype.__old_init__=function(f,d,e,b){if(e!==null){var a=["Date"];for(var c=0;c<e.length;c++){a.push(e[c])}Dygraph.update(b,{labels:a})}this.__init__(f,d,b)};Dygraph.prototype.__init__=function(a,c,l){if(/MSIE/.test(navigator.userAgent)&&!window.opera&&typeof(G_vmlCanvasManager)!="undefined"&&document.readyState!="complete"){var o=this;setTimeout(function(){o.__init__(a,c,l)},100);return}if(l===null||l===undefined){l={}}l=Dygraph.mapLegacyOptions_(l);if(typeof(a)=="string"){a=document.getElementById(a)}if(!a){Dygraph.error("Constructing dygraph with a non-existent div!");return}this.isUsingExcanvas_=typeof(G_vmlCanvasManager)!="undefined";this.maindiv_=a;this.file_=c;this.rollPeriod_=l.rollPeriod||Dygraph.DEFAULT_ROLL_PERIOD;this.previousVerticalX_=-1;this.fractions_=l.fractions||false;this.dateWindow_=l.dateWindow||null;this.annotations_=[];this.zoomed_x_=false;this.zoomed_y_=false;a.innerHTML="";if(a.style.width===""&&l.width){a.style.width=l.width+"px"}if(a.style.height===""&&l.height){a.style.height=l.height+"px"}if(a.style.height===""&&a.clientHeight===0){a.style.height=Dygraph.DEFAULT_HEIGHT+"px";if(a.style.width===""){a.style.width=Dygraph.DEFAULT_WIDTH+"px"}}this.width_=a.clientWidth||l.width||0;this.height_=a.clientHeight||l.height||0;if(l.stackedGraph){l.fillGraph=true}this.user_attrs_={};Dygraph.update(this.user_attrs_,l);this.attrs_={};Dygraph.updateDeep(this.attrs_,Dygraph.DEFAULT_ATTRS);this.boundaryIds_=[];this.setIndexByName_={};this.datasetIndex_=[];this.registeredEvents_=[];this.eventListeners_={};this.attributes_=new DygraphOptions(this);this.createInterface_();this.plugins_=[];var d=Dygraph.PLUGINS.concat(this.getOption("plugins"));for(var g=0;g<d.length;g++){var k=d[g];var f=new k();var j={plugin:f,events:{},options:{},pluginOptions:{}};var b=f.activate(this);for(var h in b){j.events[h]=b[h]}this.plugins_.push(j)}for(var g=0;g<this.plugins_.length;g++){var n=this.plugins_[g];for(var h in n.events){if(!n.events.hasOwnProperty(h)){continue}var m=n.events[h];var e=[n.plugin,m];if(!(h in this.eventListeners_)){this.eventListeners_[h]=[e]}else{this.eventListeners_[h].push(e)}}}this.createDragInterface_();this.start_()};Dygraph.prototype.cascadeEvents_=function(c,b){if(!(c in this.eventListeners_)){return true}var g={dygraph:this,cancelable:false,defaultPrevented:false,preventDefault:function(){if(!g.cancelable){throw"Cannot call preventDefault on non-cancelable event."}g.defaultPrevented=true},propagationStopped:false,stopPropagation:function(){g.propagationStopped=true}};Dygraph.update(g,b);var a=this.eventListeners_[c];if(a){for(var d=a.length-1;d>=0;d--){var f=a[d][0];var h=a[d][1];h.call(f,g);if(g.propagationStopped){break}}}return g.defaultPrevented};Dygraph.prototype.isZoomed=function(a){if(a===null||a===undefined){return this.zoomed_x_||this.zoomed_y_}if(a==="x"){return this.zoomed_x_}if(a==="y"){return this.zoomed_y_}throw"axis parameter is ["+a+"] must be null, 'x' or 'y'."};Dygraph.prototype.toString=function(){var a=this.maindiv_;var b=(a&&a.id)?a.id:a;return"[Dygraph "+b+"]"};Dygraph.prototype.attr_=function(b,a){return a?this.attributes_.getForSeries(b,a):this.attributes_.get(b)};Dygraph.prototype.getOption=function(a,b){return this.attr_(a,b)};Dygraph.prototype.getOptionForAxis=function(a,b){return this.attributes_.getForAxis(a,b)};Dygraph.prototype.optionsViewForAxis_=function(b){var a=this;return function(c){var d=a.user_attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(typeof(a.user_attrs_[c])!="undefined"){return a.user_attrs_[c]}d=a.attrs_.axes;if(d&&d[b]&&d[b].hasOwnProperty(c)){return d[b][c]}if(b=="y"&&a.axes_[0].hasOwnProperty(c)){return a.axes_[0][c]}else{if(b=="y2"&&a.axes_[1].hasOwnProperty(c)){return a.axes_[1][c]}}return a.attr_(c)}};Dygraph.prototype.rollPeriod=function(){return this.rollPeriod_};Dygraph.prototype.xAxisRange=function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes()};Dygraph.prototype.xAxisExtremes=function(){var d=this.attr_("xRangePad")/this.plotter_.area.w;if(this.numRows()===0){return[0-d,1+d]}var c=this.rawData_[0][0];var b=this.rawData_[this.rawData_.length-1][0];if(d){var a=b-c;c-=a*d;b+=a*d}return[c,b]};Dygraph.prototype.yAxisRange=function(a){if(typeof(a)=="undefined"){a=0}if(a<0||a>=this.axes_.length){return null}var b=this.axes_[a];return[b.computedValueRange[0],b.computedValueRange[1]]};Dygraph.prototype.yAxisRanges=function(){var a=[];for(var b=0;b<this.axes_.length;b++){a.push(this.yAxisRange(b))}return a};Dygraph.prototype.toDomCoords=function(a,c,b){return[this.toDomXCoord(a),this.toDomYCoord(c,b)]};Dygraph.prototype.toDomXCoord=function(b){if(b===null){return null}var c=this.plotter_.area;var a=this.xAxisRange();return c.x+(b-a[0])/(a[1]-a[0])*c.w};Dygraph.prototype.toDomYCoord=function(d,a){var c=this.toPercentYCoord(d,a);if(c===null){return null}var b=this.plotter_.area;return b.y+c*b.h};Dygraph.prototype.toDataCoords=function(a,c,b){return[this.toDataXCoord(a),this.toDataYCoord(c,b)]};Dygraph.prototype.toDataXCoord=function(b){if(b===null){return null}var c=this.plotter_.area;var a=this.xAxisRange();return a[0]+(b-c.x)/c.w*(a[1]-a[0])};Dygraph.prototype.toDataYCoord=function(h,b){if(h===null){return null}var c=this.plotter_.area;var g=this.yAxisRange(b);if(typeof(b)=="undefined"){b=0}if(!this.axes_[b].logscale){return g[0]+(c.y+c.h-h)/c.h*(g[1]-g[0])}else{var f=(h-c.y)/c.h;var a=Dygraph.log10(g[1]);var e=a-(f*(a-Dygraph.log10(g[0])));var d=Math.pow(Dygraph.LOG_SCALE,e);return d}};Dygraph.prototype.toPercentYCoord=function(f,c){if(f===null){return null}if(typeof(c)=="undefined"){c=0}var e=this.yAxisRange(c);var d;var b=this.attributes_.getForAxis("logscale",c);if(!b){d=(e[1]-f)/(e[1]-e[0])}else{var a=Dygraph.log10(e[1]);d=(a-Dygraph.log10(f))/(a-Dygraph.log10(e[0]))}return d};Dygraph.prototype.toPercentXCoord=function(b){if(b===null){return null}var a=this.xAxisRange();return(b-a[0])/(a[1]-a[0])};Dygraph.prototype.numColumns=function(){if(!this.rawData_){return 0}return this.rawData_[0]?this.rawData_[0].length:this.attr_("labels").length};Dygraph.prototype.numRows=function(){if(!this.rawData_){return 0}return this.rawData_.length};Dygraph.prototype.getValue=function(b,a){if(b<0||b>this.rawData_.length){return null}if(a<0||a>this.rawData_[b].length){return null}return this.rawData_[b][a]};Dygraph.prototype.createInterface_=function(){var a=this.maindiv_;this.graphDiv=document.createElement("div");this.graphDiv.style.textAlign="left";a.appendChild(this.graphDiv);this.canvas_=Dygraph.createCanvas();this.canvas_.style.position="absolute";this.hidden_=this.createPlotKitCanvas_(this.canvas_);this.resizeElements_();this.canvas_ctx_=Dygraph.getContext(this.canvas_);this.hidden_ctx_=Dygraph.getContext(this.hidden_);this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_=this.createMouseEventElement_();this.layout_=new DygraphLayout(this);var b=this;this.mouseMoveHandler_=function(c){b.mouseMove_(c)};this.mouseOutHandler_=function(f){var d=f.target||f.fromElement;var c=f.relatedTarget||f.toElement;if(Dygraph.isNodeContainedBy(d,b.graphDiv)&&!Dygraph.isNodeContainedBy(c,b.graphDiv)){b.mouseOut_(f)}};this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_);this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);if(!this.resizeHandler_){this.resizeHandler_=function(c){b.resize()};this.addAndTrackEvent(window,"resize",this.resizeHandler_)}};Dygraph.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px";this.graphDiv.style.height=this.height_+"px";this.canvas_.width=this.width_;this.canvas_.height=this.height_;this.canvas_.style.width=this.width_+"px";this.canvas_.style.height=this.height_+"px";this.hidden_.width=this.width_;this.hidden_.height=this.height_;this.hidden_.style.width=this.width_+"px";this.hidden_.style.height=this.height_+"px"};Dygraph.prototype.destroy=function(){this.canvas_ctx_.restore();this.hidden_ctx_.restore();var a=function(c){while(c.hasChildNodes()){a(c.firstChild);c.removeChild(c.firstChild)}};this.removeTrackedEvents_();Dygraph.removeEvent(window,"mouseout",this.mouseOutHandler_);Dygraph.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_);Dygraph.removeEvent(window,"resize",this.resizeHandler_);this.resizeHandler_=null;a(this.maindiv_);var b=function(c){for(var d in c){if(typeof(c[d])==="object"){c[d]=null}}};b(this.layout_);b(this.plotter_);b(this)};Dygraph.prototype.createPlotKitCanvas_=function(a){var b=Dygraph.createCanvas();b.style.position="absolute";b.style.top=a.style.top;b.style.left=a.style.left;b.width=this.width_;b.height=this.height_;b.style.width=this.width_+"px";b.style.height=this.height_+"px";return b};Dygraph.prototype.createMouseEventElement_=function(){if(this.isUsingExcanvas_){var a=document.createElement("div");a.style.position="absolute";a.style.backgroundColor="white";a.style.filter="alpha(opacity=0)";a.style.width=this.width_+"px";a.style.height=this.height_+"px";this.graphDiv.appendChild(a);return a}else{return this.canvas_}};Dygraph.prototype.setColors_=function(){var g=this.getLabels();var e=g.length-1;this.colors_=[];this.colorsMap_={};var a=this.attr_("colors");var d;if(!a){var c=this.attr_("colorSaturation")||1;var b=this.attr_("colorValue")||0.5;var k=Math.ceil(e/2);for(d=1;d<=e;d++){if(!this.visibility()[d-1]){continue}var h=d%2?Math.ceil(d/2):(k+d/2);var f=(1*h/(1+e));var j=Dygraph.hsvToRGB(f,c,b);this.colors_.push(j);this.colorsMap_[g[d]]=j}}else{for(d=0;d<e;d++){if(!this.visibility()[d]){continue}var j=a[d%a.length];this.colors_.push(j);this.colorsMap_[g[1+d]]=j}}};Dygraph.prototype.getColors=function(){return this.colors_};Dygraph.prototype.getPropertiesForSeries=function(c){var a=-1;var d=this.getLabels();for(var b=1;b<d.length;b++){if(d[b]==c){a=b;break}}if(a==-1){return null}return{name:c,column:a,visible:this.visibility()[a-1],color:this.colorsMap_[c],axis:1+this.attributes_.axisForSeries(c)}};Dygraph.prototype.createRollInterface_=function(){if(!this.roller_){this.roller_=document.createElement("input");this.roller_.type="text";this.roller_.style.display="none";this.graphDiv.appendChild(this.roller_)}var e=this.attr_("showRoller")?"block":"none";var d=this.plotter_.area;var b={position:"absolute",zIndex:10,top:(d.y+d.h-25)+"px",left:(d.x+1)+"px",display:e};this.roller_.size="2";this.roller_.value=this.rollPeriod_;for(var a in b){if(b.hasOwnProperty(a)){this.roller_.style[a]=b[a]}}var c=this;this.roller_.onchange=function(){c.adjustRoll(c.roller_.value)}};Dygraph.prototype.dragGetX_=function(b,a){return Dygraph.pageX(b)-a.px};Dygraph.prototype.dragGetY_=function(b,a){return Dygraph.pageY(b)-a.py};Dygraph.prototype.createDragInterface_=function(){var c={isZooming:false,isPanning:false,is2DPan:false,dragStartX:null,dragStartY:null,dragEndX:null,dragEndY:null,dragDirection:null,prevEndX:null,prevEndY:null,prevDragDirection:null,cancelNextDblclick:false,initialLeftmostDate:null,xUnitsPerPixel:null,dateRange:null,px:0,py:0,boundedDates:null,boundedValues:null,tarp:new Dygraph.IFrameTarp(),initializeMouseDown:function(j,i,h){if(j.preventDefault){j.preventDefault()}else{j.returnValue=false;j.cancelBubble=true}h.px=Dygraph.findPosX(i.canvas_);h.py=Dygraph.findPosY(i.canvas_);h.dragStartX=i.dragGetX_(j,h);h.dragStartY=i.dragGetY_(j,h);h.cancelNextDblclick=false;h.tarp.cover()}};var f=this.attr_("interactionModel");var b=this;var e=function(g){return function(h){g(h,b,c)}};for(var a in f){if(!f.hasOwnProperty(a)){continue}this.addAndTrackEvent(this.mouseEventElement_,a,e(f[a]))}var d=function(h){if(c.isZooming||c.isPanning){c.isZooming=false;c.dragStartX=null;c.dragStartY=null}if(c.isPanning){c.isPanning=false;c.draggingDate=null;c.dateRange=null;for(var g=0;g<b.axes_.length;g++){delete b.axes_[g].draggingValue;delete b.axes_[g].dragValueRange}}c.tarp.uncover()};this.addAndTrackEvent(document,"mouseup",d)};Dygraph.prototype.drawZoomRect_=function(e,c,i,b,g,a,f,d){var h=this.canvas_ctx_;if(a==Dygraph.HORIZONTAL){h.clearRect(Math.min(c,f),this.layout_.getPlotArea().y,Math.abs(c-f),this.layout_.getPlotArea().h)}else{if(a==Dygraph.VERTICAL){h.clearRect(this.layout_.getPlotArea().x,Math.min(b,d),this.layout_.getPlotArea().w,Math.abs(b-d))}}if(e==Dygraph.HORIZONTAL){if(i&&c){h.fillStyle="rgba(128,128,128,0.33)";h.fillRect(Math.min(c,i),this.layout_.getPlotArea().y,Math.abs(i-c),this.layout_.getPlotArea().h)}}else{if(e==Dygraph.VERTICAL){if(g&&b){h.fillStyle="rgba(128,128,128,0.33)";h.fillRect(this.layout_.getPlotArea().x,Math.min(b,g),this.layout_.getPlotArea().w,Math.abs(g-b))}}}if(this.isUsingExcanvas_){this.currentZoomRectArgs_=[e,c,i,b,g,0,0,0]}};Dygraph.prototype.clearZoomRect_=function(){this.currentZoomRectArgs_=null;this.canvas_ctx_.clearRect(0,0,this.canvas_.width,this.canvas_.height)};Dygraph.prototype.doZoomX_=function(c,a){this.currentZoomRectArgs_=null;var b=this.toDataXCoord(c);var d=this.toDataXCoord(a);this.doZoomXDates_(b,d)};Dygraph.zoomAnimationFunction=function(c,b){var a=1.5;return(1-Math.pow(a,-c))/(1-Math.pow(a,-b))};Dygraph.prototype.doZoomXDates_=function(c,e){var a=this.xAxisRange();var d=[c,e];this.zoomed_x_=true;var b=this;this.doAnimatedZoom(a,d,null,null,function(){if(b.attr_("zoomCallback")){b.attr_("zoomCallback")(c,e,b.yAxisRanges())}})};Dygraph.prototype.doZoomY_=function(h,f){this.currentZoomRectArgs_=null;var c=this.yAxisRanges();var b=[];for(var e=0;e<this.axes_.length;e++){var d=this.toDataYCoord(h,e);var a=this.toDataYCoord(f,e);b.push([a,d])}this.zoomed_y_=true;var g=this;this.doAnimatedZoom(null,null,c,b,function(){if(g.attr_("zoomCallback")){var i=g.xAxisRange();g.attr_("zoomCallback")(i[0],i[1],g.yAxisRanges())}})};Dygraph.prototype.resetZoom=function(){var c=false,d=false,a=false;if(this.dateWindow_!==null){c=true;d=true}for(var g=0;g<this.axes_.length;g++){if(typeof(this.axes_[g].valueWindow)!=="undefined"&&this.axes_[g].valueWindow!==null){c=true;a=true}}this.clearSelection();if(c){this.zoomed_x_=false;this.zoomed_y_=false;var f=this.rawData_[0][0];var b=this.rawData_[this.rawData_.length-1][0];if(!this.attr_("animatedZooms")){this.dateWindow_=null;for(g=0;g<this.axes_.length;g++){if(this.axes_[g].valueWindow!==null){delete this.axes_[g].valueWindow}}this.drawGraph_();if(this.attr_("zoomCallback")){this.attr_("zoomCallback")(f,b,this.yAxisRanges())}return}var l=null,m=null,k=null,h=null;if(d){l=this.xAxisRange();m=[f,b]}if(a){k=this.yAxisRanges();var n=this.gatherDatasets_(this.rolledSeries_,null);var o=n.extremes;this.computeYAxisRanges_(o);h=[];for(g=0;g<this.axes_.length;g++){var e=this.axes_[g];h.push((e.valueRange!==null&&e.valueRange!==undefined)?e.valueRange:e.extremeRange)}}var j=this;this.doAnimatedZoom(l,m,k,h,function(){j.dateWindow_=null;for(var p=0;p<j.axes_.length;p++){if(j.axes_[p].valueWindow!==null){delete j.axes_[p].valueWindow}}if(j.attr_("zoomCallback")){j.attr_("zoomCallback")(f,b,j.yAxisRanges())}})}};Dygraph.prototype.doAnimatedZoom=function(a,e,b,c,m){var i=this.attr_("animatedZooms")?Dygraph.ANIMATION_STEPS:1;var l=[];var k=[];var f,d;if(a!==null&&e!==null){for(f=1;f<=i;f++){d=Dygraph.zoomAnimationFunction(f,i);l[f-1]=[a[0]*(1-d)+d*e[0],a[1]*(1-d)+d*e[1]]}}if(b!==null&&c!==null){for(f=1;f<=i;f++){d=Dygraph.zoomAnimationFunction(f,i);var n=[];for(var g=0;g<this.axes_.length;g++){n.push([b[g][0]*(1-d)+d*c[g][0],b[g][1]*(1-d)+d*c[g][1]])}k[f-1]=n}}var h=this;Dygraph.repeatAndCleanup(function(p){if(k.length){for(var o=0;o<h.axes_.length;o++){var j=k[p][o];h.axes_[o].valueWindow=[j[0],j[1]]}}if(l.length){h.dateWindow_=l[p]}h.drawGraph_()},i,Dygraph.ANIMATION_DURATION/i,m)};Dygraph.prototype.getArea=function(){return this.plotter_.area};Dygraph.prototype.eventToDomCoords=function(c){if(c.offsetX&&c.offsetY){return[c.offsetX,c.offsetY]}else{var b=Dygraph.pageX(c)-Dygraph.findPosX(this.mouseEventElement_);var a=Dygraph.pageY(c)-Dygraph.findPosY(this.mouseEventElement_);return[b,a]}};Dygraph.prototype.findClosestRow=function(a){var g=Infinity;var h=-1;var e=this.layout_.points;for(var c=0;c<e.length;c++){var l=e[c];var d=l.length;for(var b=0;b<d;b++){var k=l[b];if(!Dygraph.isValidPoint(k,true)){continue}var f=Math.abs(k.canvasx-a);if(f<g){g=f;h=k.idx}}}return h};Dygraph.prototype.findClosestPoint=function(f,e){var k=Infinity;var h,o,n,l,d,c,j;for(var b=this.layout_.points.length-1;b>=0;--b){var m=this.layout_.points[b];for(var g=0;g<m.length;++g){l=m[g];if(!Dygraph.isValidPoint(l)){continue}o=l.canvasx-f;n=l.canvasy-e;h=o*o+n*n;if(h<k){k=h;d=l;c=b;j=l.idx}}}var a=this.layout_.setNames[c];return{row:j,seriesName:a,point:d}};Dygraph.prototype.findStackedPoint=function(i,h){var p=this.findClosestRow(i);var f,c;for(var d=0;d<this.layout_.points.length;++d){var g=this.getLeftBoundary_(d);var e=p-g;var l=this.layout_.points[d];if(e>=l.length){continue}var m=l[e];if(!Dygraph.isValidPoint(m)){continue}var j=m.canvasy;if(i>m.canvasx&&e+1<l.length){var k=l[e+1];if(Dygraph.isValidPoint(k)){var o=k.canvasx-m.canvasx;if(o>0){var a=(i-m.canvasx)/o;j+=a*(k.canvasy-m.canvasy)}}}else{if(i<m.canvasx&&e>0){var n=l[e-1];if(Dygraph.isValidPoint(n)){var o=m.canvasx-n.canvasx;if(o>0){var a=(m.canvasx-i)/o;j+=a*(n.canvasy-m.canvasy)}}}}if(d===0||j<h){f=m;c=d}}var b=this.layout_.setNames[c];return{row:p,seriesName:b,point:f}};Dygraph.prototype.mouseMove_=function(b){var h=this.layout_.points;if(h===undefined||h===null){return}var e=this.eventToDomCoords(b);var a=e[0];var j=e[1];var f=this.attr_("highlightSeriesOpts");var d=false;if(f&&!this.isSeriesLocked()){var c;if(this.attr_("stackedGraph")){c=this.findStackedPoint(a,j)}else{c=this.findClosestPoint(a,j)}d=this.setSelection(c.row,c.seriesName)}else{var g=this.findClosestRow(a);d=this.setSelection(g)}var i=this.attr_("highlightCallback");if(i&&d){i(b,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_)}};Dygraph.prototype.getLeftBoundary_=function(b){if(this.boundaryIds_[b]){return this.boundaryIds_[b][0]}else{for(var a=0;a<this.boundaryIds_.length;a++){if(this.boundaryIds_[a]!==undefined){return this.boundaryIds_[a][0]}}return 0}};Dygraph.prototype.animateSelection_=function(f){var e=10;var c=30;if(this.fadeLevel===undefined){this.fadeLevel=0}if(this.animateId===undefined){this.animateId=0}var g=this.fadeLevel;var b=f<0?g:e-g;if(b<=0){if(this.fadeLevel){this.updateSelection_(1)}return}var a=++this.animateId;var d=this;Dygraph.repeatAndCleanup(function(h){if(d.animateId!=a){return}d.fadeLevel+=f;if(d.fadeLevel===0){d.clearSelection()}else{d.updateSelection_(d.fadeLevel/e)}},b,c,function(){})};Dygraph.prototype.updateSelection_=function(d){this.cascadeEvents_("select",{selectedX:this.lastx_,selectedPoints:this.selPoints_});var h;var n=this.canvas_ctx_;if(this.attr_("highlightSeriesOpts")){n.clearRect(0,0,this.width_,this.height_);var f=1-this.attr_("highlightSeriesBackgroundAlpha");if(f){var g=true;if(g){if(d===undefined){this.animateSelection_(1);return}f*=d}n.fillStyle="rgba(255,255,255,"+f+")";n.fillRect(0,0,this.width_,this.height_)}this.plotter_._renderLineChart(this.highlightSet_,n)}else{if(this.previousVerticalX_>=0){var j=0;var k=this.attr_("labels");for(h=1;h<k.length;h++){var c=this.attr_("highlightCircleSize",k[h]);if(c>j){j=c}}var l=this.previousVerticalX_;n.clearRect(l-j-1,0,2*j+2,this.height_)}}if(this.isUsingExcanvas_&&this.currentZoomRectArgs_){Dygraph.prototype.drawZoomRect_.apply(this,this.currentZoomRectArgs_)}if(this.selPoints_.length>0){var b=this.selPoints_[0].canvasx;n.save();for(h=0;h<this.selPoints_.length;h++){var o=this.selPoints_[h];if(!Dygraph.isOK(o.canvasy)){continue}var a=this.attr_("highlightCircleSize",o.name);var m=this.attr_("drawHighlightPointCallback",o.name);var e=this.plotter_.colors[o.name];if(!m){m=Dygraph.Circles.DEFAULT}n.lineWidth=this.attr_("strokeWidth",o.name);n.strokeStyle=e;n.fillStyle=e;m(this.g,o.name,n,b,o.canvasy,e,a,o.idx)}n.restore();this.previousVerticalX_=b}};Dygraph.prototype.setSelection=function(f,h,g){this.selPoints_=[];var e=false;if(f!==false&&f>=0){if(f!=this.lastRow_){e=true}this.lastRow_=f;for(var d=0;d<this.layout_.points.length;++d){var b=this.layout_.points[d];var c=f-this.getLeftBoundary_(d);if(c<b.length){var a=b[c];if(a.yval!==null){this.selPoints_.push(a)}}}}else{if(this.lastRow_>=0){e=true}this.lastRow_=-1}if(this.selPoints_.length){this.lastx_=this.selPoints_[0].xval}else{this.lastx_=-1}if(h!==undefined){if(this.highlightSet_!==h){e=true}this.highlightSet_=h}if(g!==undefined){this.lockedSet_=g}if(e){this.updateSelection_(undefined)}return e};Dygraph.prototype.mouseOut_=function(a){if(this.attr_("unhighlightCallback")){this.attr_("unhighlightCallback")(a)}if(this.attr_("hideOverlayOnMouseOut")&&!this.lockedSet_){this.clearSelection()}};Dygraph.prototype.clearSelection=function(){this.cascadeEvents_("deselect",{});this.lockedSet_=false;if(this.fadeLevel){this.animateSelection_(-1);return}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel=0;this.selPoints_=[];this.lastx_=-1;this.lastRow_=-1;this.highlightSet_=null};Dygraph.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1){return -1}for(var c=0;c<this.layout_.points.length;c++){var a=this.layout_.points[c];for(var b=0;b<a.length;b++){if(a[b].x==this.selPoints_[0].x){return a[b].idx}}}return -1};Dygraph.prototype.getHighlightSeries=function(){return this.highlightSet_};Dygraph.prototype.isSeriesLocked=function(){return this.lockedSet_};Dygraph.prototype.loadedEvent_=function(a){this.rawData_=this.parseCSV_(a);this.predraw_()};Dygraph.prototype.addXTicks_=function(){var a;if(this.dateWindow_){a=[this.dateWindow_[0],this.dateWindow_[1]]}else{a=this.xAxisExtremes()}var c=this.optionsViewForAxis_("x");var b=c("ticker")(a[0],a[1],this.width_,c,this);this.layout_.setXTicks(b)};Dygraph.prototype.extremeValues_=function(d){var h=null,f=null,c,g;var b=this.attr_("errorBars")||this.attr_("customBars");if(b){for(c=0;c<d.length;c++){g=d[c][1][0];if(g===null||isNaN(g)){continue}var a=g-d[c][1][1];var e=g+d[c][1][2];if(a>g){a=g}if(e<g){e=g}if(f===null||e>f){f=e}if(h===null||a<h){h=a}}}else{for(c=0;c<d.length;c++){g=d[c][1];if(g===null||isNaN(g)){continue}if(f===null||g>f){f=g}if(h===null||g<h){h=g}}}return[h,f]};Dygraph.prototype.predraw_=function(){var e=new Date();this.layout_.computePlotArea();this.computeYAxes_();if(this.plotter_){this.cascadeEvents_("clearChart");this.plotter_.clear()}if(!this.is_initial_draw_){this.canvas_ctx_.restore();this.hidden_ctx_.restore()}this.canvas_ctx_.save();this.hidden_ctx_.save();this.plotter_=new DygraphCanvasRenderer(this,this.hidden_,this.hidden_ctx_,this.layout_);this.createRollInterface_();this.cascadeEvents_("predraw");this.rolledSeries_=[null];for(var c=1;c<this.numColumns();c++){var d=this.attr_("logscale");var b=this.extractSeries_(this.rawData_,c,d);b=this.rollingAverage(b,this.rollPeriod_);this.rolledSeries_.push(b)}this.drawGraph_();var a=new Date();this.drawingTimeMs_=(a-e)};Dygraph.PointType=undefined;Dygraph.seriesToPoints_=function(b,j,d,f){var h=[];for(var c=0;c<b.length;++c){var k=b[c];var a=j?k[1][0]:k[1];var e=a===null?null:DygraphLayout.parseFloat_(a);var g={x:NaN,y:NaN,xval:DygraphLayout.parseFloat_(k[0]),yval:e,name:d,idx:c+f};if(j){g.y_top=NaN;g.y_bottom=NaN;g.yval_minus=DygraphLayout.parseFloat_(k[1][1]);g.yval_plus=DygraphLayout.parseFloat_(k[1][2])}h.push(g)}return h};Dygraph.stackPoints_=function(m,l,o,f){var h=null;var d=null;var g=null;var c=-1;var k=function(i){if(c>=i){return}for(var p=i;p<m.length;++p){g=null;if(!isNaN(m[p].yval)&&m[p].yval!==null){c=p;g=m[p];break}}};for(var b=0;b<m.length;++b){var j=m[b];var n=j.xval;if(l[n]===undefined){l[n]=0}var e=j.yval;if(isNaN(e)||e===null){k(b);if(d&&g&&f!="none"){e=d.yval+(g.yval-d.yval)*((n-d.xval)/(g.xval-d.xval))}else{if(d&&f=="all"){e=d.yval}else{if(g&&f=="all"){e=g.yval}else{e=0}}}}else{d=j}var a=l[n];if(h!=n){a+=e;l[n]=a}h=n;j.yval_stacked=a;if(a>o[1]){o[1]=a}if(a<o[0]){o[0]=a}}};Dygraph.prototype.gatherDatasets_=function(w,f){var s=[];var t=[];var q=[];var a={};var u,r;var x=this.attr_("errorBars");var h=this.attr_("customBars");var p=x||h;var b=function(i){if(!p){return i[1]===null}else{return h?i[1][1]===null:x?i[1][0]===null:false}};var n=w.length-1;var l;for(u=n;u>=1;u--){if(!this.visibility()[u-1]){continue}if(f){l=w[u];var z=f[0];var j=f[1];var g=null,y=null;for(r=0;r<l.length;r++){if(l[r][0]>=z&&g===null){g=r}if(l[r][0]<=j){y=r}}if(g===null){g=0}var e=g;var d=true;while(d&&e>0){e--;d=b(l[e])}if(y===null){y=l.length-1}var c=y;d=true;while(d&&c<l.length-1){c++;d=b(l[c])}if(e!==g){g=e}if(c!==y){y=c}s[u-1]=[g,y];l=l.slice(g,y+1)}else{l=w[u];s[u-1]=[0,l.length-1]}var v=this.attr_("labels")[u];var o=this.extremeValues_(l);var m=Dygraph.seriesToPoints_(l,p,v,s[u-1][0]);if(this.attr_("stackedGraph")){Dygraph.stackPoints_(m,q,o,this.attr_("stackedGraphNaNFill"))}a[v]=o;t[u]=m}return{points:t,extremes:a,boundaryIds:s}};Dygraph.prototype.drawGraph_=function(){var a=new Date();var d=this.is_initial_draw_;this.is_initial_draw_=false;this.layout_.removeAllDatasets();this.setColors_();this.attrs_.pointSize=0.5*this.attr_("highlightCircleSize");var j=this.gatherDatasets_(this.rolledSeries_,this.dateWindow_);var h=j.points;var k=j.extremes;this.boundaryIds_=j.boundaryIds;this.setIndexByName_={};var g=this.attr_("labels");if(g.length>0){this.setIndexByName_[g[0]]=0}var e=0;for(var f=1;f<h.length;f++){this.setIndexByName_[g[f]]=f;if(!this.visibility()[f-1]){continue}this.layout_.addDataset(g[f],h[f]);this.datasetIndex_[f]=e++}this.computeYAxisRanges_(k);this.layout_.setYAxes(this.axes_);this.addXTicks_();var b=this.zoomed_x_;this.zoomed_x_=b;this.layout_.evaluate();this.renderGraph_(d);if(this.attr_("timingName")){var c=new Date();Dygraph.info(this.attr_("timingName")+" - drawGraph: "+(c-a)+"ms")}};Dygraph.prototype.renderGraph_=function(b){this.cascadeEvents_("clearChart");this.plotter_.clear();if(this.attr_("underlayCallback")){this.attr_("underlayCallback")(this.hidden_ctx_,this.layout_.getPlotArea(),this,this)}var c={canvas:this.hidden_,drawingContext:this.hidden_ctx_};this.cascadeEvents_("willDrawChart",c);this.plotter_.render();this.cascadeEvents_("didDrawChart",c);this.lastRow_=-1;this.canvas_.getContext("2d").clearRect(0,0,this.canvas_.width,this.canvas_.height);if(this.attr_("drawCallback")!==null){this.attr_("drawCallback")(this,b)}if(b){this.readyFired_=true;while(this.readyFns_.length>0){var a=this.readyFns_.pop();a(this)}}};Dygraph.prototype.computeYAxes_=function(){var b,d,c,f,a;if(this.axes_!==undefined&&this.user_attrs_.hasOwnProperty("valueRange")===false){b=[];for(c=0;c<this.axes_.length;c++){b.push(this.axes_[c].valueWindow)}}this.axes_=[];for(d=0;d<this.attributes_.numAxes();d++){f={g:this};Dygraph.update(f,this.attributes_.axisOptions(d));this.axes_[d]=f}a=this.attr_("valueRange");if(a){this.axes_[0].valueRange=a}if(b!==undefined){var e=Math.min(b.length,this.axes_.length);for(c=0;c<e;c++){this.axes_[c].valueWindow=b[c]}}for(d=0;d<this.axes_.length;d++){if(d===0){f=this.optionsViewForAxis_("y"+(d?"2":""));a=f("valueRange");if(a){this.axes_[d].valueRange=a}}else{var g=this.user_attrs_.axes;if(g&&g.y2){a=g.y2.valueRange;if(a){this.axes_[d].valueRange=a}}}}};Dygraph.prototype.numAxes=function(){return this.attributes_.numAxes()};Dygraph.prototype.axisPropertiesForSeries=function(a){return this.axes_[this.attributes_.axisForSeries(a)]};Dygraph.prototype.computeYAxisRanges_=function(a){var g=function(i){return isNaN(parseFloat(i))};var q=this.attributes_.numAxes();var b,x,o,B;var p;for(var y=0;y<q;y++){var c=this.axes_[y];var C=this.attributes_.getForAxis("logscale",y);var G=this.attributes_.getForAxis("includeZero",y);var l=this.attributes_.getForAxis("independentTicks",y);o=this.attributes_.seriesForAxis(y);b=true;B=0.1;if(this.attr_("yRangePad")!==null){b=false;B=this.attr_("yRangePad")/this.plotter_.area.h}if(o.length===0){c.extremeRange=[0,1]}else{var D=Infinity;var A=-Infinity;var t,s;for(var w=0;w<o.length;w++){if(!a.hasOwnProperty(o[w])){continue}t=a[o[w]][0];if(t!==null){D=Math.min(t,D)}s=a[o[w]][1];if(s!==null){A=Math.max(s,A)}}if(G&&!C){if(D>0){D=0}if(A<0){A=0}}if(D==Infinity){D=0}if(A==-Infinity){A=1}x=A-D;if(x===0){if(A!==0){x=Math.abs(A)}else{A=1;x=1}}var h,H;if(C){if(b){h=A+B*x;H=D}else{var E=Math.exp(Math.log(x)*B);h=A*E;H=D/E}}else{h=A+B*x;H=D-B*x;if(b&&!this.attr_("avoidMinZero")){if(H<0&&D>=0){H=0}if(h>0&&A<=0){h=0}}}c.extremeRange=[H,h]}if(c.valueWindow){c.computedValueRange=[c.valueWindow[0],c.valueWindow[1]]}else{if(c.valueRange){var e=g(c.valueRange[0])?c.extremeRange[0]:c.valueRange[0];var d=g(c.valueRange[1])?c.extremeRange[1]:c.valueRange[1];if(!b){if(c.logscale){var E=Math.exp(Math.log(x)*B);e*=E;d/=E}else{x=d-e;e-=x*B;d+=x*B}}c.computedValueRange=[e,d]}else{c.computedValueRange=c.extremeRange}}if(l){c.independentTicks=l;var r=this.optionsViewForAxis_("y"+(y?"2":""));var F=r("ticker");c.ticks=F(c.computedValueRange[0],c.computedValueRange[1],this.height_,r,this);if(!p){p=c}}}if(p===undefined){throw ('Configuration Error: At least one axis has to have the "independentTicks" option activated.')}for(var y=0;y<q;y++){var c=this.axes_[y];if(!c.independentTicks){var r=this.optionsViewForAxis_("y"+(y?"2":""));var F=r("ticker");var m=p.ticks;var n=p.computedValueRange[1]-p.computedValueRange[0];var I=c.computedValueRange[1]-c.computedValueRange[0];var f=[];for(var v=0;v<m.length;v++){var u=(m[v].v-p.computedValueRange[0])/n;var z=c.computedValueRange[0]+u*I;f.push(z)}c.ticks=F(c.computedValueRange[0],c.computedValueRange[1],this.height_,r,this,f)}}};Dygraph.prototype.extractSeries_=function(a,f,c){var g=[];var h=this.attr_("errorBars");var e=this.attr_("customBars");for(var d=0;d<a.length;d++){var l=a[d][0];var m=a[d][f];if(c){if(h||e){for(var b=0;b<m.length;b++){if(m[b]<=0){m=null;break}}}else{if(m<=0){m=null}}}if(m!==null){g.push([l,m])}else{g.push([l,h?[null,null]:e?[null,null,null]:m])}}return g};Dygraph.prototype.rollingAverage=function(l,d){d=Math.min(d,l.length);var b=[];var t=this.attr_("sigma");var G,o,z,x,m,c,F,A;if(this.fractions_){var k=0;var h=0;var e=100;for(z=0;z<l.length;z++){k+=l[z][1][0];h+=l[z][1][1];if(z-d>=0){k-=l[z-d][1][0];h-=l[z-d][1][1]}var C=l[z][0];var w=h?k/h:0;if(this.attr_("errorBars")){if(this.attr_("wilsonInterval")){if(h){var s=w<0?0:w,u=h;var B=t*Math.sqrt(s*(1-s)/u+t*t/(4*u*u));var a=1+t*t/h;G=(s+t*t/(2*h)-B)/a;o=(s+t*t/(2*h)+B)/a;b[z]=[C,[s*e,(s-G)*e,(o-s)*e]]}else{b[z]=[C,[0,0,0]]}}else{A=h?t*Math.sqrt(w*(1-w)/h):1;b[z]=[C,[e*w,e*A,e*A]]}}else{b[z]=[C,e*w]}}}else{if(this.attr_("customBars")){G=0;var D=0;o=0;var g=0;for(z=0;z<l.length;z++){var E=l[z][1];m=E[1];b[z]=[l[z][0],[m,m-E[0],E[2]-m]];if(m!==null&&!isNaN(m)){G+=E[0];D+=m;o+=E[2];g+=1}if(z-d>=0){var r=l[z-d];if(r[1][1]!==null&&!isNaN(r[1][1])){G-=r[1][0];D-=r[1][1];o-=r[1][2];g-=1}}if(g){b[z]=[l[z][0],[1*D/g,1*(D-G)/g,1*(o-D)/g]]}else{b[z]=[l[z][0],[null,null,null]]}}}else{if(!this.attr_("errorBars")){if(d==1){return l}for(z=0;z<l.length;z++){c=0;F=0;for(x=Math.max(0,z-d+1);x<z+1;x++){m=l[x][1];if(m===null||isNaN(m)){continue}F++;c+=l[x][1]}if(F){b[z]=[l[z][0],c/F]}else{b[z]=[l[z][0],null]}}}else{for(z=0;z<l.length;z++){c=0;var f=0;F=0;for(x=Math.max(0,z-d+1);x<z+1;x++){m=l[x][1][0];if(m===null||isNaN(m)){continue}F++;c+=l[x][1][0];f+=Math.pow(l[x][1][1],2)}if(F){A=Math.sqrt(f)/F;b[z]=[l[z][0],[c/F,t*A,t*A]]}else{var q=(d==1)?l[z][1][0]:null;b[z]=[l[z][0],[q,q,q]]}}}}}return b};Dygraph.prototype.detectTypeFromString_=function(b){var a=false;var c=b.indexOf("-");if((c>0&&(b[c-1]!="e"&&b[c-1]!="E"))||b.indexOf("/")>=0||isNaN(parseFloat(b))){a=true}else{if(b.length==8&&b>"19700101"&&b<"20371231"){a=true}}this.setXAxisOptions_(a)};Dygraph.prototype.setXAxisOptions_=function(a){if(a){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{this.attrs_.xValueParser=function(b){return parseFloat(b)};this.attrs_.axes.x.valueFormatter=function(b){return b};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}};Dygraph.prototype.parseFloat_=function(a,c,b){var e=parseFloat(a);if(!isNaN(e)){return e}if(/^ *$/.test(a)){return null}if(/^ *nan *$/i.test(a)){return NaN}var d="Unable to parse '"+a+"' as a number";if(b!==null&&c!==null){d+=" on line "+(1+c)+" ('"+b+"') of CSV."}this.error(d);return null};Dygraph.prototype.parseCSV_=function(t){var r=[];var s=Dygraph.detectLineDelimiter(t);var a=t.split(s||"\n");var g,k;var p=this.attr_("delimiter");if(a[0].indexOf(p)==-1&&a[0].indexOf("\t")>=0){p="\t"}var b=0;if(!("labels" in this.user_attrs_)){b=1;this.attrs_.labels=a[0].split(p);this.attributes_.reparseSeries()}var o=0;var m;var q=false;var c=this.attr_("labels").length;var f=false;for(var l=b;l<a.length;l++){var e=a[l];o=l;if(e.length===0){continue}if(e[0]=="#"){continue}var d=e.split(p);if(d.length<2){continue}var h=[];if(!q){this.detectTypeFromString_(d[0]);m=this.attr_("xValueParser");q=true}h[0]=m(d[0],this);if(this.fractions_){for(k=1;k<d.length;k++){g=d[k].split("/");if(g.length!=2){this.error('Expected fractional "num/den" values in CSV data but found a value \''+d[k]+"' on line "+(1+l)+" ('"+e+"') which is not of this form.");h[k]=[0,0]}else{h[k]=[this.parseFloat_(g[0],l,e),this.parseFloat_(g[1],l,e)]}}}else{if(this.attr_("errorBars")){if(d.length%2!=1){this.error("Expected alternating (value, stdev.) pairs in CSV data but line "+(1+l)+" has an odd number of values ("+(d.length-1)+"): '"+e+"'")}for(k=1;k<d.length;k+=2){h[(k+1)/2]=[this.parseFloat_(d[k],l,e),this.parseFloat_(d[k+1],l,e)]}}else{if(this.attr_("customBars")){for(k=1;k<d.length;k++){var u=d[k];if(/^ *$/.test(u)){h[k]=[null,null,null]}else{g=u.split(";");if(g.length==3){h[k]=[this.parseFloat_(g[0],l,e),this.parseFloat_(g[1],l,e),this.parseFloat_(g[2],l,e)]}else{this.warn('When using customBars, values must be either blank or "low;center;high" tuples (got "'+u+'" on line '+(1+l))}}}}else{for(k=1;k<d.length;k++){h[k]=this.parseFloat_(d[k],l,e)}}}}if(r.length>0&&h[0]<r[r.length-1][0]){f=true}if(h.length!=c){this.error("Number of columns in line "+l+" ("+h.length+") does not agree with number of labels ("+c+") "+e)}if(l===0&&this.attr_("labels")){var n=true;for(k=0;n&&k<h.length;k++){if(h[k]){n=false}}if(n){this.warn("The dygraphs 'labels' option is set, but the first row of CSV data ('"+e+"') appears to also contain labels. Will drop the CSV labels and use the option labels.");continue}}r.push(h)}if(f){this.warn("CSV is out of order; order it correctly to speed loading.");r.sort(function(j,i){return j[0]-i[0]})}return r};Dygraph.prototype.parseArray_=function(c){if(c.length===0){this.error("Can't plot empty data set");return null}if(c[0].length===0){this.error("Data set cannot contain an empty row");return null}var a;if(this.attr_("labels")===null){this.warn("Using default labels. Set labels explicitly via 'labels' in the options parameter");this.attrs_.labels=["X"];for(a=1;a<c[0].length;a++){this.attrs_.labels.push("Y"+a)}this.attributes_.reparseSeries()}else{var b=this.attr_("labels");if(b.length!=c[0].length){this.error("Mismatch between number of labels ("+b+") and number of columns in array ("+c[0].length+")");return null}}if(Dygraph.isDateLike(c[0][0])){this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter;var d=Dygraph.clone(c);for(a=0;a<c.length;a++){if(d[a].length===0){this.error("Row "+(1+a)+" of data is empty");return null}if(d[a][0]===null||typeof(d[a][0].getTime)!="function"||isNaN(d[a][0].getTime())){this.error("x value in row "+(1+a)+" is not a Date");return null}d[a][0]=d[a][0].getTime()}return d}else{this.attrs_.axes.x.valueFormatter=function(e){return e};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=Dygraph.numberAxisLabelFormatter;return c}};Dygraph.prototype.parseDataTable_=function(w){var d=function(i){var j=String.fromCharCode(65+i%26);i=Math.floor(i/26);while(i>0){j=String.fromCharCode(65+(i-1)%26)+j.toLowerCase();i=Math.floor((i-1)/26)}return j};var h=w.getNumberOfColumns();var g=w.getNumberOfRows();var f=w.getColumnType(0);if(f=="date"||f=="datetime"){this.attrs_.xValueParser=Dygraph.dateParser;this.attrs_.axes.x.valueFormatter=Dygraph.dateString_;this.attrs_.axes.x.ticker=Dygraph.dateTicker;this.attrs_.axes.x.axisLabelFormatter=Dygraph.dateAxisFormatter}else{if(f=="number"){this.attrs_.xValueParser=function(i){return parseFloat(i)};this.attrs_.axes.x.valueFormatter=function(i){return i};this.attrs_.axes.x.ticker=Dygraph.numericLinearTicks;this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}else{this.error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+f+"')");return null}}var m=[];var t={};var s=false;var q,o;for(q=1;q<h;q++){var b=w.getColumnType(q);if(b=="number"){m.push(q)}else{if(b=="string"&&this.attr_("displayAnnotations")){var r=m[m.length-1];if(!t.hasOwnProperty(r)){t[r]=[q]}else{t[r].push(q)}s=true}else{this.error("Only 'number' is supported as a dependent type with Gviz. 'string' is only supported if displayAnnotations is true")}}}var u=[w.getColumnLabel(0)];for(q=0;q<m.length;q++){u.push(w.getColumnLabel(m[q]));if(this.attr_("errorBars")){q+=1}}this.attrs_.labels=u;h=u.length;var v=[];var l=false;var a=[];for(q=0;q<g;q++){var e=[];if(typeof(w.getValue(q,0))==="undefined"||w.getValue(q,0)===null){this.warn("Ignoring row "+q+" of DataTable because of undefined or null first column.");continue}if(f=="date"||f=="datetime"){e.push(w.getValue(q,0).getTime())}else{e.push(w.getValue(q,0))}if(!this.attr_("errorBars")){for(o=0;o<m.length;o++){var c=m[o];e.push(w.getValue(q,c));if(s&&t.hasOwnProperty(c)&&w.getValue(q,t[c][0])!==null){var p={};p.series=w.getColumnLabel(c);p.xval=e[0];p.shortText=d(a.length);p.text="";for(var n=0;n<t[c].length;n++){if(n){p.text+="\n"}p.text+=w.getValue(q,t[c][n])}a.push(p)}}for(o=0;o<e.length;o++){if(!isFinite(e[o])){e[o]=null}}}else{for(o=0;o<h-1;o++){e.push([w.getValue(q,1+2*o),w.getValue(q,2+2*o)])}}if(v.length>0&&e[0]<v[v.length-1][0]){l=true}v.push(e)}if(l){this.warn("DataTable is out of order; order it correctly to speed loading.");v.sort(function(j,i){return j[0]-i[0]})}this.rawData_=v;if(a.length>0){this.setAnnotations(a,true)}this.attributes_.reparseSeries()};Dygraph.prototype.start_=function(){var d=this.file_;if(typeof d=="function"){d=d()}if(Dygraph.isArrayLike(d)){this.rawData_=this.parseArray_(d);this.predraw_()}else{if(typeof d=="object"&&typeof d.getColumnRange=="function"){this.parseDataTable_(d);this.predraw_()}else{if(typeof d=="string"){var c=Dygraph.detectLineDelimiter(d);if(c){this.loadedEvent_(d)}else{var b;if(window.XMLHttpRequest){b=new XMLHttpRequest()}else{b=new ActiveXObject("Microsoft.XMLHTTP")}var a=this;b.onreadystatechange=function(){if(b.readyState==4){if(b.status===200||b.status===0){a.loadedEvent_(b.responseText)}}};b.open("GET",d,true);b.send(null)}}else{this.error("Unknown data format: "+(typeof d))}}}};Dygraph.prototype.updateOptions=function(e,b){if(typeof(b)=="undefined"){b=false}var d=e.file;var c=Dygraph.mapLegacyOptions_(e);if("rollPeriod" in c){this.rollPeriod_=c.rollPeriod}if("dateWindow" in c){this.dateWindow_=c.dateWindow;if(!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_x_=(c.dateWindow!==null)}}if("valueRange" in c&&!("isZoomedIgnoreProgrammaticZoom" in c)){this.zoomed_y_=(c.valueRange!==null)}var a=Dygraph.isPixelChangingOptionList(this.attr_("labels"),c);Dygraph.updateDeep(this.user_attrs_,c);this.attributes_.reparseSeries();if(d){this.file_=d;if(!b){this.start_()}}else{if(!b){if(a){this.predraw_()}else{this.renderGraph_(false)}}}};Dygraph.mapLegacyOptions_=function(c){var a={};for(var b in c){if(b=="file"){continue}if(c.hasOwnProperty(b)){a[b]=c[b]}}var e=function(g,f,h){if(!a.axes){a.axes={}}if(!a.axes[g]){a.axes[g]={}}a.axes[g][f]=h};var d=function(f,g,h){if(typeof(c[f])!="undefined"){Dygraph.warn("Option "+f+" is deprecated. Use the "+h+" option for the "+g+" axis instead. (e.g. { axes : { "+g+" : { "+h+" : ... } } } (see http://dygraphs.com/per-axis.html for more information.");e(g,h,c[f]);delete a[f]}};d("xValueFormatter","x","valueFormatter");d("pixelsPerXLabel","x","pixelsPerLabel");d("xAxisLabelFormatter","x","axisLabelFormatter");d("xTicker","x","ticker");d("yValueFormatter","y","valueFormatter");d("pixelsPerYLabel","y","pixelsPerLabel");d("yAxisLabelFormatter","y","axisLabelFormatter");d("yTicker","y","ticker");return a};Dygraph.prototype.resize=function(d,b){if(this.resize_lock){return}this.resize_lock=true;if((d===null)!=(b===null)){this.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero.");d=b=null}var a=this.width_;var c=this.height_;if(d){this.maindiv_.style.width=d+"px";this.maindiv_.style.height=b+"px";this.width_=d;this.height_=b}else{this.width_=this.maindiv_.clientWidth;this.height_=this.maindiv_.clientHeight}if(a!=this.width_||c!=this.height_){this.resizeElements_();this.predraw_()}this.resize_lock=false};Dygraph.prototype.adjustRoll=function(a){this.rollPeriod_=a;this.predraw_()};Dygraph.prototype.visibility=function(){if(!this.attr_("visibility")){this.attrs_.visibility=[]}while(this.attr_("visibility").length<this.numColumns()-1){this.attrs_.visibility.push(true)}return this.attr_("visibility")};Dygraph.prototype.setVisibility=function(b,c){var a=this.visibility();if(b<0||b>=a.length){this.warn("invalid series number in setVisibility: "+b)}else{a[b]=c;this.predraw_()}};Dygraph.prototype.size=function(){return{width:this.width_,height:this.height_}};Dygraph.prototype.setAnnotations=function(b,a){Dygraph.addAnnotationRule();this.annotations_=b;if(!this.layout_){this.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html");return}this.layout_.setAnnotations(this.annotations_);if(!a){this.predraw_()}};Dygraph.prototype.annotations=function(){return this.annotations_};Dygraph.prototype.getLabels=function(){var a=this.attr_("labels");return a?a.slice():null};Dygraph.prototype.indexFromSetName=function(a){return this.setIndexByName_[a]};Dygraph.prototype.ready=function(a){if(this.is_initial_draw_){this.readyFns_.push(a)}else{a(this)}};Dygraph.addAnnotationRule=function(){if(Dygraph.addedAnnotationCSS){return}var f="border: 1px solid black; background-color: white; text-align: center;";var e=document.createElement("style");e.type="text/css";document.getElementsByTagName("head")[0].appendChild(e);for(var b=0;b<document.styleSheets.length;b++){if(document.styleSheets[b].disabled){continue}var d=document.styleSheets[b];try{if(d.insertRule){var a=d.cssRules?d.cssRules.length:0;d.insertRule(".dygraphDefaultAnnotation { "+f+" }",a)}else{if(d.addRule){d.addRule(".dygraphDefaultAnnotation",f)}}Dygraph.addedAnnotationCSS=true;return}catch(c){}}this.warn("Unable to add default annotation CSS rule; display may be off.")};var DateGraph=Dygraph;"use strict";Dygraph.LOG_SCALE=10;Dygraph.LN_TEN=Math.log(Dygraph.LOG_SCALE);Dygraph.log10=function(a){return Math.log(a)/Dygraph.LN_TEN};Dygraph.DEBUG=1;Dygraph.INFO=2;Dygraph.WARNING=3;Dygraph.ERROR=3;Dygraph.LOG_STACK_TRACES=false;Dygraph.DOTTED_LINE=[2,2];Dygraph.DASHED_LINE=[7,3];Dygraph.DOT_DASH_LINE=[7,2,2,2];Dygraph.log=function(c,g){var b;if(typeof(printStackTrace)!="undefined"){try{b=printStackTrace({guess:false});while(b[0].indexOf("stacktrace")!=-1){b.splice(0,1)}b.splice(0,2);for(var d=0;d<b.length;d++){b[d]=b[d].replace(/\([^)]*\/(.*)\)/,"@$1").replace(/\@.*\/([^\/]*)/,"@$1").replace("[object Object].","")}var j=b.splice(0,1)[0];g+=" ("+j.replace(/^.*@ ?/,"")+")"}catch(h){}}if(typeof(window.console)!="undefined"){var a=window.console;var f=function(e,k,i){if(k&&typeof(k)=="function"){k.call(e,i)}else{e.log(i)}};switch(c){case Dygraph.DEBUG:f(a,a.debug,"dygraphs: "+g);break;case Dygraph.INFO:f(a,a.info,"dygraphs: "+g);break;case Dygraph.WARNING:f(a,a.warn,"dygraphs: "+g);break;case Dygraph.ERROR:f(a,a.error,"dygraphs: "+g);break}}if(Dygraph.LOG_STACK_TRACES){window.console.log(b.join("\n"))}};Dygraph.info=function(a){Dygraph.log(Dygraph.INFO,a)};Dygraph.prototype.info=Dygraph.info;Dygraph.warn=function(a){Dygraph.log(Dygraph.WARNING,a)};Dygraph.prototype.warn=Dygraph.warn;Dygraph.error=function(a){Dygraph.log(Dygraph.ERROR,a)};Dygraph.prototype.error=Dygraph.error;Dygraph.getContext=function(a){return(a.getContext("2d"))};Dygraph.addEvent=function addEvent(c,b,a){if(c.addEventListener){c.addEventListener(b,a,false)}else{c[b+a]=function(){a(window.event)};c.attachEvent("on"+b,c[b+a])}};Dygraph.prototype.addAndTrackEvent=function(c,b,a){Dygraph.addEvent(c,b,a);this.registeredEvents_.push({elem:c,type:b,fn:a})};Dygraph.removeEvent=function(c,b,a){if(c.removeEventListener){c.removeEventListener(b,a,false)}else{try{c.detachEvent("on"+b,c[b+a])}catch(d){}c[b+a]=null}};Dygraph.prototype.removeTrackedEvents_=function(){if(this.registeredEvents_){for(var a=0;a<this.registeredEvents_.length;a++){var b=this.registeredEvents_[a];Dygraph.removeEvent(b.elem,b.type,b.fn)}}this.registeredEvents_=[]};Dygraph.cancelEvent=function(a){a=a?a:window.event;if(a.stopPropagation){a.stopPropagation()}if(a.preventDefault){a.preventDefault()}a.cancelBubble=true;a.cancel=true;a.returnValue=false;return false};Dygraph.hsvToRGB=function(h,g,k){var c;var d;var l;if(g===0){c=k;d=k;l=k}else{var e=Math.floor(h*6);var j=(h*6)-e;var b=k*(1-g);var a=k*(1-(g*j));var m=k*(1-(g*(1-j)));switch(e){case 1:c=a;d=k;l=b;break;case 2:c=b;d=k;l=m;break;case 3:c=b;d=a;l=k;break;case 4:c=m;d=b;l=k;break;case 5:c=k;d=b;l=a;break;case 6:case 0:c=k;d=m;l=b;break}}c=Math.floor(255*c+0.5);d=Math.floor(255*d+0.5);l=Math.floor(255*l+0.5);return"rgb("+c+","+d+","+l+")"};Dygraph.findPosX=function(c){var d=0;if(c.offsetParent){var a=c;while(1){var b="0";if(window.getComputedStyle){b=window.getComputedStyle(a,null).borderLeft||"0"}d+=parseInt(b,10);d+=a.offsetLeft;if(!a.offsetParent){break}a=a.offsetParent}}else{if(c.x){d+=c.x}}while(c&&c!=document.body){d-=c.scrollLeft;c=c.parentNode}return d};Dygraph.findPosY=function(c){var b=0;if(c.offsetParent){var a=c;while(1){var d="0";if(window.getComputedStyle){d=window.getComputedStyle(a,null).borderTop||"0"}b+=parseInt(d,10);b+=a.offsetTop;if(!a.offsetParent){break}a=a.offsetParent}}else{if(c.y){b+=c.y}}while(c&&c!=document.body){b-=c.scrollTop;c=c.parentNode}return b};Dygraph.pageX=function(c){if(c.pageX){return(!c.pageX||c.pageX<0)?0:c.pageX}else{var d=document.documentElement;var a=document.body;return c.clientX+(d.scrollLeft||a.scrollLeft)-(d.clientLeft||0)}};Dygraph.pageY=function(c){if(c.pageY){return(!c.pageY||c.pageY<0)?0:c.pageY}else{var d=document.documentElement;var a=document.body;return c.clientY+(d.scrollTop||a.scrollTop)-(d.clientTop||0)}};Dygraph.isOK=function(a){return !!a&&!isNaN(a)};Dygraph.isValidPoint=function(b,a){if(!b){return false}if(b.yval===null){return false}if(b.x===null||b.x===undefined){return false}if(b.y===null||b.y===undefined){return false}if(isNaN(b.x)||(!a&&isNaN(b.y))){return false}return true};Dygraph.floatFormat=function(a,b){var c=Math.min(Math.max(1,b||2),21);return(Math.abs(a)<0.001&&a!==0)?a.toExponential(c-1):a.toPrecision(c)};Dygraph.zeropad=function(a){if(a<10){return"0"+a}else{return""+a}};Dygraph.hmsString_=function(a){var c=Dygraph.zeropad;var b=new Date(a);if(b.getSeconds()){return c(b.getHours())+":"+c(b.getMinutes())+":"+c(b.getSeconds())}else{return c(b.getHours())+":"+c(b.getMinutes())}};Dygraph.round_=function(c,b){var a=Math.pow(10,b);return Math.round(c*a)/a};Dygraph.binarySearch=function(a,d,i,e,b){if(e===null||e===undefined||b===null||b===undefined){e=0;b=d.length-1}if(e>b){return -1}if(i===null||i===undefined){i=0}var h=function(j){return j>=0&&j<d.length};var g=parseInt((e+b)/2,10);var c=d[g];var f;if(c==a){return g}else{if(c>a){if(i>0){f=g-1;if(h(f)&&d[f]<a){return g}}return Dygraph.binarySearch(a,d,i,e,g-1)}else{if(c<a){if(i<0){f=g+1;if(h(f)&&d[f]>a){return g}}return Dygraph.binarySearch(a,d,i,g+1,b)}}}return -1};Dygraph.dateParser=function(a){var b;var c;if(a.search("-")==-1||a.search("T")!=-1||a.search("Z")!=-1){c=Dygraph.dateStrToMillis(a);if(c&&!isNaN(c)){return c}}if(a.search("-")!=-1){b=a.replace("-","/","g");while(b.search("-")!=-1){b=b.replace("-","/")}c=Dygraph.dateStrToMillis(b)}else{if(a.length==8){b=a.substr(0,4)+"/"+a.substr(4,2)+"/"+a.substr(6,2);c=Dygraph.dateStrToMillis(b)}else{c=Dygraph.dateStrToMillis(a)}}if(!c||isNaN(c)){Dygraph.error("Couldn't parse "+a+" as a date")}return c};Dygraph.dateStrToMillis=function(a){return new Date(a).getTime()};Dygraph.update=function(b,c){if(typeof(c)!="undefined"&&c!==null){for(var a in c){if(c.hasOwnProperty(a)){b[a]=c[a]}}}return b};Dygraph.updateDeep=function(b,d){function c(e){return(typeof Node==="object"?e instanceof Node:typeof e==="object"&&typeof e.nodeType==="number"&&typeof e.nodeName==="string")}if(typeof(d)!="undefined"&&d!==null){for(var a in d){if(d.hasOwnProperty(a)){if(d[a]===null){b[a]=null}else{if(Dygraph.isArrayLike(d[a])){b[a]=d[a].slice()}else{if(c(d[a])){b[a]=d[a]}else{if(typeof(d[a])=="object"){if(typeof(b[a])!="object"||b[a]===null){b[a]={}}Dygraph.updateDeep(b[a],d[a])}else{b[a]=d[a]}}}}}}}return b};Dygraph.isArrayLike=function(b){var a=typeof(b);if((a!="object"&&!(a=="function"&&typeof(b.item)=="function"))||b===null||typeof(b.length)!="number"||b.nodeType===3){return false}return true};Dygraph.isDateLike=function(a){if(typeof(a)!="object"||a===null||typeof(a.getTime)!="function"){return false}return true};Dygraph.clone=function(c){var b=[];for(var a=0;a<c.length;a++){if(Dygraph.isArrayLike(c[a])){b.push(Dygraph.clone(c[a]))}else{b.push(c[a])}}return b};Dygraph.createCanvas=function(){var a=document.createElement("canvas");var b=(/MSIE/.test(navigator.userAgent)&&!window.opera);if(b&&(typeof(G_vmlCanvasManager)!="undefined")){a=G_vmlCanvasManager.initElement((a))}return a};Dygraph.isAndroid=function(){return(/Android/).test(navigator.userAgent)};Dygraph.Iterator=function(d,c,b,a){c=c||0;b=b||d.length;this.hasNext=true;this.peek=null;this.start_=c;this.array_=d;this.predicate_=a;this.end_=Math.min(d.length,c+b);this.nextIdx_=c-1;this.next()};Dygraph.Iterator.prototype.next=function(){if(!this.hasNext){return null}var c=this.peek;var b=this.nextIdx_+1;var a=false;while(b<this.end_){if(!this.predicate_||this.predicate_(this.array_,b)){this.peek=this.array_[b];a=true;break}b++}this.nextIdx_=b;if(!a){this.hasNext=false;this.peek=null}return c};Dygraph.createIterator=function(d,c,b,a){return new Dygraph.Iterator(d,c,b,a)};Dygraph.requestAnimFrame=(function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1000/60)}})();Dygraph.repeatAndCleanup=function(h,g,f,a){var i=0;var d;var b=new Date().getTime();h(i);if(g==1){a();return}var e=g-1;(function c(){if(i>=g){return}Dygraph.requestAnimFrame.call(window,function(){var l=new Date().getTime();var j=l-b;d=i;i=Math.floor(j/f);var k=i-d;var m=(i+k)>e;if(m||(i>=e)){h(e);a()}else{if(k!==0){h(i)}c()}})})()};Dygraph.isPixelChangingOptionList=function(h,e){var d={annotationClickHandler:true,annotationDblClickHandler:true,annotationMouseOutHandler:true,annotationMouseOverHandler:true,axisLabelColor:true,axisLineColor:true,axisLineWidth:true,clickCallback:true,digitsAfterDecimal:true,drawCallback:true,drawHighlightPointCallback:true,drawPoints:true,drawPointCallback:true,drawXGrid:true,drawYGrid:true,fillAlpha:true,gridLineColor:true,gridLineWidth:true,hideOverlayOnMouseOut:true,highlightCallback:true,highlightCircleSize:true,interactionModel:true,isZoomedIgnoreProgrammaticZoom:true,labelsDiv:true,labelsDivStyles:true,labelsDivWidth:true,labelsKMB:true,labelsKMG2:true,labelsSeparateLines:true,labelsShowZeroValues:true,legend:true,maxNumberWidth:true,panEdgeFraction:true,pixelsPerYLabel:true,pointClickCallback:true,pointSize:true,rangeSelectorPlotFillColor:true,rangeSelectorPlotStrokeColor:true,showLabelsOnHighlight:true,showRoller:true,sigFigs:true,strokeWidth:true,underlayCallback:true,unhighlightCallback:true,xAxisLabelFormatter:true,xTicker:true,xValueFormatter:true,yAxisLabelFormatter:true,yValueFormatter:true,zoomCallback:true};var a=false;var b={};if(h){for(var f=1;f<h.length;f++){b[h[f]]=true}}for(var g in e){if(a){break}if(e.hasOwnProperty(g)){if(b[g]){for(var c in e[g]){if(a){break}if(e[g].hasOwnProperty(c)&&!d[c]){a=true}}}else{if(!d[g]){a=true}}}}return a};Dygraph.compareArrays=function(c,b){if(!Dygraph.isArrayLike(c)||!Dygraph.isArrayLike(b)){return false}if(c.length!==b.length){return false}for(var a=0;a<c.length;a++){if(c[a]!==b[a]){return false}}return true};Dygraph.regularShape_=function(o,c,i,f,e,a,n){a=a||0;n=n||Math.PI*2/c;o.beginPath();var g=a;var d=g;var h=function(){var p=f+(Math.sin(d)*i);var q=e+(-Math.cos(d)*i);return[p,q]};var b=h();var l=b[0];var j=b[1];o.moveTo(l,j);for(var m=0;m<c;m++){d=(m==c-1)?g:(d+n);var k=h();o.lineTo(k[0],k[1])}o.fill();o.stroke()};Dygraph.shapeFunction_=function(b,a,c){return function(j,i,f,e,k,h,d){f.strokeStyle=h;f.fillStyle="white";Dygraph.regularShape_(f,b,d,e,k,a,c)}};Dygraph.Circles={DEFAULT:function(h,f,b,e,d,c,a){b.beginPath();b.fillStyle=c;b.arc(e,d,a,0,2*Math.PI,false);b.fill()},TRIANGLE:Dygraph.shapeFunction_(3),SQUARE:Dygraph.shapeFunction_(4,Math.PI/4),DIAMOND:Dygraph.shapeFunction_(4),PENTAGON:Dygraph.shapeFunction_(5),HEXAGON:Dygraph.shapeFunction_(6),CIRCLE:function(f,e,c,b,h,d,a){c.beginPath();c.strokeStyle=d;c.fillStyle="white";c.arc(b,h,a,0,2*Math.PI,false);c.fill();c.stroke()},STAR:Dygraph.shapeFunction_(5,0,4*Math.PI/5),PLUS:function(f,e,c,b,h,d,a){c.strokeStyle=d;c.beginPath();c.moveTo(b+a,h);c.lineTo(b-a,h);c.closePath();c.stroke();c.beginPath();c.moveTo(b,h+a);c.lineTo(b,h-a);c.closePath();c.stroke()},EX:function(f,e,c,b,h,d,a){c.strokeStyle=d;c.beginPath();c.moveTo(b+a,h+a);c.lineTo(b-a,h-a);c.closePath();c.stroke();c.beginPath();c.moveTo(b+a,h-a);c.lineTo(b-a,h+a);c.closePath();c.stroke()}};Dygraph.IFrameTarp=function(){this.tarps=[]};Dygraph.IFrameTarp.prototype.cover=function(){var f=document.getElementsByTagName("iframe");for(var c=0;c<f.length;c++){var e=f[c];var b=Dygraph.findPosX(e),h=Dygraph.findPosY(e),d=e.offsetWidth,a=e.offsetHeight;var g=document.createElement("div");g.style.position="absolute";g.style.left=b+"px";g.style.top=h+"px";g.style.width=d+"px";g.style.height=a+"px";g.style.zIndex=999;document.body.appendChild(g);this.tarps.push(g)}};Dygraph.IFrameTarp.prototype.uncover=function(){for(var a=0;a<this.tarps.length;a++){this.tarps[a].parentNode.removeChild(this.tarps[a])}this.tarps=[]};Dygraph.detectLineDelimiter=function(c){for(var a=0;a<c.length;a++){var b=c.charAt(a);if(b==="\r"){if(((a+1)<c.length)&&(c.charAt(a+1)==="\n")){return"\r\n"}return b}if(b==="\n"){if(((a+1)<c.length)&&(c.charAt(a+1)==="\r")){return"\n\r"}return b}}return null};Dygraph.isNodeContainedBy=function(b,a){if(a===null||b===null){return false}var c=(b);while(c&&c!==a){c=c.parentNode}return(c===a)};Dygraph.pow=function(a,b){if(b<0){return 1/Math.pow(a,-b)}return Math.pow(a,b)};Dygraph.dateSetters={ms:Date.prototype.setMilliseconds,s:Date.prototype.setSeconds,m:Date.prototype.setMinutes,h:Date.prototype.setHours};Dygraph.setDateSameTZ=function(c,b){var f=c.getTimezoneOffset();for(var a in b){if(!b.hasOwnProperty(a)){continue}var e=Dygraph.dateSetters[a];if(!e){throw"Invalid setter: "+a}e.call(c,b[a]);if(c.getTimezoneOffset()!=f){c.setTime(c.getTime()+(f-c.getTimezoneOffset())*60*1000)}}};"use strict";Dygraph.GVizChart=function(a){this.container=a};Dygraph.GVizChart.prototype.draw=function(b,a){this.container.innerHTML="";if(typeof(this.date_graph)!="undefined"){this.date_graph.destroy()}this.date_graph=new Dygraph(this.container,b,a)};Dygraph.GVizChart.prototype.setSelection=function(b){var a=false;if(b.length){a=b[0].row}this.date_graph.setSelection(a)};Dygraph.GVizChart.prototype.getSelection=function(){var b=[];var d=this.date_graph.getSelection();if(d<0){return b}var a=this.date_graph.layout_.points;for(var c=0;c<a.length;++c){b.push({row:d,column:c+1})}return b};"use strict";Dygraph.Interaction={};Dygraph.Interaction.startPan=function(o,t,c){var r,b;c.isPanning=true;var k=t.xAxisRange();c.dateRange=k[1]-k[0];c.initialLeftmostDate=k[0];c.xUnitsPerPixel=c.dateRange/(t.plotter_.area.w-1);if(t.attr_("panEdgeFraction")){var x=t.width_*t.attr_("panEdgeFraction");var d=t.xAxisExtremes();var j=t.toDomXCoord(d[0])-x;var l=t.toDomXCoord(d[1])+x;var u=t.toDataXCoord(j);var w=t.toDataXCoord(l);c.boundedDates=[u,w];var f=[];var a=t.height_*t.attr_("panEdgeFraction");for(r=0;r<t.axes_.length;r++){b=t.axes_[r];var p=b.extremeRange;var q=t.toDomYCoord(p[0],r)+a;var s=t.toDomYCoord(p[1],r)-a;var n=t.toDataYCoord(q,r);var e=t.toDataYCoord(s,r);f[r]=[n,e]}c.boundedValues=f}c.is2DPan=false;c.axes=[];for(r=0;r<t.axes_.length;r++){b=t.axes_[r];var h={};var m=t.yAxisRange(r);var v=t.attributes_.getForAxis("logscale",r);if(v){h.initialTopValue=Dygraph.log10(m[1]);h.dragValueRange=Dygraph.log10(m[1])-Dygraph.log10(m[0])}else{h.initialTopValue=m[1];h.dragValueRange=m[1]-m[0]}h.unitsPerPixel=h.dragValueRange/(t.plotter_.area.h-1);c.axes.push(h);if(b.valueWindow||b.valueRange){c.is2DPan=true}}};Dygraph.Interaction.movePan=function(b,k,c){c.dragEndX=k.dragGetX_(b,c);c.dragEndY=k.dragGetY_(b,c);var h=c.initialLeftmostDate-(c.dragEndX-c.dragStartX)*c.xUnitsPerPixel;if(c.boundedDates){h=Math.max(h,c.boundedDates[0])}var a=h+c.dateRange;if(c.boundedDates){if(a>c.boundedDates[1]){h=h-(a-c.boundedDates[1]);a=h+c.dateRange}}k.dateWindow_=[h,a];if(c.is2DPan){var d=c.dragEndY-c.dragStartY;for(var j=0;j<k.axes_.length;j++){var e=k.axes_[j];var o=c.axes[j];var p=d*o.unitsPerPixel;var f=c.boundedValues?c.boundedValues[j]:null;var l=o.initialTopValue+p;if(f){l=Math.min(l,f[1])}var n=l-o.dragValueRange;if(f){if(n<f[0]){l=l-(n-f[0]);n=l-o.dragValueRange}}var m=k.attributes_.getForAxis("logscale",j);if(m){e.valueWindow=[Math.pow(Dygraph.LOG_SCALE,n),Math.pow(Dygraph.LOG_SCALE,l)]}else{e.valueWindow=[n,l]}}}k.drawGraph_(false)};Dygraph.Interaction.endPan=function(c,b,a){a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragEndX-a.dragStartX);var d=Math.abs(a.dragEndY-a.dragStartY);if(e<2&&d<2&&b.lastx_!==undefined&&b.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(b,c,a)}a.isPanning=false;a.is2DPan=false;a.initialLeftmostDate=null;a.dateRange=null;a.valueRange=null;a.boundedDates=null;a.boundedValues=null;a.axes=null};Dygraph.Interaction.startZoom=function(c,b,a){a.isZooming=true;a.zoomMoved=false};Dygraph.Interaction.moveZoom=function(c,b,a){a.zoomMoved=true;a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragStartX-a.dragEndX);var d=Math.abs(a.dragStartY-a.dragEndY);a.dragDirection=(e<d/2)?Dygraph.VERTICAL:Dygraph.HORIZONTAL;b.drawZoomRect_(a.dragDirection,a.dragStartX,a.dragEndX,a.dragStartY,a.dragEndY,a.prevDragDirection,a.prevEndX,a.prevEndY);a.prevEndX=a.dragEndX;a.prevEndY=a.dragEndY;a.prevDragDirection=a.dragDirection};Dygraph.Interaction.treatMouseOpAsClick=function(f,b,d){var k=f.attr_("clickCallback");var n=f.attr_("pointClickCallback");var j=null;if(n){var l=-1;var m=Number.MAX_VALUE;for(var e=0;e<f.selPoints_.length;e++){var c=f.selPoints_[e];var a=Math.pow(c.canvasx-d.dragEndX,2)+Math.pow(c.canvasy-d.dragEndY,2);if(!isNaN(a)&&(l==-1||a<m)){m=a;l=e}}var h=f.attr_("highlightCircleSize")+2;if(m<=h*h){j=f.selPoints_[l]}}if(j){n(b,j)}if(k){k(b,f.lastx_,f.selPoints_)}};Dygraph.Interaction.endZoom=function(c,i,e){e.isZooming=false;e.dragEndX=i.dragGetX_(c,e);e.dragEndY=i.dragGetY_(c,e);var h=Math.abs(e.dragEndX-e.dragStartX);var d=Math.abs(e.dragEndY-e.dragStartY);if(h<2&&d<2&&i.lastx_!==undefined&&i.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(i,c,e)}var b=i.getArea();if(h>=10&&e.dragDirection==Dygraph.HORIZONTAL){var f=Math.min(e.dragStartX,e.dragEndX),k=Math.max(e.dragStartX,e.dragEndX);f=Math.max(f,b.x);k=Math.min(k,b.x+b.w);if(f<k){i.doZoomX_(f,k)}e.cancelNextDblclick=true}else{if(d>=10&&e.dragDirection==Dygraph.VERTICAL){var j=Math.min(e.dragStartY,e.dragEndY),a=Math.max(e.dragStartY,e.dragEndY);j=Math.max(j,b.y);a=Math.min(a,b.y+b.h);if(j<a){i.doZoomY_(j,a)}e.cancelNextDblclick=true}else{if(e.zoomMoved){i.clearZoomRect_()}}}e.dragStartX=null;e.dragStartY=null};Dygraph.Interaction.startTouch=function(f,e,d){f.preventDefault();if(f.touches.length>1){d.startTimeForDoubleTapMs=null}var h=[];for(var c=0;c<f.touches.length;c++){var b=f.touches[c];h.push({pageX:b.pageX,pageY:b.pageY,dataX:e.toDataXCoord(b.pageX),dataY:e.toDataYCoord(b.pageY)})}d.initialTouches=h;if(h.length==1){d.initialPinchCenter=h[0];d.touchDirections={x:true,y:true}}else{if(h.length>=2){d.initialPinchCenter={pageX:0.5*(h[0].pageX+h[1].pageX),pageY:0.5*(h[0].pageY+h[1].pageY),dataX:0.5*(h[0].dataX+h[1].dataX),dataY:0.5*(h[0].dataY+h[1].dataY)};var a=180/Math.PI*Math.atan2(d.initialPinchCenter.pageY-h[0].pageY,h[0].pageX-d.initialPinchCenter.pageX);a=Math.abs(a);if(a>90){a=90-a}d.touchDirections={x:(a<(90-45/2)),y:(a>45/2)}}}d.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}};Dygraph.Interaction.moveTouch=function(n,q,d){d.startTimeForDoubleTapMs=null;var p,l=[];for(p=0;p<n.touches.length;p++){var k=n.touches[p];l.push({pageX:k.pageX,pageY:k.pageY})}var a=d.initialTouches;var h;var j=d.initialPinchCenter;if(l.length==1){h=l[0]}else{h={pageX:0.5*(l[0].pageX+l[1].pageX),pageY:0.5*(l[0].pageY+l[1].pageY)}}var m={pageX:h.pageX-j.pageX,pageY:h.pageY-j.pageY};var f=d.initialRange.x[1]-d.initialRange.x[0];var o=d.initialRange.y[0]-d.initialRange.y[1];m.dataX=(m.pageX/q.plotter_.area.w)*f;m.dataY=(m.pageY/q.plotter_.area.h)*o;var w,c;if(l.length==1){w=1;c=1}else{if(l.length>=2){var e=(a[1].pageX-j.pageX);w=(l[1].pageX-h.pageX)/e;var v=(a[1].pageY-j.pageY);c=(l[1].pageY-h.pageY)/v}}w=Math.min(8,Math.max(0.125,w));c=Math.min(8,Math.max(0.125,c));var u=false;if(d.touchDirections.x){q.dateWindow_=[j.dataX-m.dataX+(d.initialRange.x[0]-j.dataX)/w,j.dataX-m.dataX+(d.initialRange.x[1]-j.dataX)/w];u=true}if(d.touchDirections.y){for(p=0;p<1;p++){var b=q.axes_[p];var s=q.attributes_.getForAxis("logscale",p);if(s){}else{b.valueWindow=[j.dataY-m.dataY+(d.initialRange.y[0]-j.dataY)/c,j.dataY-m.dataY+(d.initialRange.y[1]-j.dataY)/c];u=true}}}q.drawGraph_(false);if(u&&l.length>1&&q.attr_("zoomCallback")){var r=q.xAxisRange();q.attr_("zoomCallback")(r[0],r[1],q.yAxisRanges())}};Dygraph.Interaction.endTouch=function(e,d,c){if(e.touches.length!==0){Dygraph.Interaction.startTouch(e,d,c)}else{if(e.changedTouches.length==1){var a=new Date().getTime();var b=e.changedTouches[0];if(c.startTimeForDoubleTapMs&&a-c.startTimeForDoubleTapMs<500&&c.doubleTapX&&Math.abs(c.doubleTapX-b.screenX)<50&&c.doubleTapY&&Math.abs(c.doubleTapY-b.screenY)<50){d.resetZoom()}else{c.startTimeForDoubleTapMs=a;c.doubleTapX=b.screenX;c.doubleTapY=b.screenY}}}};Dygraph.Interaction.defaultModel={mousedown:function(c,b,a){if(c.button&&c.button==2){return}a.initializeMouseDown(c,b,a);if(c.altKey||c.shiftKey){Dygraph.startPan(c,b,a)}else{Dygraph.startZoom(c,b,a)}},mousemove:function(c,b,a){if(a.isZooming){Dygraph.moveZoom(c,b,a)}else{if(a.isPanning){Dygraph.movePan(c,b,a)}}},mouseup:function(c,b,a){if(a.isZooming){Dygraph.endZoom(c,b,a)}else{if(a.isPanning){Dygraph.endPan(c,b,a)}}},touchstart:function(c,b,a){Dygraph.Interaction.startTouch(c,b,a)},touchmove:function(c,b,a){Dygraph.Interaction.moveTouch(c,b,a)},touchend:function(c,b,a){Dygraph.Interaction.endTouch(c,b,a)},mouseout:function(c,b,a){if(a.isZooming){a.dragEndX=null;a.dragEndY=null;b.clearZoomRect_()}},dblclick:function(c,b,a){if(a.cancelNextDblclick){a.cancelNextDblclick=false;return}if(c.altKey||c.shiftKey){return}b.resetZoom()}};Dygraph.DEFAULT_ATTRS.interactionModel=Dygraph.Interaction.defaultModel;Dygraph.defaultInteractionModel=Dygraph.Interaction.defaultModel;Dygraph.endZoom=Dygraph.Interaction.endZoom;Dygraph.moveZoom=Dygraph.Interaction.moveZoom;Dygraph.startZoom=Dygraph.Interaction.startZoom;Dygraph.endPan=Dygraph.Interaction.endPan;Dygraph.movePan=Dygraph.Interaction.movePan;Dygraph.startPan=Dygraph.Interaction.startPan;Dygraph.Interaction.nonInteractiveModel_={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a)},mouseup:function(c,b,a){a.dragEndX=b.dragGetX_(c,a);a.dragEndY=b.dragGetY_(c,a);var e=Math.abs(a.dragEndX-a.dragStartX);var d=Math.abs(a.dragEndY-a.dragStartY);if(e<2&&d<2&&b.lastx_!==undefined&&b.lastx_!=-1){Dygraph.Interaction.treatMouseOpAsClick(b,c,a)}}};Dygraph.Interaction.dragIsPanInteractionModel={mousedown:function(c,b,a){a.initializeMouseDown(c,b,a);Dygraph.startPan(c,b,a)},mousemove:function(c,b,a){if(a.isPanning){Dygraph.movePan(c,b,a)}},mouseup:function(c,b,a){if(a.isPanning){Dygraph.endPan(c,b,a)}}};"use strict";Dygraph.TickList=undefined;Dygraph.Ticker=undefined;Dygraph.numericLinearTicks=function(d,c,i,g,f,h){var e=function(a){if(a==="logscale"){return false}return g(a)};return Dygraph.numericTicks(d,c,i,e,f,h)};Dygraph.numericTicks=function(F,E,u,p,d,q){var z=(p("pixelsPerLabel"));var G=[];var C,A,t,y;if(q){for(C=0;C<q.length;C++){G.push({v:q[C]})}}else{if(p("logscale")){y=Math.floor(u/z);var l=Dygraph.binarySearch(F,Dygraph.PREFERRED_LOG_TICK_VALUES,1);var H=Dygraph.binarySearch(E,Dygraph.PREFERRED_LOG_TICK_VALUES,-1);if(l==-1){l=0}if(H==-1){H=Dygraph.PREFERRED_LOG_TICK_VALUES.length-1}var s=null;if(H-l>=y/4){for(var r=H;r>=l;r--){var m=Dygraph.PREFERRED_LOG_TICK_VALUES[r];var k=Math.log(m/F)/Math.log(E/F)*u;var D={v:m};if(s===null){s={tickValue:m,pixel_coord:k}}else{if(Math.abs(k-s.pixel_coord)>=z){s={tickValue:m,pixel_coord:k}}else{D.label=""}}G.push(D)}G.reverse()}}if(G.length===0){var g=p("labelsKMG2");var n,h;if(g){n=[1,2,4,8,16,32,64,128,256];h=16}else{n=[1,2,5,10,20,50,100];h=10}var w=Math.ceil(u/z);var o=Math.abs(E-F)/w;var v=Math.floor(Math.log(o)/Math.log(h));var f=Math.pow(h,v);var I,x,c,e;for(A=0;A<n.length;A++){I=f*n[A];x=Math.floor(F/I)*I;c=Math.ceil(E/I)*I;y=Math.abs(c-x)/I;e=u/y;if(e>z){break}}if(x>c){I*=-1}for(C=0;C<y;C++){t=x+C*I;G.push({v:t})}}}var B=(p("axisLabelFormatter"));for(C=0;C<G.length;C++){if(G[C].label!==undefined){continue}G[C].label=B(G[C].v,0,p,d)}return G};Dygraph.dateTicker=function(e,c,i,g,f,h){var d=Dygraph.pickDateTickGranularity(e,c,i,g);if(d>=0){return Dygraph.getDateAxis(e,c,d,g,f)}else{return[]}};Dygraph.SECONDLY=0;Dygraph.TWO_SECONDLY=1;Dygraph.FIVE_SECONDLY=2;Dygraph.TEN_SECONDLY=3;Dygraph.THIRTY_SECONDLY=4;Dygraph.MINUTELY=5;Dygraph.TWO_MINUTELY=6;Dygraph.FIVE_MINUTELY=7;Dygraph.TEN_MINUTELY=8;Dygraph.THIRTY_MINUTELY=9;Dygraph.HOURLY=10;Dygraph.TWO_HOURLY=11;Dygraph.SIX_HOURLY=12;Dygraph.DAILY=13;Dygraph.WEEKLY=14;Dygraph.MONTHLY=15;Dygraph.QUARTERLY=16;Dygraph.BIANNUAL=17;Dygraph.ANNUAL=18;Dygraph.DECADAL=19;Dygraph.CENTENNIAL=20;Dygraph.NUM_GRANULARITIES=21;Dygraph.SHORT_SPACINGS=[];Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]=1000*1;Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY]=1000*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY]=1000*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY]=1000*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY]=1000*30;Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY]=1000*60;Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY]=1000*60*2;Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY]=1000*60*5;Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY]=1000*60*10;Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY]=1000*60*30;Dygraph.SHORT_SPACINGS[Dygraph.HOURLY]=1000*3600;Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]=1000*3600*2;Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY]=1000*3600*6;Dygraph.SHORT_SPACINGS[Dygraph.DAILY]=1000*86400;Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]=1000*604800;Dygraph.LONG_TICK_PLACEMENTS=[];Dygraph.LONG_TICK_PLACEMENTS[Dygraph.MONTHLY]={months:[0,1,2,3,4,5,6,7,8,9,10,11],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.QUARTERLY]={months:[0,3,6,9],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.BIANNUAL]={months:[0,6],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.ANNUAL]={months:[0],year_mod:1};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.DECADAL]={months:[0],year_mod:10};Dygraph.LONG_TICK_PLACEMENTS[Dygraph.CENTENNIAL]={months:[0],year_mod:100};Dygraph.PREFERRED_LOG_TICK_VALUES=function(){var c=[];for(var b=-39;b<=39;b++){var a=Math.pow(10,b);for(var d=1;d<=9;d++){var e=a*d;c.push(e)}}return c}();Dygraph.pickDateTickGranularity=function(d,c,j,h){var g=(h("pixelsPerLabel"));for(var f=0;f<Dygraph.NUM_GRANULARITIES;f++){var e=Dygraph.numDateTicks(d,c,f);if(j/e>=g){return f}}return -1};Dygraph.numDateTicks=function(e,b,f){if(f<Dygraph.MONTHLY){var g=Dygraph.SHORT_SPACINGS[f];return Math.floor(0.5+1*(b-e)/g)}else{var d=Dygraph.LONG_TICK_PLACEMENTS[f];var c=365.2524*24*3600*1000;var a=1*(b-e)/c;return Math.floor(0.5+1*a*d.months.length/d.year_mod)}};Dygraph.getDateAxis=function(p,l,a,n,z){var w=(n("axisLabelFormatter"));var C=[];var m;if(a<Dygraph.MONTHLY){var c=Dygraph.SHORT_SPACINGS[a];var y=c/1000;var A=new Date(p);Dygraph.setDateSameTZ(A,{ms:0});var h;if(y<=60){h=A.getSeconds();Dygraph.setDateSameTZ(A,{s:h-h%y})}else{Dygraph.setDateSameTZ(A,{s:0});y/=60;if(y<=60){h=A.getMinutes();Dygraph.setDateSameTZ(A,{m:h-h%y})}else{Dygraph.setDateSameTZ(A,{m:0});y/=60;if(y<=24){h=A.getHours();A.setHours(h-h%y)}else{A.setHours(0);y/=24;if(y==7){A.setDate(A.getDate()-A.getDay())}}}}p=A.getTime();var B=new Date(p).getTimezoneOffset();var e=(c>=Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]);for(m=p;m<=l;m+=c){A=new Date(m);if(e&&A.getTimezoneOffset()!=B){var k=A.getTimezoneOffset()-B;m+=k*60*1000;A=new Date(m);B=A.getTimezoneOffset();if(new Date(m+c).getTimezoneOffset()!=B){m+=c;A=new Date(m);B=A.getTimezoneOffset()}}C.push({v:m,label:w(A,a,n,z)})}}else{var f;var r=1;if(a<Dygraph.NUM_GRANULARITIES){f=Dygraph.LONG_TICK_PLACEMENTS[a].months;r=Dygraph.LONG_TICK_PLACEMENTS[a].year_mod}else{Dygraph.warn("Span of dates is too long")}var v=new Date(p).getFullYear();var q=new Date(l).getFullYear();var b=Dygraph.zeropad;for(var u=v;u<=q;u++){if(u%r!==0){continue}for(var s=0;s<f.length;s++){var o=u+"/"+b(1+f[s])+"/01";m=Dygraph.dateStrToMillis(o);if(m<p||m>l){continue}C.push({v:m,label:w(new Date(m),a,n,z)})}}}return C};if(Dygraph&&Dygraph.DEFAULT_ATTRS&&Dygraph.DEFAULT_ATTRS.axes&&Dygraph.DEFAULT_ATTRS.axes["x"]&&Dygraph.DEFAULT_ATTRS.axes["y"]&&Dygraph.DEFAULT_ATTRS.axes["y2"]){Dygraph.DEFAULT_ATTRS.axes["x"]["ticker"]=Dygraph.dateTicker;Dygraph.DEFAULT_ATTRS.axes["y"]["ticker"]=Dygraph.numericTicks;Dygraph.DEFAULT_ATTRS.axes["y2"]["ticker"]=Dygraph.numericTicks}Dygraph.Plugins={};Dygraph.Plugins.Annotations=(function(){var a=function(){this.annotations_=[]};a.prototype.toString=function(){return"Annotations Plugin"};a.prototype.activate=function(b){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}};a.prototype.detachLabels=function(){for(var c=0;c<this.annotations_.length;c++){var b=this.annotations_[c];if(b.parentNode){b.parentNode.removeChild(b)}this.annotations_[c]=null}this.annotations_=[]};a.prototype.clearChart=function(b){this.detachLabels()};a.prototype.didDrawChart=function(v){var t=v.dygraph;var r=t.layout_.annotated_points;if(!r||r.length===0){return}var h=v.canvas.parentNode;var x={position:"absolute",fontSize:t.getOption("axisLabelFontSize")+"px",zIndex:10,overflow:"hidden"};var b=function(e,g,i){return function(y){var p=i.annotation;if(p.hasOwnProperty(e)){p[e](p,i,t,y)}else{if(t.getOption(g)){t.getOption(g)(p,i,t,y)}}}};var u=v.dygraph.plotter_.area;var q={};for(var s=0;s<r.length;s++){var l=r[s];if(l.canvasx<u.x||l.canvasx>u.x+u.w||l.canvasy<u.y||l.canvasy>u.y+u.h){continue}var w=l.annotation;var n=6;if(w.hasOwnProperty("tickHeight")){n=w.tickHeight}var j=document.createElement("div");for(var A in x){if(x.hasOwnProperty(A)){j.style[A]=x[A]}}if(!w.hasOwnProperty("icon")){j.className="dygraphDefaultAnnotation"}if(w.hasOwnProperty("cssClass")){j.className+=" "+w.cssClass}var m=w.hasOwnProperty("width")?w.width:16;var k=w.hasOwnProperty("height")?w.height:16;if(w.hasOwnProperty("icon")){var z=document.createElement("img");z.src=w.icon;z.width=m;z.height=k;j.appendChild(z)}else{if(l.annotation.hasOwnProperty("shortText")){j.appendChild(document.createTextNode(l.annotation.shortText))}}var c=l.canvasx-m/2;j.style.left=c+"px";var f=0;if(w.attachAtBottom){var d=(u.y+u.h-k-n);if(q[c]){d-=q[c]}else{q[c]=0}q[c]+=(n+k);f=d}else{f=l.canvasy-k-n}j.style.top=f+"px";j.style.width=m+"px";j.style.height=k+"px";j.title=l.annotation.text;j.style.color=t.colorsMap_[l.name];j.style.borderColor=t.colorsMap_[l.name];w.div=j;t.addAndTrackEvent(j,"click",b("clickHandler","annotationClickHandler",l,this));t.addAndTrackEvent(j,"mouseover",b("mouseOverHandler","annotationMouseOverHandler",l,this));t.addAndTrackEvent(j,"mouseout",b("mouseOutHandler","annotationMouseOutHandler",l,this));t.addAndTrackEvent(j,"dblclick",b("dblClickHandler","annotationDblClickHandler",l,this));h.appendChild(j);this.annotations_.push(j);var o=v.drawingContext;o.save();o.strokeStyle=t.colorsMap_[l.name];o.beginPath();if(!w.attachAtBottom){o.moveTo(l.canvasx,l.canvasy);o.lineTo(l.canvasx,l.canvasy-2-n)}else{var d=f+k;o.moveTo(l.canvasx,d);o.lineTo(l.canvasx,d+n)}o.closePath();o.stroke();o.restore()}};a.prototype.destroy=function(){this.detachLabels()};return a})();Dygraph.Plugins.Axes=(function(){var a=function(){this.xlabels_=[];this.ylabels_=[]};a.prototype.toString=function(){return"Axes Plugin"};a.prototype.activate=function(b){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}};a.prototype.layout=function(f){var d=f.dygraph;if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceLeft(b)}if(d.getOption("drawXAxis")){var c;if(d.getOption("xAxisHeight")){c=d.getOption("xAxisHeight")}else{c=d.getOptionForAxis("axisLabelFontSize","x")+2*d.getOption("axisTickSize")}f.reserveSpaceBottom(c)}if(d.numAxes()==2){if(d.getOption("drawYAxis")){var b=d.getOption("yAxisLabelWidth")+2*d.getOption("axisTickSize");f.reserveSpaceRight(b)}}else{if(d.numAxes()>2){d.error("Only two y-axes are supported at this time. (Trying to use "+d.numAxes()+")")}}};a.prototype.detachLabels=function(){function b(d){for(var c=0;c<d.length;c++){var e=d[c];if(e.parentNode){e.parentNode.removeChild(e)}}}b(this.xlabels_);b(this.ylabels_);this.xlabels_=[];this.ylabels_=[]};a.prototype.clearChart=function(b){this.detachLabels()};a.prototype.willDrawChart=function(H){var F=H.dygraph;if(!F.getOption("drawXAxis")&&!F.getOption("drawYAxis")){return}function B(e){return Math.round(e)+0.5}function A(e){return Math.round(e)-0.5}var j=H.drawingContext;var v=H.canvas.parentNode;var J=H.canvas.width;var d=H.canvas.height;var s,u,t,E,D;var C=function(e){return{position:"absolute",fontSize:F.getOptionForAxis("axisLabelFontSize",e)+"px",zIndex:10,color:F.getOptionForAxis("axisLabelColor",e),width:F.getOption("axisLabelWidth")+"px",lineHeight:"normal",overflow:"hidden"}};var p={x:C("x"),y:C("y"),y2:C("y2")};var m=function(g,x,y){var K=document.createElement("div");var e=p[y=="y2"?"y2":x];for(var r in e){if(e.hasOwnProperty(r)){K.style[r]=e[r]}}var i=document.createElement("div");i.className="dygraph-axis-label dygraph-axis-label-"+x+(y?" dygraph-axis-label-"+y:"");i.innerHTML=g;K.appendChild(i);return K};j.save();var I=F.layout_;var G=H.dygraph.plotter_.area;if(F.getOption("drawYAxis")){if(I.yticks&&I.yticks.length>0){var h=F.numAxes();for(D=0;D<I.yticks.length;D++){E=I.yticks[D];if(typeof(E)=="function"){return}u=G.x;var o=1;var f="y1";if(E[0]==1){u=G.x+G.w;o=-1;f="y2"}var k=F.getOptionForAxis("axisLabelFontSize",f);t=G.y+E[1]*G.h;s=m(E[2],"y",h==2?f:null);var z=(t-k/2);if(z<0){z=0}if(z+k+3>d){s.style.bottom="0px"}else{s.style.top=z+"px"}if(E[0]===0){s.style.left=(G.x-F.getOption("yAxisLabelWidth")-F.getOption("axisTickSize"))+"px";s.style.textAlign="right"}else{if(E[0]==1){s.style.left=(G.x+G.w+F.getOption("axisTickSize"))+"px";s.style.textAlign="left"}}s.style.width=F.getOption("yAxisLabelWidth")+"px";v.appendChild(s);this.ylabels_.push(s)}var n=this.ylabels_[0];var k=F.getOptionForAxis("axisLabelFontSize","y");var q=parseInt(n.style.top,10)+k;if(q>d-k){n.style.top=(parseInt(n.style.top,10)-k/2)+"px"}}var c;if(F.getOption("drawAxesAtZero")){var w=F.toPercentXCoord(0);if(w>1||w<0||isNaN(w)){w=0}c=B(G.x+w*G.w)}else{c=B(G.x)}j.strokeStyle=F.getOptionForAxis("axisLineColor","y");j.lineWidth=F.getOptionForAxis("axisLineWidth","y");j.beginPath();j.moveTo(c,A(G.y));j.lineTo(c,A(G.y+G.h));j.closePath();j.stroke();if(F.numAxes()==2){j.strokeStyle=F.getOptionForAxis("axisLineColor","y2");j.lineWidth=F.getOptionForAxis("axisLineWidth","y2");j.beginPath();j.moveTo(A(G.x+G.w),A(G.y));j.lineTo(A(G.x+G.w),A(G.y+G.h));j.closePath();j.stroke()}}if(F.getOption("drawXAxis")){if(I.xticks){for(D=0;D<I.xticks.length;D++){E=I.xticks[D];u=G.x+E[0]*G.w;t=G.y+G.h;s=m(E[1],"x");s.style.textAlign="center";s.style.top=(t+F.getOption("axisTickSize"))+"px";var l=(u-F.getOption("axisLabelWidth")/2);if(l+F.getOption("axisLabelWidth")>J){l=J-F.getOption("xAxisLabelWidth");s.style.textAlign="right"}if(l<0){l=0;s.style.textAlign="left"}s.style.left=l+"px";s.style.width=F.getOption("xAxisLabelWidth")+"px";v.appendChild(s);this.xlabels_.push(s)}}j.strokeStyle=F.getOptionForAxis("axisLineColor","x");j.lineWidth=F.getOptionForAxis("axisLineWidth","x");j.beginPath();var b;if(F.getOption("drawAxesAtZero")){var w=F.toPercentYCoord(0,0);if(w>1||w<0){w=1}b=A(G.y+w*G.h)}else{b=A(G.y+G.h)}j.moveTo(B(G.x),b);j.lineTo(B(G.x+G.w),b);j.closePath();j.stroke()}j.restore()};return a})();Dygraph.Plugins.ChartLabels=(function(){var c=function(){this.title_div_=null;this.xlabel_div_=null;this.ylabel_div_=null;this.y2label_div_=null};c.prototype.toString=function(){return"ChartLabels Plugin"};c.prototype.activate=function(d){return{layout:this.layout,didDrawChart:this.didDrawChart}};var b=function(d){var e=document.createElement("div");e.style.position="absolute";e.style.left=d.x+"px";e.style.top=d.y+"px";e.style.width=d.w+"px";e.style.height=d.h+"px";return e};c.prototype.detachLabels_=function(){var e=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_];for(var d=0;d<e.length;d++){var f=e[d];if(!f){continue}if(f.parentNode){f.parentNode.removeChild(f)}}this.title_div_=null;this.xlabel_div_=null;this.ylabel_div_=null;this.y2label_div_=null};var a=function(l,i,f,h,j){var d=document.createElement("div");d.style.position="absolute";if(f==1){d.style.left="0px"}else{d.style.left=i.x+"px"}d.style.top=i.y+"px";d.style.width=i.w+"px";d.style.height=i.h+"px";d.style.fontSize=(l.getOption("yLabelWidth")-2)+"px";var m=document.createElement("div");m.style.position="absolute";m.style.width=i.h+"px";m.style.height=i.w+"px";m.style.top=(i.h/2-i.w/2)+"px";m.style.left=(i.w/2-i.h/2)+"px";m.style.textAlign="center";var e="rotate("+(f==1?"-":"")+"90deg)";m.style.transform=e;m.style.WebkitTransform=e;m.style.MozTransform=e;m.style.OTransform=e;m.style.msTransform=e;if(typeof(document.documentMode)!=="undefined"&&document.documentMode<9){m.style.filter="progid:DXImageTransform.Microsoft.BasicImage(rotation="+(f==1?"3":"1")+")";m.style.left="0px";m.style.top="0px"}var k=document.createElement("div");k.className=h;k.innerHTML=j;m.appendChild(k);d.appendChild(m);return d};c.prototype.layout=function(k){this.detachLabels_();var i=k.dygraph;var m=k.chart_div;if(i.getOption("title")){var d=k.reserveSpaceTop(i.getOption("titleHeight"));this.title_div_=b(d);this.title_div_.style.textAlign="center";this.title_div_.style.fontSize=(i.getOption("titleHeight")-8)+"px";this.title_div_.style.fontWeight="bold";this.title_div_.style.zIndex=10;var f=document.createElement("div");f.className="dygraph-label dygraph-title";f.innerHTML=i.getOption("title");this.title_div_.appendChild(f);m.appendChild(this.title_div_)}if(i.getOption("xlabel")){var j=k.reserveSpaceBottom(i.getOption("xLabelHeight"));this.xlabel_div_=b(j);this.xlabel_div_.style.textAlign="center";this.xlabel_div_.style.fontSize=(i.getOption("xLabelHeight")-2)+"px";var f=document.createElement("div");f.className="dygraph-label dygraph-xlabel";f.innerHTML=i.getOption("xlabel");this.xlabel_div_.appendChild(f);m.appendChild(this.xlabel_div_)}if(i.getOption("ylabel")){var h=k.reserveSpaceLeft(0);this.ylabel_div_=a(i,h,1,"dygraph-label dygraph-ylabel",i.getOption("ylabel"));m.appendChild(this.ylabel_div_)}if(i.getOption("y2label")&&i.numAxes()==2){var l=k.reserveSpaceRight(0);this.y2label_div_=a(i,l,2,"dygraph-label dygraph-y2label",i.getOption("y2label"));m.appendChild(this.y2label_div_)}};c.prototype.didDrawChart=function(f){var d=f.dygraph;if(this.title_div_){this.title_div_.children[0].innerHTML=d.getOption("title")}if(this.xlabel_div_){this.xlabel_div_.children[0].innerHTML=d.getOption("xlabel")}if(this.ylabel_div_){this.ylabel_div_.children[0].children[0].innerHTML=d.getOption("ylabel")}if(this.y2label_div_){this.y2label_div_.children[0].children[0].innerHTML=d.getOption("y2label")}};c.prototype.clearChart=function(){};c.prototype.destroy=function(){this.detachLabels_()};return c})();Dygraph.Plugins.Grid=(function(){var a=function(){};a.prototype.toString=function(){return"Gridline Plugin"};a.prototype.activate=function(b){return{willDrawChart:this.willDrawChart}};a.prototype.willDrawChart=function(s){var q=s.dygraph;var l=s.drawingContext;var t=q.layout_;var r=s.dygraph.plotter_.area;function k(e){return Math.round(e)+0.5}function j(e){return Math.round(e)-0.5}var h,f,p,u;if(q.getOption("drawYGrid")){var o=["y","y2"];var m=[],v=[],b=[],n=[],d=[];for(var p=0;p<o.length;p++){b[p]=q.getOptionForAxis("drawGrid",o[p]);if(b[p]){m[p]=q.getOptionForAxis("gridLineColor",o[p]);v[p]=q.getOptionForAxis("gridLineWidth",o[p]);d[p]=q.getOptionForAxis("gridLinePattern",o[p]);n[p]=d[p]&&(d[p].length>=2)}}u=t.yticks;l.save();for(p=0;p<u.length;p++){var c=u[p][0];if(b[c]){if(n[c]){l.installPattern(d[c])}l.strokeStyle=m[c];l.lineWidth=v[c];h=k(r.x);f=j(r.y+u[p][1]*r.h);l.beginPath();l.moveTo(h,f);l.lineTo(h+r.w,f);l.closePath();l.stroke();if(n[c]){l.uninstallPattern()}}}l.restore()}if(q.getOption("drawXGrid")&&q.getOptionForAxis("drawGrid","x")){u=t.xticks;l.save();var d=q.getOptionForAxis("gridLinePattern","x");var n=d&&(d.length>=2);if(n){l.installPattern(d)}l.strokeStyle=q.getOptionForAxis("gridLineColor","x");l.lineWidth=q.getOptionForAxis("gridLineWidth","x");for(p=0;p<u.length;p++){h=k(r.x+u[p][0]*r.w);f=j(r.y+r.h);l.beginPath();l.moveTo(h,f);l.lineTo(h,r.y);l.closePath();l.stroke()}if(n){l.uninstallPattern()}l.restore()}};a.prototype.destroy=function(){};return a})();Dygraph.Plugins.Legend=(function(){var c=function(){this.legend_div_=null;this.is_generated_div_=false};c.prototype.toString=function(){return"Legend Plugin"};var a,d;c.prototype.activate=function(j){var m;var f=j.getOption("labelsDivWidth");var l=j.getOption("labelsDiv");if(l&&null!==l){if(typeof(l)=="string"||l instanceof String){m=document.getElementById(l)}else{m=l}}else{var i={position:"absolute",fontSize:"14px",zIndex:10,width:f+"px",top:"0px",left:(j.size().width-f-2)+"px",background:"white",lineHeight:"normal",textAlign:"left",overflow:"hidden"};Dygraph.update(i,j.getOption("labelsDivStyles"));m=document.createElement("div");m.className="dygraph-legend";for(var h in i){if(!i.hasOwnProperty(h)){continue}try{m.style[h]=i[h]}catch(k){this.warn("You are using unsupported css properties for your browser in labelsDivStyles")}}j.graphDiv.appendChild(m);this.is_generated_div_=true}this.legend_div_=m;this.one_em_width_=10;return{select:this.select,deselect:this.deselect,predraw:this.predraw,didDrawChart:this.didDrawChart}};var b=function(g){var f=document.createElement("span");f.setAttribute("style","margin: 0; padding: 0 0 0 1em; border: 0;");g.appendChild(f);var e=f.offsetWidth;g.removeChild(f);return e};c.prototype.select=function(i){var h=i.selectedX;var g=i.selectedPoints;var f=a(i.dygraph,h,g,this.one_em_width_);this.legend_div_.innerHTML=f};c.prototype.deselect=function(h){var f=b(this.legend_div_);this.one_em_width_=f;var g=a(h.dygraph,undefined,undefined,f);this.legend_div_.innerHTML=g};c.prototype.didDrawChart=function(f){this.deselect(f)};c.prototype.predraw=function(h){if(!this.is_generated_div_){return}h.dygraph.graphDiv.appendChild(this.legend_div_);var g=h.dygraph.plotter_.area;var f=h.dygraph.getOption("labelsDivWidth");this.legend_div_.style.left=g.x+g.w-f-1+"px";this.legend_div_.style.top=g.y+"px";this.legend_div_.style.width=f+"px"};c.prototype.destroy=function(){this.legend_div_=null};a=function(w,p,l,f){if(w.getOption("showLabelsOnHighlight")!==true){return""}var r,C,u,s,m;var z=w.getLabels();if(typeof(p)==="undefined"){if(w.getOption("legend")!="always"){return""}C=w.getOption("labelsSeparateLines");r="";for(u=1;u<z.length;u++){var q=w.getPropertiesForSeries(z[u]);if(!q.visible){continue}if(r!==""){r+=(C?"<br/>":" ")}m=w.getOption("strokePattern",z[u]);s=d(m,q.color,f);r+="<span style='font-weight: bold; color: "+q.color+";'>"+s+" "+z[u]+"</span>"}return r}var A=w.optionsViewForAxis_("x");var o=A("valueFormatter");r=o(p,A,z[0],w);if(r!==""){r+=":"}var v=[];var j=w.numAxes();for(u=0;u<j;u++){v[u]=w.optionsViewForAxis_("y"+(u?1+u:""))}var k=w.getOption("labelsShowZeroValues");C=w.getOption("labelsSeparateLines");var B=w.getHighlightSeries();for(u=0;u<l.length;u++){var t=l[u];if(t.yval===0&&!k){continue}if(!Dygraph.isOK(t.canvasy)){continue}if(C){r+="<br/>"}var q=w.getPropertiesForSeries(t.name);var n=v[q.axis-1];var y=n("valueFormatter");var e=y(t.yval,n,t.name,w);var h=(t.name==B)?" class='highlight'":"";r+="<span"+h+"> <b><span style='color: "+q.color+";'>"+t.name+"</span></b>: "+e+"</span>"}return r};d=function(s,h,r){var e=(/MSIE/.test(navigator.userAgent)&&!window.opera);if(e){return"—"}if(!s||s.length<=1){return'<div style="display: inline-block; position: relative; bottom: .5ex; padding-left: 1em; height: 1px; border-bottom: 2px solid '+h+';"></div>'}var l,k,f,o;var g=0,q=0;var p=[];var n;for(l=0;l<=s.length;l++){g+=s[l%s.length]}n=Math.floor(r/(g-s[0]));if(n>1){for(l=0;l<s.length;l++){p[l]=s[l]/r}q=p.length}else{n=1;for(l=0;l<s.length;l++){p[l]=s[l]/g}q=p.length+1}var m="";for(k=0;k<n;k++){for(l=0;l<q;l+=2){f=p[l%p.length];if(l<s.length){o=p[(l+1)%p.length]}else{o=0}m+='<div style="display: inline-block; position: relative; bottom: .5ex; margin-right: '+o+"em; padding-left: "+f+"em; height: 1px; border-bottom: 2px solid "+h+';"></div>'}}return m};return c})();Dygraph.Plugins.RangeSelector=(function(){var a=function(){this.isIE_=/MSIE/.test(navigator.userAgent)&&!window.opera;this.hasTouchInterface_=typeof(TouchEvent)!="undefined";this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion);this.interfaceCreated_=false};a.prototype.toString=function(){return"RangeSelector Plugin"};a.prototype.activate=function(b){this.dygraph_=b;this.isUsingExcanvas_=b.isUsingExcanvas_;if(this.getOption_("showRangeSelector")){this.createInterface_()}return{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}};a.prototype.destroy=function(){this.bgcanvas_=null;this.fgcanvas_=null;this.leftZoomHandle_=null;this.rightZoomHandle_=null;this.iePanOverlay_=null};a.prototype.getOption_=function(b){return this.dygraph_.getOption(b)};a.prototype.setDefaultOption_=function(b,c){return this.dygraph_.attrs_[b]=c};a.prototype.createInterface_=function(){this.createCanvases_();if(this.isUsingExcanvas_){this.createIEPanOverlay_()}this.createZoomHandles_();this.initInteraction_();if(this.getOption_("animatedZooms")){this.dygraph_.warn("Animated zooms and range selector are not compatible; disabling animatedZooms.");this.dygraph_.updateOptions({animatedZooms:false},true)}this.interfaceCreated_=true;this.addToGraph_()};a.prototype.addToGraph_=function(){var b=this.graphDiv_=this.dygraph_.graphDiv;b.appendChild(this.bgcanvas_);b.appendChild(this.fgcanvas_);b.appendChild(this.leftZoomHandle_);b.appendChild(this.rightZoomHandle_)};a.prototype.removeFromGraph_=function(){var b=this.graphDiv_;b.removeChild(this.bgcanvas_);b.removeChild(this.fgcanvas_);b.removeChild(this.leftZoomHandle_);b.removeChild(this.rightZoomHandle_);this.graphDiv_=null};a.prototype.reserveSpace_=function(b){if(this.getOption_("showRangeSelector")){b.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)}};a.prototype.renderStaticLayer_=function(){if(!this.updateVisibility_()){return}this.resize_();this.drawStaticLayer_()};a.prototype.renderInteractiveLayer_=function(){if(!this.updateVisibility_()||this.isChangingRange_){return}this.placeZoomHandles_();this.drawInteractiveLayer_()};a.prototype.updateVisibility_=function(){var b=this.getOption_("showRangeSelector");if(b){if(!this.interfaceCreated_){this.createInterface_()}else{if(!this.graphDiv_||!this.graphDiv_.parentNode){this.addToGraph_()}}}else{if(this.graphDiv_){this.removeFromGraph_();var c=this.dygraph_;setTimeout(function(){c.width_=0;c.resize()},1)}}return b};a.prototype.resize_=function(){function d(e,f){e.style.top=f.y+"px";e.style.left=f.x+"px";e.width=f.w;e.height=f.h;e.style.width=e.width+"px";e.style.height=e.height+"px"}var c=this.dygraph_.layout_.getPlotArea();var b=0;if(this.getOption_("drawXAxis")){b=this.getOption_("xAxisHeight")||(this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize"))}this.canvasRect_={x:c.x,y:c.y+c.h+b+4,w:c.w,h:this.getOption_("rangeSelectorHeight")};d(this.bgcanvas_,this.canvasRect_);d(this.fgcanvas_,this.canvasRect_)};a.prototype.createCanvases_=function(){this.bgcanvas_=Dygraph.createCanvas();this.bgcanvas_.className="dygraph-rangesel-bgcanvas";this.bgcanvas_.style.position="absolute";this.bgcanvas_.style.zIndex=9;this.bgcanvas_ctx_=Dygraph.getContext(this.bgcanvas_);this.fgcanvas_=Dygraph.createCanvas();this.fgcanvas_.className="dygraph-rangesel-fgcanvas";this.fgcanvas_.style.position="absolute";this.fgcanvas_.style.zIndex=9;this.fgcanvas_.style.cursor="default";this.fgcanvas_ctx_=Dygraph.getContext(this.fgcanvas_)};a.prototype.createIEPanOverlay_=function(){this.iePanOverlay_=document.createElement("div");this.iePanOverlay_.style.position="absolute";this.iePanOverlay_.style.backgroundColor="white";this.iePanOverlay_.style.filter="alpha(opacity=0)";this.iePanOverlay_.style.display="none";this.iePanOverlay_.style.cursor="move";this.fgcanvas_.appendChild(this.iePanOverlay_)};a.prototype.createZoomHandles_=function(){var b=new Image();b.className="dygraph-rangesel-zoomhandle";b.style.position="absolute";b.style.zIndex=10;b.style.visibility="hidden";b.style.cursor="col-resize";if(/MSIE 7/.test(navigator.userAgent)){b.width=7;b.height=14;b.style.backgroundColor="white";b.style.border="1px solid #333333"}else{b.width=9;b.height=16;b.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII="}if(this.isMobileDevice_){b.width*=2;b.height*=2}this.leftZoomHandle_=b;this.rightZoomHandle_=b.cloneNode(false)};a.prototype.initInteraction_=function(){var o=this;var i=this.isIE_?document:window;var u=0;var v=null;var s=false;var d=false;var g=!this.isMobileDevice_&&!this.isUsingExcanvas_;var k=new Dygraph.IFrameTarp();var p,f,r,j,w,h,x,t,q,c,l;var e,n,m;p=function(C){var B=o.dygraph_.xAxisExtremes();var z=(B[1]-B[0])/o.canvasRect_.w;var A=B[0]+(C.leftHandlePos-o.canvasRect_.x)*z;var y=B[0]+(C.rightHandlePos-o.canvasRect_.x)*z;return[A,y]};f=function(y){Dygraph.cancelEvent(y);s=true;u=y.clientX;v=y.target?y.target:y.srcElement;if(y.type==="mousedown"||y.type==="dragstart"){Dygraph.addEvent(i,"mousemove",r);Dygraph.addEvent(i,"mouseup",j)}o.fgcanvas_.style.cursor="col-resize";k.cover();return true};r=function(C){if(!s){return false}Dygraph.cancelEvent(C);var z=C.clientX-u;if(Math.abs(z)<4){return true}u=C.clientX;var B=o.getZoomHandleStatus_();var y;if(v==o.leftZoomHandle_){y=B.leftHandlePos+z;y=Math.min(y,B.rightHandlePos-v.width-3);y=Math.max(y,o.canvasRect_.x)}else{y=B.rightHandlePos+z;y=Math.min(y,o.canvasRect_.x+o.canvasRect_.w);y=Math.max(y,B.leftHandlePos+v.width+3)}var A=v.width/2;v.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){w()}return true};j=function(y){if(!s){return false}s=false;k.uncover();Dygraph.removeEvent(i,"mousemove",r);Dygraph.removeEvent(i,"mouseup",j);o.fgcanvas_.style.cursor="default";if(!g){w()}return true};w=function(){try{var z=o.getZoomHandleStatus_();o.isChangingRange_=true;if(!z.isZoomed){o.dygraph_.resetZoom()}else{var y=p(z);o.dygraph_.doZoomXDates_(y[0],y[1])}}finally{o.isChangingRange_=false}};h=function(A){if(o.isUsingExcanvas_){return A.srcElement==o.iePanOverlay_}else{var z=o.leftZoomHandle_.getBoundingClientRect();var y=z.left+z.width/2;z=o.rightZoomHandle_.getBoundingClientRect();var B=z.left+z.width/2;return(A.clientX>y&&A.clientX<B)}};x=function(y){if(!d&&h(y)&&o.getZoomHandleStatus_().isZoomed){Dygraph.cancelEvent(y);d=true;u=y.clientX;if(y.type==="mousedown"){Dygraph.addEvent(i,"mousemove",t);Dygraph.addEvent(i,"mouseup",q)}return true}return false};t=function(C){if(!d){return false}Dygraph.cancelEvent(C);var z=C.clientX-u;if(Math.abs(z)<4){return true}u=C.clientX;var B=o.getZoomHandleStatus_();var E=B.leftHandlePos;var y=B.rightHandlePos;var D=y-E;if(E+z<=o.canvasRect_.x){E=o.canvasRect_.x;y=E+D}else{if(y+z>=o.canvasRect_.x+o.canvasRect_.w){y=o.canvasRect_.x+o.canvasRect_.w;E=y-D}else{E+=z;y+=z}}var A=o.leftZoomHandle_.width/2;o.leftZoomHandle_.style.left=(E-A)+"px";o.rightZoomHandle_.style.left=(y-A)+"px";o.drawInteractiveLayer_();if(g){c()}return true};q=function(y){if(!d){return false}d=false;Dygraph.removeEvent(i,"mousemove",t);Dygraph.removeEvent(i,"mouseup",q);if(!g){c()}return true};c=function(){try{o.isChangingRange_=true;o.dygraph_.dateWindow_=p(o.getZoomHandleStatus_());o.dygraph_.drawGraph_(false)}finally{o.isChangingRange_=false}};l=function(y){if(s||d){return}var z=h(y)?"move":"default";if(z!=o.fgcanvas_.style.cursor){o.fgcanvas_.style.cursor=z}};e=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(f(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(r(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{j(y)}}};n=function(y){if(y.type=="touchstart"&&y.targetTouches.length==1){if(x(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{if(y.type=="touchmove"&&y.targetTouches.length==1){if(t(y.targetTouches[0])){Dygraph.cancelEvent(y)}}else{q(y)}}};m=function(B,A){var z=["touchstart","touchend","touchmove","touchcancel"];for(var y=0;y<z.length;y++){o.dygraph_.addAndTrackEvent(B,z[y],A)}};this.setDefaultOption_("interactionModel",Dygraph.Interaction.dragIsPanInteractionModel);this.setDefaultOption_("panEdgeFraction",0.0001);var b=window.opera?"mousedown":"dragstart";this.dygraph_.addAndTrackEvent(this.leftZoomHandle_,b,f);this.dygraph_.addAndTrackEvent(this.rightZoomHandle_,b,f);if(this.isUsingExcanvas_){this.dygraph_.addAndTrackEvent(this.iePanOverlay_,"mousedown",x)}else{this.dygraph_.addAndTrackEvent(this.fgcanvas_,"mousedown",x);this.dygraph_.addAndTrackEvent(this.fgcanvas_,"mousemove",l)}if(this.hasTouchInterface_){m(this.leftZoomHandle_,e);m(this.rightZoomHandle_,e);m(this.fgcanvas_,n)}};a.prototype.drawStaticLayer_=function(){var b=this.bgcanvas_ctx_;b.clearRect(0,0,this.canvasRect_.w,this.canvasRect_.h);try{this.drawMiniPlot_()}catch(c){Dygraph.warn(c)}var d=0.5;this.bgcanvas_ctx_.lineWidth=1;b.strokeStyle="gray";b.beginPath();b.moveTo(d,d);b.lineTo(d,this.canvasRect_.h-d);b.lineTo(this.canvasRect_.w-d,this.canvasRect_.h-d);b.lineTo(this.canvasRect_.w-d,d);b.stroke()};a.prototype.drawMiniPlot_=function(){var f=this.getOption_("rangeSelectorPlotFillColor");var r=this.getOption_("rangeSelectorPlotStrokeColor");if(!f&&!r){return}var j=this.getOption_("stepPlot");var v=this.computeCombinedSeriesAndLimits_();var q=v.yMax-v.yMin;var p=this.bgcanvas_ctx_;var n=0.5;var e=this.dygraph_.xAxisExtremes();var o=Math.max(e[1]-e[0],1e-30);var g=(this.canvasRect_.w-n)/o;var u=(this.canvasRect_.h-n)/q;var t=this.canvasRect_.w-n;var b=this.canvasRect_.h-n;var d=null,c=null;p.beginPath();p.moveTo(n,b);for(var s=0;s<v.data.length;s++){var h=v.data[s];var l=((h[0]!==null)?((h[0]-e[0])*g):NaN);var k=((h[1]!==null)?(b-(h[1]-v.yMin)*u):NaN);if(isFinite(l)&&isFinite(k)){if(d===null){p.lineTo(l,b)}else{if(j){p.lineTo(l,c)}}p.lineTo(l,k);d=l;c=k}else{if(d!==null){if(j){p.lineTo(l,c);p.lineTo(l,b)}else{p.lineTo(d,b)}}d=c=null}}p.lineTo(t,b);p.closePath();if(f){var m=this.bgcanvas_ctx_.createLinearGradient(0,0,0,b);m.addColorStop(0,"white");m.addColorStop(1,f);this.bgcanvas_ctx_.fillStyle=m;p.fill()}if(r){this.bgcanvas_ctx_.strokeStyle=r;this.bgcanvas_ctx_.lineWidth=1.5;p.stroke()}};a.prototype.computeCombinedSeriesAndLimits_=function(){var v=this.dygraph_.rawData_;var u=this.getOption_("logscale");var q=[];var d;var h;var m;var t,s,r;var e,g;for(t=0;t<v.length;t++){if(v[t].length>1&&v[t][1]!==null){m=typeof v[t][1]!="number";if(m){d=[];h=[];for(r=0;r<v[t][1].length;r++){d.push(0);h.push(0)}}break}}for(t=0;t<v.length;t++){var l=v[t];e=l[0];if(m){for(r=0;r<d.length;r++){d[r]=h[r]=0}}else{d=h=0}for(s=1;s<l.length;s++){if(this.dygraph_.visibility()[s-1]){var n;if(m){for(r=0;r<d.length;r++){n=l[s][r];if(n===null||isNaN(n)){continue}d[r]+=n;h[r]++}}else{n=l[s];if(n===null||isNaN(n)){continue}d+=n;h++}}}if(m){for(r=0;r<d.length;r++){d[r]/=h[r]}g=d.slice(0)}else{g=d/h}q.push([e,g])}q=this.dygraph_.rollingAverage(q,this.dygraph_.rollPeriod_);if(typeof q[0][1]!="number"){for(t=0;t<q.length;t++){g=q[t][1];q[t][1]=g[0]}}var b=Number.MAX_VALUE;var c=-Number.MAX_VALUE;for(t=0;t<q.length;t++){g=q[t][1];if(g!==null&&isFinite(g)&&(!u||g>0)){b=Math.min(b,g);c=Math.max(c,g)}}var o=0.25;if(u){c=Dygraph.log10(c);c+=c*o;b=Dygraph.log10(b);for(t=0;t<q.length;t++){q[t][1]=Dygraph.log10(q[t][1])}}else{var f;var p=c-b;if(p<=Number.MIN_VALUE){f=c*o}else{f=p*o}c+=f;b-=f}return{data:q,yMin:b,yMax:c}};a.prototype.placeZoomHandles_=function(){var h=this.dygraph_.xAxisExtremes();var b=this.dygraph_.xAxisRange();var c=h[1]-h[0];var j=Math.max(0,(b[0]-h[0])/c);var f=Math.max(0,(h[1]-b[1])/c);var i=this.canvasRect_.x+this.canvasRect_.w*j;var e=this.canvasRect_.x+this.canvasRect_.w*(1-f);var d=Math.max(this.canvasRect_.y,this.canvasRect_.y+(this.canvasRect_.h-this.leftZoomHandle_.height)/2);var g=this.leftZoomHandle_.width/2;this.leftZoomHandle_.style.left=(i-g)+"px";this.leftZoomHandle_.style.top=d+"px";this.rightZoomHandle_.style.left=(e-g)+"px";this.rightZoomHandle_.style.top=this.leftZoomHandle_.style.top;this.leftZoomHandle_.style.visibility="visible";this.rightZoomHandle_.style.visibility="visible"};a.prototype.drawInteractiveLayer_=function(){var c=this.fgcanvas_ctx_;c.clearRect(0,0,this.canvasRect_.w,this.canvasRect_.h);var f=1;var e=this.canvasRect_.w-f;var b=this.canvasRect_.h-f;var h=this.getZoomHandleStatus_();c.strokeStyle="black";if(!h.isZoomed){c.beginPath();c.moveTo(f,f);c.lineTo(f,b);c.lineTo(e,b);c.lineTo(e,f);c.stroke();if(this.iePanOverlay_){this.iePanOverlay_.style.display="none"}}else{var g=Math.max(f,h.leftHandlePos-this.canvasRect_.x);var d=Math.min(e,h.rightHandlePos-this.canvasRect_.x);c.fillStyle="rgba(240, 240, 240, 0.6)";c.fillRect(0,0,g,this.canvasRect_.h);c.fillRect(d,0,this.canvasRect_.w-d,this.canvasRect_.h);c.beginPath();c.moveTo(f,f);c.lineTo(g,f);c.lineTo(g,b);c.lineTo(d,b);c.lineTo(d,f);c.lineTo(e,f);c.stroke();if(this.isUsingExcanvas_){this.iePanOverlay_.style.width=(d-g)+"px";this.iePanOverlay_.style.left=g+"px";this.iePanOverlay_.style.height=b+"px";this.iePanOverlay_.style.display="inline"}}};a.prototype.getZoomHandleStatus_=function(){var c=this.leftZoomHandle_.width/2;var d=parseFloat(this.leftZoomHandle_.style.left)+c;var b=parseFloat(this.rightZoomHandle_.style.left)+c;return{leftHandlePos:d,rightHandlePos:b,isZoomed:(d-1>this.canvasRect_.x||b+1<this.canvasRect_.x+this.canvasRect_.w)}};return a})();Dygraph.PLUGINS.push(Dygraph.Plugins.Legend,Dygraph.Plugins.Axes,Dygraph.Plugins.RangeSelector,Dygraph.Plugins.ChartLabels,Dygraph.Plugins.Annotations,Dygraph.Plugins.Grid);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-dev.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-dev.js new file mode 100644 index 00000000..c06d23d5 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-dev.js @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +// A dygraph "auto-loader". + +// Check where this script was sourced from. If it was sourced from +// '../dygraph-dev.js', then we should source all the other scripts with the +// same relative path ('../dygraph.js', '../dygraph-canvas.js', ...) +(function() { + var src=document.getElementsByTagName('script'); + var script = src[src.length-1].getAttribute("src"); + + // This list needs to be kept in sync w/ the one in generate-combined.sh + // and the one in jsTestDriver.conf. + var source_files = [ + "strftime/strftime-min.js", + "rgbcolor/rgbcolor.js", + "stacktrace.js", + "dashed-canvas.js", + "dygraph-options.js", + "dygraph-layout.js", + "dygraph-canvas.js", + "dygraph.js", + "dygraph-utils.js", + "dygraph-gviz.js", + "dygraph-interaction-model.js", + "dygraph-tickers.js", + "dygraph-plugin-base.js", + "plugins/annotations.js", + "plugins/axes.js", + "plugins/chart-labels.js", + "plugins/grid.js", + "plugins/legend.js", + "plugins/range-selector.js", + "dygraph-plugin-install.js", + "dygraph-options-reference.js" // Shouldn't be included in generate-combined.sh + ]; + + for (var i = 0; i < source_files.length; i++) { + document.write('<script type="text/javascript" src="' + script.replace('dygraph-dev.js', source_files[i]) + '"></script>\n'); + } +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-externs.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-externs.js new file mode 100644 index 00000000..ed5de4c1 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-externs.js @@ -0,0 +1,93 @@ +/** + * @param {Object} dict + * @return {!Array.<string>} + */ +function printStackTrace(dict) {} + + +/** + * @constructor + */ +function G_vmlCanvasManager() {} + +/** + * @param {!HTMLCanvasElement} canvas + */ +G_vmlCanvasManager.initElement = function(canvas) {}; + +// For IE +/** + * @param {string} type + * @param {Object} fn + */ +Element.prototype.detachEvent = function(type, fn) {}; + + +/** + * @typedef {function( + * (number|Date), + * number, + * function(string):*, + * (Dygraph|undefined) + * ):string} + */ +var AxisLabelFormatter; + + +/** + * @typedef {function(number,function(string),Dygraph):string} + */ +var ValueFormatter; + + +/** + * @typedef {Array.<Array.<string|number|Array.<number>>>} + */ +var DygraphDataArray; + +/** + * @constructor + */ +function GVizDataTable() {} + +// TODO(danvk): move the Dygraph definitions out of here once I closure-ify dygraphs.js +/** + * @param {!HTMLDivElement|string} div + * @param {DygraphDataArray| + * GVizDataTable| + * string| + * function():(DygraphDataArray|GVizDataTable|string)} file + * @param {Object} attrs + * @constructor + */ +function Dygraph(div, file, attrs) {} + +/** + * @constructor + */ +function DygraphLayout() {} + +/** + * @type {Array} + */ +DygraphLayout.prototype.datasets; + +// TODO: DygraphOptions should not reach inside Dygraph private data like this. +/** @type {Object} */ +Dygraph.prototype.attrs_; +/** @type {Object} */ +Dygraph.prototype.user_attrs_; + +/** + * @type {DygraphLayout} + */ +Dygraph.prototype.layout_; + +/** @type {function(): string} */ +Dygraph.prototype.getHighlightSeries; + +/** @type {Array.<{elem:Element,type:string,fn:function(!Event):(boolean|undefined|null)}>} */ +Dygraph.prototype.registeredEvents_; + +/** @type {{axes: Object}} */ +Dygraph.DEFAULT_ATTRS; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-gviz.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-gviz.js new file mode 100644 index 00000000..988e0ace --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-gviz.js @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview A wrapper around the Dygraph class which implements the + * interface for a GViz (aka Google Visualization API) visualization. + * It is designed to be a drop-in replacement for Google's AnnotatedTimeline, + * so the documentation at + * http://code.google.com/apis/chart/interactive/docs/gallery/annotatedtimeline.html + * translates over directly. + * + * For a full demo, see: + * - http://dygraphs.com/tests/gviz.html + * - http://dygraphs.com/tests/annotation-gviz.html + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + +/** + * A wrapper around Dygraph that implements the gviz API. + * @param {!HTMLDivElement} container The DOM object the visualization should + * live in. + * @constructor + */ +Dygraph.GVizChart = function(container) { + this.container = container; +}; + +/** + * @param {GVizDataTable} data + * @param {Object.<*>} options + */ +Dygraph.GVizChart.prototype.draw = function(data, options) { + // Clear out any existing dygraph. + // TODO(danvk): would it make more sense to simply redraw using the current + // date_graph object? + this.container.innerHTML = ''; + if (typeof(this.date_graph) != 'undefined') { + this.date_graph.destroy(); + } + + this.date_graph = new Dygraph(this.container, data, options); +}; + +/** + * Google charts compatible setSelection + * Only row selection is supported, all points in the row will be highlighted + * @param {Array.<{row:number}>} selection_array array of the selected cells + * @public + */ +Dygraph.GVizChart.prototype.setSelection = function(selection_array) { + var row = false; + if (selection_array.length) { + row = selection_array[0].row; + } + this.date_graph.setSelection(row); +}; + +/** + * Google charts compatible getSelection implementation + * @return {Array.<{row:number,column:number}>} array of the selected cells + * @public + */ +Dygraph.GVizChart.prototype.getSelection = function() { + var selection = []; + + var row = this.date_graph.getSelection(); + + if (row < 0) return selection; + + var points = this.date_graph.layout_.points; + for (var setIdx = 0; setIdx < points.length; ++setIdx) { + selection.push({row: row, column: setIdx + 1}); + } + + return selection; +}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js new file mode 100644 index 00000000..2af345c7 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js @@ -0,0 +1,676 @@ +/** + * @license + * Copyright 2011 Robert Konigsberg (konigsberg@google.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview The default interaction model for Dygraphs. This is kept out + * of dygraph.js for better navigability. + * @author Robert Konigsberg (konigsberg@google.com) + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + +/** + * A collection of functions to facilitate build custom interaction models. + * @class + */ +Dygraph.Interaction = {}; + +/** + * Called in response to an interaction model operation that + * should start the default panning behavior. + * + * It's used in the default callback for "mousedown" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the startPan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.startPan = function(event, g, context) { + var i, axis; + context.isPanning = true; + var xRange = g.xAxisRange(); + context.dateRange = xRange[1] - xRange[0]; + context.initialLeftmostDate = xRange[0]; + context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1); + + if (g.attr_("panEdgeFraction")) { + var maxXPixelsToDraw = g.width_ * g.attr_("panEdgeFraction"); + var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes! + + var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw; + var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw; + + var boundedLeftDate = g.toDataXCoord(boundedLeftX); + var boundedRightDate = g.toDataXCoord(boundedRightX); + context.boundedDates = [boundedLeftDate, boundedRightDate]; + + var boundedValues = []; + var maxYPixelsToDraw = g.height_ * g.attr_("panEdgeFraction"); + + for (i = 0; i < g.axes_.length; i++) { + axis = g.axes_[i]; + var yExtremes = axis.extremeRange; + + var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw; + var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw; + + var boundedTopValue = g.toDataYCoord(boundedTopY, i); + var boundedBottomValue = g.toDataYCoord(boundedBottomY, i); + + boundedValues[i] = [boundedTopValue, boundedBottomValue]; + } + context.boundedValues = boundedValues; + } + + // Record the range of each y-axis at the start of the drag. + // If any axis has a valueRange or valueWindow, then we want a 2D pan. + // We can't store data directly in g.axes_, because it does not belong to us + // and could change out from under us during a pan (say if there's a data + // update). + context.is2DPan = false; + context.axes = []; + for (i = 0; i < g.axes_.length; i++) { + axis = g.axes_[i]; + var axis_data = {}; + var yRange = g.yAxisRange(i); + // TODO(konigsberg): These values should be in |context|. + // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale. + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + axis_data.initialTopValue = Dygraph.log10(yRange[1]); + axis_data.dragValueRange = Dygraph.log10(yRange[1]) - Dygraph.log10(yRange[0]); + } else { + axis_data.initialTopValue = yRange[1]; + axis_data.dragValueRange = yRange[1] - yRange[0]; + } + axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1); + context.axes.push(axis_data); + + // While calculating axes, set 2dpan. + if (axis.valueWindow || axis.valueRange) context.is2DPan = true; + } +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that pans the view. + * + * It's used in the default callback for "mousemove" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the movePan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.movePan = function(event, g, context) { + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + + var minDate = context.initialLeftmostDate - + (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel; + if (context.boundedDates) { + minDate = Math.max(minDate, context.boundedDates[0]); + } + var maxDate = minDate + context.dateRange; + if (context.boundedDates) { + if (maxDate > context.boundedDates[1]) { + // Adjust minDate, and recompute maxDate. + minDate = minDate - (maxDate - context.boundedDates[1]); + maxDate = minDate + context.dateRange; + } + } + + g.dateWindow_ = [minDate, maxDate]; + + // y-axis scaling is automatic unless this is a full 2D pan. + if (context.is2DPan) { + + var pixelsDragged = context.dragEndY - context.dragStartY; + + // Adjust each axis appropriately. + for (var i = 0; i < g.axes_.length; i++) { + var axis = g.axes_[i]; + var axis_data = context.axes[i]; + var unitsDragged = pixelsDragged * axis_data.unitsPerPixel; + + var boundedValue = context.boundedValues ? context.boundedValues[i] : null; + + // In log scale, maxValue and minValue are the logs of those values. + var maxValue = axis_data.initialTopValue + unitsDragged; + if (boundedValue) { + maxValue = Math.min(maxValue, boundedValue[1]); + } + var minValue = maxValue - axis_data.dragValueRange; + if (boundedValue) { + if (minValue < boundedValue[0]) { + // Adjust maxValue, and recompute minValue. + maxValue = maxValue - (minValue - boundedValue[0]); + minValue = maxValue - axis_data.dragValueRange; + } + } + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + axis.valueWindow = [ Math.pow(Dygraph.LOG_SCALE, minValue), + Math.pow(Dygraph.LOG_SCALE, maxValue) ]; + } else { + axis.valueWindow = [ minValue, maxValue ]; + } + } + } + + g.drawGraph_(false); +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that ends panning. + * + * It's used in the default callback for "mouseup" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the endPan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.endPan = function(event, g, context) { + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + + var regionWidth = Math.abs(context.dragEndX - context.dragStartX); + var regionHeight = Math.abs(context.dragEndY - context.dragStartY); + + if (regionWidth < 2 && regionHeight < 2 && + g.lastx_ !== undefined && g.lastx_ != -1) { + Dygraph.Interaction.treatMouseOpAsClick(g, event, context); + } + + // TODO(konigsberg): mouseup should just delete the + // context object, and mousedown should create a new one. + context.isPanning = false; + context.is2DPan = false; + context.initialLeftmostDate = null; + context.dateRange = null; + context.valueRange = null; + context.boundedDates = null; + context.boundedValues = null; + context.axes = null; +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that starts zooming. + * + * It's used in the default callback for "mousedown" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the startZoom call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.startZoom = function(event, g, context) { + context.isZooming = true; + context.zoomMoved = false; +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that defines zoom boundaries. + * + * It's used in the default callback for "mousemove" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the moveZoom call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.moveZoom = function(event, g, context) { + context.zoomMoved = true; + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + + var xDelta = Math.abs(context.dragStartX - context.dragEndX); + var yDelta = Math.abs(context.dragStartY - context.dragEndY); + + // drag direction threshold for y axis is twice as large as x axis + context.dragDirection = (xDelta < yDelta / 2) ? Dygraph.VERTICAL : Dygraph.HORIZONTAL; + + g.drawZoomRect_( + context.dragDirection, + context.dragStartX, + context.dragEndX, + context.dragStartY, + context.dragEndY, + context.prevDragDirection, + context.prevEndX, + context.prevEndY); + + context.prevEndX = context.dragEndX; + context.prevEndY = context.dragEndY; + context.prevDragDirection = context.dragDirection; +}; + +/** + * @param {Dygraph} g + * @param {Event} event + * @param {Object} context + */ +Dygraph.Interaction.treatMouseOpAsClick = function(g, event, context) { + var clickCallback = g.attr_('clickCallback'); + var pointClickCallback = g.attr_('pointClickCallback'); + + var selectedPoint = null; + + // Find out if the click occurs on a point. This only matters if there's a + // pointClickCallback. + if (pointClickCallback) { + var closestIdx = -1; + var closestDistance = Number.MAX_VALUE; + + // check if the click was on a particular point. + for (var i = 0; i < g.selPoints_.length; i++) { + var p = g.selPoints_[i]; + var distance = Math.pow(p.canvasx - context.dragEndX, 2) + + Math.pow(p.canvasy - context.dragEndY, 2); + if (!isNaN(distance) && + (closestIdx == -1 || distance < closestDistance)) { + closestDistance = distance; + closestIdx = i; + } + } + + // Allow any click within two pixels of the dot. + var radius = g.attr_('highlightCircleSize') + 2; + if (closestDistance <= radius * radius) { + selectedPoint = g.selPoints_[closestIdx]; + } + } + + if (selectedPoint) { + pointClickCallback(event, selectedPoint); + } + + // TODO(danvk): pass along more info about the points, e.g. 'x' + if (clickCallback) { + clickCallback(event, g.lastx_, g.selPoints_); + } +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that performs a zoom based on previously defined + * bounds.. + * + * It's used in the default callback for "mouseup" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the endZoom call. + * @param {Dygraph} g The dygraph on which to end the zoom. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.endZoom = function(event, g, context) { + context.isZooming = false; + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + var regionWidth = Math.abs(context.dragEndX - context.dragStartX); + var regionHeight = Math.abs(context.dragEndY - context.dragStartY); + + if (regionWidth < 2 && regionHeight < 2 && + g.lastx_ !== undefined && g.lastx_ != -1) { + Dygraph.Interaction.treatMouseOpAsClick(g, event, context); + } + + // The zoom rectangle is visibly clipped to the plot area, so its behavior + // should be as well. + // See http://code.google.com/p/dygraphs/issues/detail?id=280 + var plotArea = g.getArea(); + if (regionWidth >= 10 && context.dragDirection == Dygraph.HORIZONTAL) { + var left = Math.min(context.dragStartX, context.dragEndX), + right = Math.max(context.dragStartX, context.dragEndX); + left = Math.max(left, plotArea.x); + right = Math.min(right, plotArea.x + plotArea.w); + if (left < right) { + g.doZoomX_(left, right); + } + context.cancelNextDblclick = true; + } else if (regionHeight >= 10 && context.dragDirection == Dygraph.VERTICAL) { + var top = Math.min(context.dragStartY, context.dragEndY), + bottom = Math.max(context.dragStartY, context.dragEndY); + top = Math.max(top, plotArea.y); + bottom = Math.min(bottom, plotArea.y + plotArea.h); + if (top < bottom) { + g.doZoomY_(top, bottom); + } + context.cancelNextDblclick = true; + } else { + if (context.zoomMoved) g.clearZoomRect_(); + } + context.dragStartX = null; + context.dragStartY = null; +}; + +/** + * @private + */ +Dygraph.Interaction.startTouch = function(event, g, context) { + event.preventDefault(); // touch browsers are all nice. + if (event.touches.length > 1) { + // If the user ever puts two fingers down, it's not a double tap. + context.startTimeForDoubleTapMs = null; + } + + var touches = []; + for (var i = 0; i < event.touches.length; i++) { + var t = event.touches[i]; + // we dispense with 'dragGetX_' because all touchBrowsers support pageX + touches.push({ + pageX: t.pageX, + pageY: t.pageY, + dataX: g.toDataXCoord(t.pageX), + dataY: g.toDataYCoord(t.pageY) + // identifier: t.identifier + }); + } + context.initialTouches = touches; + + if (touches.length == 1) { + // This is just a swipe. + context.initialPinchCenter = touches[0]; + context.touchDirections = { x: true, y: true }; + } else if (touches.length >= 2) { + // It's become a pinch! + // In case there are 3+ touches, we ignore all but the "first" two. + + // only screen coordinates can be averaged (data coords could be log scale). + context.initialPinchCenter = { + pageX: 0.5 * (touches[0].pageX + touches[1].pageX), + pageY: 0.5 * (touches[0].pageY + touches[1].pageY), + + // TODO(danvk): remove + dataX: 0.5 * (touches[0].dataX + touches[1].dataX), + dataY: 0.5 * (touches[0].dataY + touches[1].dataY) + }; + + // Make pinches in a 45-degree swath around either axis 1-dimensional zooms. + var initialAngle = 180 / Math.PI * Math.atan2( + context.initialPinchCenter.pageY - touches[0].pageY, + touches[0].pageX - context.initialPinchCenter.pageX); + + // use symmetry to get it into the first quadrant. + initialAngle = Math.abs(initialAngle); + if (initialAngle > 90) initialAngle = 90 - initialAngle; + + context.touchDirections = { + x: (initialAngle < (90 - 45/2)), + y: (initialAngle > 45/2) + }; + } + + // save the full x & y ranges. + context.initialRange = { + x: g.xAxisRange(), + y: g.yAxisRange() + }; +}; + +/** + * @private + */ +Dygraph.Interaction.moveTouch = function(event, g, context) { + // If the tap moves, then it's definitely not part of a double-tap. + context.startTimeForDoubleTapMs = null; + + var i, touches = []; + for (i = 0; i < event.touches.length; i++) { + var t = event.touches[i]; + touches.push({ + pageX: t.pageX, + pageY: t.pageY + }); + } + var initialTouches = context.initialTouches; + + var c_now; + + // old and new centers. + var c_init = context.initialPinchCenter; + if (touches.length == 1) { + c_now = touches[0]; + } else { + c_now = { + pageX: 0.5 * (touches[0].pageX + touches[1].pageX), + pageY: 0.5 * (touches[0].pageY + touches[1].pageY) + }; + } + + // this is the "swipe" component + // we toss it out for now, but could use it in the future. + var swipe = { + pageX: c_now.pageX - c_init.pageX, + pageY: c_now.pageY - c_init.pageY + }; + var dataWidth = context.initialRange.x[1] - context.initialRange.x[0]; + var dataHeight = context.initialRange.y[0] - context.initialRange.y[1]; + swipe.dataX = (swipe.pageX / g.plotter_.area.w) * dataWidth; + swipe.dataY = (swipe.pageY / g.plotter_.area.h) * dataHeight; + var xScale, yScale; + + // The residual bits are usually split into scale & rotate bits, but we split + // them into x-scale and y-scale bits. + if (touches.length == 1) { + xScale = 1.0; + yScale = 1.0; + } else if (touches.length >= 2) { + var initHalfWidth = (initialTouches[1].pageX - c_init.pageX); + xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; + + var initHalfHeight = (initialTouches[1].pageY - c_init.pageY); + yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight; + } + + // Clip scaling to [1/8, 8] to prevent too much blowup. + xScale = Math.min(8, Math.max(0.125, xScale)); + yScale = Math.min(8, Math.max(0.125, yScale)); + + var didZoom = false; + if (context.touchDirections.x) { + g.dateWindow_ = [ + c_init.dataX - swipe.dataX + (context.initialRange.x[0] - c_init.dataX) / xScale, + c_init.dataX - swipe.dataX + (context.initialRange.x[1] - c_init.dataX) / xScale + ]; + didZoom = true; + } + + if (context.touchDirections.y) { + for (i = 0; i < 1 /*g.axes_.length*/; i++) { + var axis = g.axes_[i]; + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + // TODO(danvk): implement + } else { + axis.valueWindow = [ + c_init.dataY - swipe.dataY + (context.initialRange.y[0] - c_init.dataY) / yScale, + c_init.dataY - swipe.dataY + (context.initialRange.y[1] - c_init.dataY) / yScale + ]; + didZoom = true; + } + } + } + + g.drawGraph_(false); + + // We only call zoomCallback on zooms, not pans, to mirror desktop behavior. + if (didZoom && touches.length > 1 && g.attr_('zoomCallback')) { + var viewWindow = g.xAxisRange(); + g.attr_("zoomCallback")(viewWindow[0], viewWindow[1], g.yAxisRanges()); + } +}; + +/** + * @private + */ +Dygraph.Interaction.endTouch = function(event, g, context) { + if (event.touches.length !== 0) { + // this is effectively a "reset" + Dygraph.Interaction.startTouch(event, g, context); + } else if (event.changedTouches.length == 1) { + // Could be part of a "double tap" + // The heuristic here is that it's a double-tap if the two touchend events + // occur within 500ms and within a 50x50 pixel box. + var now = new Date().getTime(); + var t = event.changedTouches[0]; + if (context.startTimeForDoubleTapMs && + now - context.startTimeForDoubleTapMs < 500 && + context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && + context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) { + g.resetZoom(); + } else { + context.startTimeForDoubleTapMs = now; + context.doubleTapX = t.screenX; + context.doubleTapY = t.screenY; + } + } +}; + +/** + * Default interation model for dygraphs. You can refer to specific elements of + * this when constructing your own interaction model, e.g.: + * g.updateOptions( { + * interactionModel: { + * mousedown: Dygraph.defaultInteractionModel.mousedown + * } + * } ); + */ +Dygraph.Interaction.defaultModel = { + // Track the beginning of drag events + mousedown: function(event, g, context) { + // Right-click should not initiate a zoom. + if (event.button && event.button == 2) return; + + context.initializeMouseDown(event, g, context); + + if (event.altKey || event.shiftKey) { + Dygraph.startPan(event, g, context); + } else { + Dygraph.startZoom(event, g, context); + } + }, + + // Draw zoom rectangles when the mouse is down and the user moves around + mousemove: function(event, g, context) { + if (context.isZooming) { + Dygraph.moveZoom(event, g, context); + } else if (context.isPanning) { + Dygraph.movePan(event, g, context); + } + }, + + mouseup: function(event, g, context) { + if (context.isZooming) { + Dygraph.endZoom(event, g, context); + } else if (context.isPanning) { + Dygraph.endPan(event, g, context); + } + }, + + touchstart: function(event, g, context) { + Dygraph.Interaction.startTouch(event, g, context); + }, + touchmove: function(event, g, context) { + Dygraph.Interaction.moveTouch(event, g, context); + }, + touchend: function(event, g, context) { + Dygraph.Interaction.endTouch(event, g, context); + }, + + // Temporarily cancel the dragging event when the mouse leaves the graph + mouseout: function(event, g, context) { + if (context.isZooming) { + context.dragEndX = null; + context.dragEndY = null; + g.clearZoomRect_(); + } + }, + + // Disable zooming out if panning. + dblclick: function(event, g, context) { + if (context.cancelNextDblclick) { + context.cancelNextDblclick = false; + return; + } + if (event.altKey || event.shiftKey) { + return; + } + g.resetZoom(); + } +}; + +Dygraph.DEFAULT_ATTRS.interactionModel = Dygraph.Interaction.defaultModel; + +// old ways of accessing these methods/properties +Dygraph.defaultInteractionModel = Dygraph.Interaction.defaultModel; +Dygraph.endZoom = Dygraph.Interaction.endZoom; +Dygraph.moveZoom = Dygraph.Interaction.moveZoom; +Dygraph.startZoom = Dygraph.Interaction.startZoom; +Dygraph.endPan = Dygraph.Interaction.endPan; +Dygraph.movePan = Dygraph.Interaction.movePan; +Dygraph.startPan = Dygraph.Interaction.startPan; + +Dygraph.Interaction.nonInteractiveModel_ = { + mousedown: function(event, g, context) { + context.initializeMouseDown(event, g, context); + }, + mouseup: function(event, g, context) { + // TODO(danvk): this logic is repeated in Dygraph.Interaction.endZoom + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + var regionWidth = Math.abs(context.dragEndX - context.dragStartX); + var regionHeight = Math.abs(context.dragEndY - context.dragStartY); + + if (regionWidth < 2 && regionHeight < 2 && + g.lastx_ !== undefined && g.lastx_ != -1) { + Dygraph.Interaction.treatMouseOpAsClick(g, event, context); + } + } +}; + +// Default interaction model when using the range selector. +Dygraph.Interaction.dragIsPanInteractionModel = { + mousedown: function(event, g, context) { + context.initializeMouseDown(event, g, context); + Dygraph.startPan(event, g, context); + }, + mousemove: function(event, g, context) { + if (context.isPanning) { + Dygraph.movePan(event, g, context); + } + }, + mouseup: function(event, g, context) { + if (context.isPanning) { + Dygraph.endPan(event, g, context); + } + } +}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-layout.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-layout.js new file mode 100644 index 00000000..e766e8f5 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-layout.js @@ -0,0 +1,349 @@ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Based on PlotKitLayout, but modified to meet the needs of + * dygraphs. + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + +/** + * Creates a new DygraphLayout object. + * + * This class contains all the data to be charted. + * It uses data coordinates, but also records the chart range (in data + * coordinates) and hence is able to calculate percentage positions ('In this + * view, Point A lies 25% down the x-axis.') + * + * Two things that it does not do are: + * 1. Record pixel coordinates for anything. + * 2. (oddly) determine anything about the layout of chart elements. + * + * The naming is a vestige of Dygraph's original PlotKit roots. + * + * @constructor + */ +var DygraphLayout = function(dygraph) { + this.dygraph_ = dygraph; + /** + * Array of points for each series. + * + * [series index][row index in series] = |Point| structure, + * where series index refers to visible series only, and the + * point index is for the reduced set of points for the current + * zoom region (including one point just outside the window). + * All points in the same row index share the same X value. + * + * @type {Array.<Array.<Dygraph.PointType>>} + */ + this.points = []; + this.setNames = []; + this.annotations = []; + this.yAxes_ = null; + + // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and + // yticks are outputs. Clean this up. + this.xTicks_ = null; + this.yTicks_ = null; +}; + +DygraphLayout.prototype.attr_ = function(name) { + return this.dygraph_.attr_(name); +}; + +/** + * Add points for a single series. + * + * @param {string} setname Name of the series. + * @param {Array.<Dygraph.PointType>} set_xy Points for the series. + */ +DygraphLayout.prototype.addDataset = function(setname, set_xy) { + this.points.push(set_xy); + this.setNames.push(setname); +}; + +/** + * Returns the box which the chart should be drawn in. This is the canvas's + * box, less space needed for the axis and chart labels. + * + * @return {{x: number, y: number, w: number, h: number}} + */ +DygraphLayout.prototype.getPlotArea = function() { + return this.area_; +}; + +// Compute the box which the chart should be drawn in. This is the canvas's +// box, less space needed for axis, chart labels, and other plug-ins. +// NOTE: This should only be called by Dygraph.predraw_(). +DygraphLayout.prototype.computePlotArea = function() { + var area = { + // TODO(danvk): per-axis setting. + x: 0, + y: 0 + }; + + area.w = this.dygraph_.width_ - area.x - this.attr_('rightGap'); + area.h = this.dygraph_.height_; + + // Let plugins reserve space. + var e = { + chart_div: this.dygraph_.graphDiv, + reserveSpaceLeft: function(px) { + var r = { + x: area.x, + y: area.y, + w: px, + h: area.h + }; + area.x += px; + area.w -= px; + return r; + }, + reserveSpaceRight: function(px) { + var r = { + x: area.x + area.w - px, + y: area.y, + w: px, + h: area.h + }; + area.w -= px; + return r; + }, + reserveSpaceTop: function(px) { + var r = { + x: area.x, + y: area.y, + w: area.w, + h: px + }; + area.y += px; + area.h -= px; + return r; + }, + reserveSpaceBottom: function(px) { + var r = { + x: area.x, + y: area.y + area.h - px, + w: area.w, + h: px + }; + area.h -= px; + return r; + }, + chartRect: function() { + return {x:area.x, y:area.y, w:area.w, h:area.h}; + } + }; + this.dygraph_.cascadeEvents_('layout', e); + + this.area_ = area; +}; + +DygraphLayout.prototype.setAnnotations = function(ann) { + // The Dygraph object's annotations aren't parsed. We parse them here and + // save a copy. If there is no parser, then the user must be using raw format. + this.annotations = []; + var parse = this.attr_('xValueParser') || function(x) { return x; }; + for (var i = 0; i < ann.length; i++) { + var a = {}; + if (!ann[i].xval && ann[i].x === undefined) { + this.dygraph_.error("Annotations must have an 'x' property"); + return; + } + if (ann[i].icon && + !(ann[i].hasOwnProperty('width') && + ann[i].hasOwnProperty('height'))) { + this.dygraph_.error("Must set width and height when setting " + + "annotation.icon property"); + return; + } + Dygraph.update(a, ann[i]); + if (!a.xval) a.xval = parse(a.x); + this.annotations.push(a); + } +}; + +DygraphLayout.prototype.setXTicks = function(xTicks) { + this.xTicks_ = xTicks; +}; + +// TODO(danvk): add this to the Dygraph object's API or move it into Layout. +DygraphLayout.prototype.setYAxes = function (yAxes) { + this.yAxes_ = yAxes; +}; + +DygraphLayout.prototype.evaluate = function() { + this._evaluateLimits(); + this._evaluateLineCharts(); + this._evaluateLineTicks(); + this._evaluateAnnotations(); +}; + +DygraphLayout.prototype._evaluateLimits = function() { + var xlimits = this.dygraph_.xAxisRange(); + this.minxval = xlimits[0]; + this.maxxval = xlimits[1]; + var xrange = xlimits[1] - xlimits[0]; + this.xscale = (xrange !== 0 ? 1 / xrange : 1.0); + + for (var i = 0; i < this.yAxes_.length; i++) { + var axis = this.yAxes_[i]; + axis.minyval = axis.computedValueRange[0]; + axis.maxyval = axis.computedValueRange[1]; + axis.yrange = axis.maxyval - axis.minyval; + axis.yscale = (axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0); + + if (axis.g.attr_("logscale")) { + axis.ylogrange = Dygraph.log10(axis.maxyval) - Dygraph.log10(axis.minyval); + axis.ylogscale = (axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0); + if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) { + axis.g.error('axis ' + i + ' of graph at ' + axis.g + + ' can\'t be displayed in log scale for range [' + + axis.minyval + ' - ' + axis.maxyval + ']'); + } + } + } +}; + +DygraphLayout._calcYNormal = function(axis, value, logscale) { + if (logscale) { + return 1.0 - ((Dygraph.log10(value) - Dygraph.log10(axis.minyval)) * axis.ylogscale); + } else { + return 1.0 - ((value - axis.minyval) * axis.yscale); + } +}; + +DygraphLayout.prototype._evaluateLineCharts = function() { + var connectSeparated = this.attr_('connectSeparatedPoints'); + var isStacked = this.attr_("stackedGraph"); + var hasBars = this.attr_('errorBars') || this.attr_('customBars'); + + for (var setIdx = 0; setIdx < this.points.length; setIdx++) { + var points = this.points[setIdx]; + var setName = this.setNames[setIdx]; + var axis = this.dygraph_.axisPropertiesForSeries(setName); + // TODO (konigsberg): use optionsForAxis instead. + var logscale = this.dygraph_.attributes_.getForSeries("logscale", setName); + + for (var j = 0; j < points.length; j++) { + var point = points[j]; + + // Range from 0-1 where 0 represents left and 1 represents right. + point.x = (point.xval - this.minxval) * this.xscale; + // Range from 0-1 where 0 represents top and 1 represents bottom + var yval = point.yval; + if (isStacked) { + point.y_stacked = DygraphLayout._calcYNormal( + axis, point.yval_stacked, logscale); + if (yval !== null && !isNaN(yval)) { + yval = point.yval_stacked; + } + } + if (yval === null) { + yval = NaN; + if (!connectSeparated) { + point.yval = NaN; + } + } + point.y = DygraphLayout._calcYNormal(axis, yval, logscale); + + if (hasBars) { + point.y_top = DygraphLayout._calcYNormal( + axis, yval - point.yval_minus, logscale); + point.y_bottom = DygraphLayout._calcYNormal( + axis, yval + point.yval_plus, logscale); + } + } + } +}; + +/** + * Optimized replacement for parseFloat, which was way too slow when almost + * all values were type number, with few edge cases, none of which were strings. + */ +DygraphLayout.parseFloat_ = function(val) { + // parseFloat(null) is NaN + if (val === null) { + return NaN; + } + + // Assume it's a number or NaN. If it's something else, I'll be shocked. + return val; +}; + +DygraphLayout.prototype._evaluateLineTicks = function() { + var i, tick, label, pos; + this.xticks = []; + for (i = 0; i < this.xTicks_.length; i++) { + tick = this.xTicks_[i]; + label = tick.label; + pos = this.xscale * (tick.v - this.minxval); + if ((pos >= 0.0) && (pos <= 1.0)) { + this.xticks.push([pos, label]); + } + } + + this.yticks = []; + for (i = 0; i < this.yAxes_.length; i++ ) { + var axis = this.yAxes_[i]; + for (var j = 0; j < axis.ticks.length; j++) { + tick = axis.ticks[j]; + label = tick.label; + pos = this.dygraph_.toPercentYCoord(tick.v, i); + if ((pos >= 0.0) && (pos <= 1.0)) { + this.yticks.push([i, pos, label]); + } + } + } +}; + +DygraphLayout.prototype._evaluateAnnotations = function() { + // Add the annotations to the point to which they belong. + // Make a map from (setName, xval) to annotation for quick lookups. + var i; + var annotations = {}; + for (i = 0; i < this.annotations.length; i++) { + var a = this.annotations[i]; + annotations[a.xval + "," + a.series] = a; + } + + this.annotated_points = []; + + // Exit the function early if there are no annotations. + if (!this.annotations || !this.annotations.length) { + return; + } + + // TODO(antrob): loop through annotations not points. + for (var setIdx = 0; setIdx < this.points.length; setIdx++) { + var points = this.points[setIdx]; + for (i = 0; i < points.length; i++) { + var p = points[i]; + var k = p.xval + "," + p.name; + if (k in annotations) { + p.annotation = annotations[k]; + this.annotated_points.push(p); + } + } + } +}; + +/** + * Convenience function to remove all the data sets from a graph + */ +DygraphLayout.prototype.removeAllDatasets = function() { + delete this.points; + delete this.setNames; + delete this.setPointsLengths; + delete this.setPointsOffsets; + this.points = []; + this.setNames = []; + this.setPointsLengths = []; + this.setPointsOffsets = []; +}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-options-reference.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-options-reference.js new file mode 100644 index 00000000..64e738d4 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-options-reference.js @@ -0,0 +1,867 @@ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false */ + +// NOTE: in addition to parsing as JS, this snippet is expected to be valid +// JSON. This assumption cannot be checked in JS, but it will be checked when +// documentation is generated by the generate-documentation.py script. For the +// most part, this just means that you should always use double quotes. +Dygraph.OPTIONS_REFERENCE = // <JSON> +{ + "xValueParser": { + "default": "parseFloat() or Date.parse()*", + "labels": ["CSV parsing"], + "type": "function(str) -> number", + "description": "A function which parses x-values (i.e. the dependent series). Must return a number, even when the values are dates. In this case, millis since epoch are used. This is used primarily for parsing CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for details." + }, + "stackedGraph": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "If set, stack series on top of one another rather than drawing them independently. The first series specified in the input data will wind up on top of the chart and the last will be on bottom. NaN values are drawn as white areas without a line on top, see stackedGraphNaNFill for details." + }, + "stackedGraphNaNFill": { + "default": "all", + "labels": ["Data Line display"], + "type": "string", + "description": "Controls handling of NaN values inside a stacked graph. NaN values are interpolated/extended for stacking purposes, but the actual point value remains NaN in the legend display. Valid option values are \"all\" (interpolate internally, repeat leftmost and rightmost value as needed), \"inside\" (interpolate internally only, use zero outside leftmost and rightmost value), and \"none\" (treat NaN as zero everywhere)." + }, + "pointSize": { + "default": "1", + "labels": ["Data Line display"], + "type": "integer", + "description": "The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is \"isolated\", i.e. there is a missing point on either side of it. This also controls the size of those dots." + }, + "labelsDivStyles": { + "default": "null", + "labels": ["Legend"], + "type": "{}", + "description": "Additional styles to apply to the currently-highlighted points div. For example, { 'fontWeight': 'bold' } will make the labels bold. In general, it is better to use CSS to style the .dygraph-legend class than to use this property." + }, + "drawPoints": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Draw a small dot at each point, in addition to a line going through the point. This makes the individual data points easier to see, but can increase visual clutter in the chart. The small dot can be replaced with a custom rendering by supplying a <a href='#drawPointCallback'>drawPointCallback</a>." + }, + "drawGapEdgePoints": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities." + }, + "drawPointCallback": { + "default": "null", + "labels": ["Data Line display"], + "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)", + "parameters": [ + [ "g" , "the reference graph" ], + [ "seriesName" , "the name of the series" ], + [ "canvasContext" , "the canvas to draw on" ], + [ "cx" , "center x coordinate" ], + [ "cy" , "center y coordinate" ], + [ "color" , "series color" ], + [ "pointSize" , "the radius of the image." ], + [ "idx" , "the row-index of the point in the data."] + ], + "description": "Draw a custom item when drawPoints is enabled. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy). Also see <a href='#drawHighlightPointCallback'>drawHighlightPointCallback</a>" + }, + "height": { + "default": "320", + "labels": ["Overall display"], + "type": "integer", + "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." + }, + "zoomCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(minDate, maxDate, yRanges)", + "parameters": [ + [ "minDate" , "milliseconds since epoch" ], + [ "maxDate" , "milliseconds since epoch." ], + [ "yRanges" , "is an array of [bottom, top] pairs, one for each y-axis." ] + ], + "description": "A function to call when the zoom window is changed (either by zooming in or out)." + }, + "pointClickCallback": { + "snippet": "function(e, point){<br> alert(point);<br>}", + "default": "null", + "labels": ["Callbacks", "Interactive Elements"], + "type": "function(e, point)", + "parameters": [ + [ "e" , "the event object for the click" ], + [ "point" , "the point that was clicked See <a href='#point_properties'>Point properties</a> for details" ] + ], + "description": "A function to call when a data point is clicked. and the point that was clicked." + }, + "colors": { + "default": "(see description)", + "labels": ["Data Series Colors"], + "type": "array<string>", + "example": "['red', '#00FF00']", + "description": "List of colors for the data series. These can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\", etc. If not specified, equally-spaced points around a color wheel are used." + }, + "connectSeparatedPoints": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Usually, when Dygraphs encounters a missing value in a data series, it interprets this as a gap and draws it as such. If, instead, the missing values represents an x-value for which only a different series has data, then you'll want to connect the dots by setting this to true. To explicitly include a gap with this option set, use a value of NaN." + }, + "highlightCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(event, x, points, row, seriesName)", + "description": "When set, this callback gets called every time a new point is highlighted.", + "parameters": [ + ["event", "the JavaScript mousemove event"], + ["x", "the x-coordinate of the highlighted points"], + ["points", "an array of highlighted points: <code>[ {name: 'series', yval: y-value}, … ]</code>"], + ["row", "integer index of the highlighted row in the data table, starting from 0"], + ["seriesName", "name of the highlighted series, only present if highlightSeriesOpts is set."] + ] + }, + "drawHighlightPointCallback": { + "default": "null", + "labels": ["Data Line display"], + "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)", + "parameters": [ + [ "g" , "the reference graph" ], + [ "seriesName" , "the name of the series" ], + [ "canvasContext" , "the canvas to draw on" ], + [ "cx" , "center x coordinate" ], + [ "cy" , "center y coordinate" ], + [ "color" , "series color" ], + [ "pointSize" , "the radius of the image." ], + [ "idx" , "the row-index of the point in the data."] + ], + "description": "Draw a custom item when a point is highlighted. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy) Also see <a href='#drawPointCallback'>drawPointCallback</a>" + }, + "highlightSeriesOpts": { + "default": "null", + "labels": ["Interactive Elements"], + "type": "Object", + "description": "When set, the options from this object are applied to the timeseries closest to the mouse pointer for interactive highlighting. See also 'highlightCallback'. Example: highlightSeriesOpts: { strokeWidth: 3 }." + }, + "highlightSeriesBackgroundAlpha": { + "default": "0.5", + "labels": ["Interactive Elements"], + "type": "float", + "description": "Fade the background while highlighting series. 1=fully visible background (disable fading), 0=hiddden background (show highlighted series only)." + }, + "includeZero": { + "default": "false", + "labels": ["Axis display"], + "type": "boolean", + "description": "Usually, dygraphs will use the range of the data plus some padding to set the range of the y-axis. If this option is set, the y-axis will always include zero, typically as the lowest value. This can be used to avoid exaggerating the variance in the data" + }, + "rollPeriod": { + "default": "1", + "labels": ["Error Bars", "Rolling Averages"], + "type": "integer >= 1", + "description": "Number of days over which to average data. Discussed extensively above." + }, + "unhighlightCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(event)", + "parameters": [ + [ "event" , "the mouse event" ] + ], + "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph." + }, + "axisTickSize": { + "default": "3.0", + "labels": ["Axis display"], + "type": "number", + "description": "The size of the line to display next to each tick mark on x- or y-axes." + }, + "labelsSeparateLines": { + "default": "false", + "labels": ["Legend"], + "type": "boolean", + "description": "Put <code><br/></code> between lines in the label string. Often used in conjunction with <strong>labelsDiv</strong>." + }, + "xValueFormatter": { + "default": "", + "labels": ["Deprecated"], + "type": "", + "description": "Prefer axes: { x: { valueFormatter } }" + }, + "valueFormatter": { + "default": "Depends on the type of your data.", + "labels": ["Legend", "Value display/formatting"], + "type": "function(num or millis, opts, dygraph)", + "description": "Function to provide a custom display format for the values displayed on mouseover. This does not affect the values that appear on tick marks next to the axes. To format those, see axisLabelFormatter. This is usually set on a <a href='per-axis.html'>per-axis</a> basis. For date axes, you can call new Date(millis) to get a Date object. opts is a function you can call to access various options (e.g. opts('labelsKMB'))." + }, + "pixelsPerYLabel": { + "default": "", + "labels": ["Deprecated"], + "type": "integer", + "description": "Prefer axes: { y: { pixelsPerLabel } }" + }, + "annotationMouseOverHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "description": "If provided, this function is called whenever the user mouses over an annotation." + }, + "annotationMouseOutHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "parameters": [ + [ "annotation" , "the annotation left" ], + [ "point" , "the point associated with the annotation" ], + [ "dygraph" , "the reference graph" ], + [ "event" , "the mouse event" ] + ], + "description": "If provided, this function is called whenever the user mouses out of an annotation." + }, + "annotationClickHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "parameters": [ + [ "annotation" , "the annotation left" ], + [ "point" , "the point associated with the annotation" ], + [ "dygraph" , "the reference graph" ], + [ "event" , "the mouse event" ] + ], + "description": "If provided, this function is called whenever the user clicks on an annotation." + }, + "annotationDblClickHandler": { + "default": "null", + "labels": ["Annotations"], + "type": "function(annotation, point, dygraph, event)", + "parameters": [ + [ "annotation" , "the annotation left" ], + [ "point" , "the point associated with the annotation" ], + [ "dygraph" , "the reference graph" ], + [ "event" , "the mouse event" ] + ], + "description": "If provided, this function is called whenever the user double-clicks on an annotation." + }, + "drawCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(dygraph, is_initial)", + "parameters": [ + [ "dygraph" , "The graph being drawn" ], + [ "is_initial" , "True if this is the initial draw, false for subsequent draws." ] + ], + "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning." + }, + "labelsKMG2": { + "default": "false", + "labels": ["Value display/formatting"], + "type": "boolean", + "description": "Show k/M/G for kilo/Mega/Giga on y-axis. This is different than <code>labelsKMB</code> in that it uses base 2, not 10." + }, + "delimiter": { + "default": ",", + "labels": ["CSV parsing"], + "type": "string", + "description": "The delimiter to look for when separating fields of a CSV file. Setting this to a tab is not usually necessary, since tab-delimited data is auto-detected." + }, + "axisLabelFontSize": { + "default": "14", + "labels": ["Axis display"], + "type": "integer", + "description": "Size of the font (in pixels) to use in the axis labels, both x- and y-axis." + }, + "underlayCallback": { + "default": "null", + "labels": ["Callbacks"], + "type": "function(context, area, dygraph)", + "parameters": [ + [ "context" , "the canvas drawing context on which to draw" ], + [ "area" , "An object with {x,y,w,h} properties describing the drawing area." ], + [ "dygraph" , "the reference graph" ] + ], + "description": "When set, this callback gets called before the chart is drawn. It details on how to use this." + }, + "width": { + "default": "480", + "labels": ["Overall display"], + "type": "integer", + "description": "Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." + }, + "interactionModel": { + "default": "...", + "labels": ["Interactive Elements"], + "type": "Object", + "description": "TODO(konigsberg): document this" + }, + "ticker": { + "default": "Dygraph.dateTicker or Dygraph.numericTicks", + "labels": ["Axis display"], + "type": "function(min, max, pixels, opts, dygraph, vals) -> [{v: ..., label: ...}, ...]", + "parameters": [ + [ "min" , "" ], + [ "max" , "" ], + [ "pixels" , "" ], + [ "opts" , "" ], + [ "dygraph" , "the reference graph" ], + [ "vals" , "" ] + ], + "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion. This is set on a <a href='per-axis.html'>per-axis</a> basis." + }, + "xAxisLabelWidth": { + "default": "50", + "labels": ["Axis display"], + "type": "integer", + "description": "Width, in pixels, of the x-axis labels." + }, + "xAxisHeight": { + "default": "(null)", + "labels": ["Axis display"], + "type": "integer", + "description": "Height, in pixels, of the x-axis. If not set explicitly, this is computed based on axisLabelFontSize and axisTickSize." + }, + "showLabelsOnHighlight": { + "default": "true", + "labels": ["Interactive Elements", "Legend"], + "type": "boolean", + "description": "Whether to show the legend upon mouseover." + }, + "axis": { + "default": "(none)", + "labels": ["Axis display"], + "type": "string or object", + "description": "Set to either an object ({}) filled with options for this axis or to the name of an existing data series with its own axis to re-use that axis. See tests for usage." + }, + "pixelsPerXLabel": { + "default": "", + "labels": ["Deprecated"], + "type": "integer", + "description": "Prefer axes { x: { pixelsPerLabel } }" + }, + "pixelsPerLabel": { + "default": "60 (x-axis) or 30 (y-axes)", + "labels": ["Axis display", "Grid"], + "type": "integer", + "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks. This is set on a <a href='per-axis.html'>per-axis</a> basis." + }, + "labelsDiv": { + "default": "null", + "labels": ["Legend"], + "type": "DOM element or string", + "example": "<code style='font-size: small'>document.getElementById('foo')</code>or<code>'foo'", + "description": "Show data labels in an external div, rather than on the graph. This value can either be a div element or a div id." + }, + "fractions": { + "default": "false", + "labels": ["CSV parsing", "Error Bars"], + "type": "boolean", + "description": "When set, attempt to parse each cell in the CSV file as \"a/b\", where a and b are integers. The ratio will be plotted. This allows computation of Wilson confidence intervals (see below)." + }, + "logscale": { + "default": "false", + "labels": ["Axis display"], + "type": "boolean", + "description": "When set for a y-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed.\n\nNot compatible with showZero, and ignores connectSeparatedPoints. Also, showing log scale with valueRanges that are less than zero will result in an unviewable graph." + }, + "strokeWidth": { + "default": "1.0", + "labels": ["Data Line display"], + "type": "float", + "example": "0.5, 2.0", + "description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs." + }, + "strokePattern": { + "default": "null", + "labels": ["Data Line display"], + "type": "array<integer>", + "example": "[10, 2, 5, 2]", + "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed lines." + }, + "strokeBorderWidth": { + "default": "null", + "labels": ["Data Line display"], + "type": "float", + "example": "1.0", + "description": "Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines." + }, + "strokeBorderColor": { + "default": "white", + "labels": ["Data Line display"], + "type": "string", + "example": "red, #ccffdd", + "description": "Color for the line border used if strokeBorderWidth is set." + }, + "wilsonInterval": { + "default": "true", + "labels": ["Error Bars"], + "type": "boolean", + "description": "Use in conjunction with the \"fractions\" option. Instead of plotting +/- N standard deviations, dygraphs will compute a Wilson confidence interval and plot that. This has more reasonable behavior for ratios close to 0 or 1." + }, + "fillGraph": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "Should the area underneath the graph be filled? This option is not compatible with error bars. This may be set on a <a href='per-axis.html'>per-series</a> basis." + }, + "highlightCircleSize": { + "default": "3", + "labels": ["Interactive Elements"], + "type": "integer", + "description": "The size in pixels of the dot drawn over highlighted points." + }, + "gridLineColor": { + "default": "rgb(128,128,128)", + "labels": ["Grid"], + "type": "red, blue", + "description": "The color of the gridlines. This may be set on a per-axis basis to define each axis' grid separately." + }, + "visibility": { + "default": "[true, true, ...]", + "labels": ["Data Line display"], + "type": "Array of booleans", + "description": "Which series should initially be visible? Once the Dygraph has been constructed, you can access and modify the visibility of each series using the <code>visibility</code> and <code>setVisibility</code> methods." + }, + "valueRange": { + "default": "Full range of the input is shown", + "labels": ["Axis display"], + "type": "Array of two numbers", + "example": "[10, 110]", + "description": "Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately. If either limit is unspecified, it will be calculated automatically (e.g. [null, 30] to automatically calculate just the lower bound)" + }, + "labelsDivWidth": { + "default": "250", + "labels": ["Legend"], + "type": "integer", + "description": "Width (in pixels) of the div which shows information on the currently-highlighted points." + }, + "colorSaturation": { + "default": "1.0", + "labels": ["Data Series Colors"], + "type": "float (0.0 - 1.0)", + "description": "If <strong>colors</strong> is not specified, saturation of the automatically-generated data series colors." + }, + "yAxisLabelWidth": { + "default": "50", + "labels": ["Axis display"], + "type": "integer", + "description": "Width, in pixels, of the y-axis labels. This also affects the amount of space available for a y-axis chart label." + }, + "hideOverlayOnMouseOut": { + "default": "true", + "labels": ["Interactive Elements", "Legend"], + "type": "boolean", + "description": "Whether to hide the legend when the mouse leaves the chart area." + }, + "yValueFormatter": { + "default": "", + "labels": ["Deprecated"], + "type": "", + "description": "Prefer axes: { y: { valueFormatter } }" + }, + "legend": { + "default": "onmouseover", + "labels": ["Legend"], + "type": "string", + "description": "When to display the legend. By default, it only appears when a user mouses over the chart. Set it to \"always\" to always display a legend of some sort." + }, + "labelsShowZeroValues": { + "default": "true", + "labels": ["Legend"], + "type": "boolean", + "description": "Show zero value labels in the labelsDiv." + }, + "stepPlot": { + "default": "false", + "labels": ["Data Line display"], + "type": "boolean", + "description": "When set, display the graph as a step plot instead of a line plot. This option may either be set for the whole graph or for single series." + }, + "labelsKMB": { + "default": "false", + "labels": ["Value display/formatting"], + "type": "boolean", + "description": "Show K/M/B for thousands/millions/billions on y-axis." + }, + "rightGap": { + "default": "5", + "labels": ["Overall display"], + "type": "integer", + "description": "Number of pixels to leave blank at the right edge of the Dygraph. This makes it easier to highlight the right-most data point." + }, + "avoidMinZero": { + "default": "false", + "labels": ["Deprecated"], + "type": "boolean", + "description": "Deprecated, please use yRangePad instead. When set, the heuristic that fixes the Y axis at zero for a data set with the minimum Y value of zero is disabled. \nThis is particularly useful for data sets that contain many zero values, especially for step plots which may otherwise have lines not visible running along the bottom axis." + }, + "drawAxesAtZero": { + "default": "false", + "labels": ["Axis display"], + "type": "boolean", + "description": "When set, draw the X axis at the Y=0 position and the Y axis at the X=0 position if those positions are inside the graph's visible area. Otherwise, draw the axes at the bottom or left graph edge as usual." + }, + "xRangePad": { + "default": "0", + "labels": ["Axis display"], + "type": "float", + "description": "Add the specified amount of extra space (in pixels) around the X-axis value range to ensure points at the edges remain visible." + }, + "yRangePad": { + "default": "null", + "labels": ["Axis display"], + "type": "float", + "description": "If set, add the specified amount of extra space (in pixels) around the Y-axis value range to ensure points at the edges remain visible. If unset, use the traditional Y padding algorithm." + }, + "xAxisLabelFormatter": { + "default": "", + "labels": ["Deprecated"], + "type": "", + "description": "Prefer axes { x: { axisLabelFormatter } }" + }, + "axisLabelFormatter": { + "default": "Depends on the data type", + "labels": ["Axis display"], + "type": "function(number or Date, granularity, opts, dygraph)", + "parameters": [ + [ "number or date" , "Either a number (for a numeric axis) or a Date object (for a date axis)" ], + [ "granularity" , "specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY." ], + [ "opts" , "a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')." ], + [ "dygraph" , "the referenced graph" ] + ], + "description": "Function to call to format the tick values that appear along an axis. This is usually set on a <a href='per-axis.html'>per-axis</a> basis." + }, + "clickCallback": { + "snippet": "function(e, date_millis){<br> alert(new Date(date_millis));<br>}", + "default": "null", + "labels": ["Callbacks"], + "type": "function(e, x, points)", + "parameters": [ + [ "e" , "The event object for the click" ], + [ "x" , "The x value that was clicked (for dates, this is milliseconds since epoch)" ], + [ "points" , "The closest points along that date. See <a href='#point_properties'>Point properties</a> for details." ] + ], + "description": "A function to call when the canvas is clicked." + }, + "yAxisLabelFormatter": { + "default": "", + "labels": ["Deprecated"], + "type": "", + "description": "Prefer axes: { y: { axisLabelFormatter } }" + }, + "labels": { + "default": "[\"X\", \"Y1\", \"Y2\", ...]*", + "labels": ["Legend"], + "type": "array<string>", + "description": "A name for each data series, including the independent (X) series. For CSV files and DataTable objections, this is determined by context. For raw data, this must be specified. If it is not, default values are supplied and a warning is logged." + }, + "dateWindow": { + "default": "Full range of the input is shown", + "labels": ["Axis display"], + "type": "Array of two Dates or numbers", + "example": "[<br> Date.parse('2006-01-01'),<br> (new Date()).valueOf()<br>]", + "description": "Initially zoom in on a section of the graph. Is of the form [earliest, latest], where earliest/latest are milliseconds since epoch. If the data for the x-axis is numeric, the values in dateWindow must also be numbers." + }, + "showRoller": { + "default": "false", + "labels": ["Interactive Elements", "Rolling Averages"], + "type": "boolean", + "description": "If the rolling average period text box should be shown." + }, + "sigma": { + "default": "2.0", + "labels": ["Error Bars"], + "type": "float", + "description": "When errorBars is set, shade this many standard deviations above/below each point." + }, + "customBars": { + "default": "false", + "labels": ["CSV parsing", "Error Bars"], + "type": "boolean", + "description": "When set, parse each CSV cell as \"low;middle;high\". Error bars will be drawn for each point between low and high, with the series itself going through middle." + }, + "colorValue": { + "default": "1.0", + "labels": ["Data Series Colors"], + "type": "float (0.0 - 1.0)", + "description": "If colors is not specified, value of the data series colors, as in hue/saturation/value. (0.0-1.0, default 0.5)" + }, + "errorBars": { + "default": "false", + "labels": ["CSV parsing", "Error Bars"], + "type": "boolean", + "description": "Does the data contain standard deviations? Setting this to true alters the input format (see above)." + }, + "displayAnnotations": { + "default": "false", + "labels": ["Annotations"], + "type": "boolean", + "description": "Only applies when Dygraphs is used as a GViz chart. Causes string columns following a data series to be interpreted as annotations on points in that series. This is the same format used by Google's AnnotatedTimeLine chart." + }, + "panEdgeFraction": { + "default": "null", + "labels": ["Axis display", "Interactive Elements"], + "type": "float", + "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% pased the edges of the displayed values. null means no bounds." + }, + "title": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display above the chart. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-title' classes." + }, + "titleHeight": { + "default": "18", + "labels": ["Chart labels"], + "type": "integer", + "description": "Height of the chart title, in pixels. This also controls the default font size of the title. If you style the title on your own, this controls how much space is set aside above the chart for the title's div." + }, + "xlabel": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display below the chart's x-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-xlabel' classes." + }, + "xLabelHeight": { + "labels": ["Chart labels"], + "type": "integer", + "default": "18", + "description": "Height of the x-axis label, in pixels. This also controls the default font size of the x-axis label. If you style the label on your own, this controls how much space is set aside below the chart for the x-axis label's div." + }, + "ylabel": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display to the left of the chart's y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-ylabel' classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the yAxisLabelWidth option. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it's rotated, so width is controlled by the 'height' property) or set the yLabelWidth option." + }, + "y2label": { + "labels": ["Chart labels"], + "type": "string", + "default": "null", + "description": "Text to display to the right of the chart's secondary y-axis. This label is only displayed if a secondary y-axis is present. See <a href='http://dygraphs.com/tests/two-axes.html'>this test</a> for an example of how to do this. The comments for the 'ylabel' option generally apply here as well. This label gets a 'dygraph-y2label' instead of a 'dygraph-ylabel' class." + }, + "yLabelWidth": { + "labels": ["Chart labels"], + "type": "integer", + "default": "18", + "description": "Width of the div which contains the y-axis label. Since the y-axis label appears rotated 90 degrees, this actually affects the height of its div." + }, + "isZoomedIgnoreProgrammaticZoom" : { + "default": "false", + "labels": ["Zooming"], + "type": "boolean", + "description" : "When this option is passed to updateOptions() along with either the <code>dateWindow</code> or <code>valueRange</code> options, the zoom flags are not changed to reflect a zoomed state. This is primarily useful for when the display area of a chart is changed programmatically and also where manual zooming is allowed and use is made of the <code>isZoomed</code> method to determine this." + }, + "drawXGrid": { + "default": "true", + "labels": ["Grid","Deprecated"], + "type": "boolean", + "description" : "Use the per-axis option drawGrid instead. Whether to display vertical gridlines under the chart." + }, + "drawYGrid": { + "default": "true", + "labels": ["Grid","Deprecated"], + "type": "boolean", + "description" : "Use the per-axis option drawGrid instead. Whether to display horizontal gridlines under the chart." + }, + "drawGrid": { + "default": "true for x and y, false for y2", + "labels": ["Grid"], + "type": "boolean", + "description" : "Whether to display gridlines in the chart. This may be set on a per-axis basis to define the visibility of each axis' grid separately." + }, + "independentTicks": { + "default": "true for y, false for y2", + "labels": ["Axis display", "Grid"], + "type": "boolean", + "description" : "Only valid for y and y2, has no effect on x: This option defines whether the y axes should align their ticks or if they should be independent. Possible combinations: 1.) y=true, y2=false (default): y is the primary axis and the y2 ticks are aligned to the the ones of y. (only 1 grid) 2.) y=false, y2=true: y2 is the primary axis and the y ticks are aligned to the the ones of y2. (only 1 grid) 3.) y=true, y2=true: Both axis are independent and have their own ticks. (2 grids) 4.) y=false, y2=false: Invalid configuration causes an error." + }, + "drawXAxis": { + "default": "true", + "labels": ["Axis display"], + "type": "boolean", + "description" : "Whether to draw the x-axis. Setting this to false also prevents x-axis ticks from being drawn and reclaims the space for the chart grid/lines." + }, + "drawYAxis": { + "default": "true", + "labels": ["Axis display"], + "type": "boolean", + "description" : "Whether to draw the y-axis. Setting this to false also prevents y-axis ticks from being drawn and reclaims the space for the chart grid/lines." + }, + "gridLineWidth": { + "default": "0.3", + "labels": ["Grid"], + "type": "float", + "description" : "Thickness (in pixels) of the gridlines drawn under the chart. The vertical/horizontal gridlines can be turned off entirely by using the drawXGrid and drawYGrid options. This may be set on a per-axis basis to define each axis' grid separately." + }, + "axisLineWidth": { + "default": "0.3", + "labels": ["Axis display"], + "type": "float", + "description" : "Thickness (in pixels) of the x- and y-axis lines." + }, + "axisLineColor": { + "default": "black", + "labels": ["Axis display"], + "type": "string", + "description" : "Color of the x- and y-axis lines. Accepts any value which the HTML canvas strokeStyle attribute understands, e.g. 'black' or 'rgb(0, 100, 255)'." + }, + "fillAlpha": { + "default": "0.15", + "labels": ["Error Bars", "Data Series Colors"], + "type": "float (0.0 - 1.0)", + "description" : "Error bars (or custom bars) for each series are drawn in the same color as the series, but with partial transparency. This sets the transparency. A value of 0.0 means that the error bars will not be drawn, whereas a value of 1.0 means that the error bars will be as dark as the line for the series itself. This can be used to produce chart lines whose thickness varies at each point." + }, + "axisLabelColor": { + "default": "black", + "labels": ["Axis display"], + "type": "string", + "description" : "Color for x- and y-axis labels. This is a CSS color string." + }, + "axisLabelWidth": { + "default": "50", + "labels": ["Axis display", "Chart labels"], + "type": "integer", + "description" : "Width (in pixels) of the containing divs for x- and y-axis labels. For the y-axis, this also controls " + }, + "sigFigs" : { + "default": "null", + "labels": ["Value display/formatting"], + "type": "integer", + "description": "By default, dygraphs displays numbers with a fixed number of digits after the decimal point. If you'd prefer to have a fixed number of significant figures, set this option to that number of sig figs. A value of 2, for instance, would cause 1 to be display as 1.0 and 1234 to be displayed as 1.23e+3." + }, + "digitsAfterDecimal" : { + "default": "2", + "labels": ["Value display/formatting"], + "type": "integer", + "description": "Unless it's run in scientific mode (see the <code>sigFigs</code> option), dygraphs displays numbers with <code>digitsAfterDecimal</code> digits after the decimal point. Trailing zeros are not displayed, so with a value of 2 you'll get '0', '0.1', '0.12', '123.45' but not '123.456' (it will be rounded to '123.46'). Numbers with absolute value less than 0.1^digitsAfterDecimal (i.e. those which would show up as '0.00') will be displayed in scientific notation." + }, + "maxNumberWidth" : { + "default": "6", + "labels": ["Value display/formatting"], + "type": "integer", + "description": "When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than <code>maxNumberWidth</code> digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you'd like to see all those digits, set this to something large, like 20 or 30." + }, + "file": { + "default": "(set when constructed)", + "labels": ["Data"], + "type": "string (URL of CSV or CSV), GViz DataTable or 2D Array", + "description": "Sets the data being displayed in the chart. This can only be set when calling updateOptions; it cannot be set from the constructor. For a full description of valid data formats, see the <a href='http://dygraphs.com/data.html'>Data Formats</a> page." + }, + "timingName": { + "default": "null", + "labels": [ "Debugging" ], + "type": "string", + "description": "Set this option to log timing information. The value of the option will be logged along with the timimg, so that you can distinguish multiple dygraphs on the same page." + }, + "showRangeSelector": { + "default": "false", + "labels": ["Interactive Elements"], + "type": "boolean", + "description": "Show or hide the range selector widget." + }, + "rangeSelectorHeight": { + "default": "40", + "labels": ["Interactive Elements"], + "type": "integer", + "description": "Height, in pixels, of the range selector widget. This option can only be specified at Dygraph creation time." + }, + "rangeSelectorPlotStrokeColor": { + "default": "#808FAB", + "labels": ["Interactive Elements"], + "type": "string", + "description": "The range selector mini plot stroke color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off stroke." + }, + "rangeSelectorPlotFillColor": { + "default": "#A7B1C4", + "labels": ["Interactive Elements"], + "type": "string", + "description": "The range selector mini plot fill color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off fill." + }, + "animatedZooms": { + "default": "false", + "labels": ["Interactive Elements"], + "type": "boolean", + "description": "Set this option to animate the transition between zoom windows. Applies to programmatic and interactive zooms. Note that if you also set a drawCallback, it will be called several times on each zoom. If you set a zoomCallback, it will only be called after the animation is complete." + }, + "plotter": { + "default": "[DygraphCanvasRenderer.Plotters.fillPlotter, DygraphCanvasRenderer.Plotters.errorPlotter, DygraphCanvasRenderer.Plotters.linePlotter]", + "labels": ["Data Line display"], + "type": "array or function", + "description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series." + }, + "series": { + "default": "null", + "labels": ["Series"], + "type": "Object", + "description": "Defines per-series options. Its keys match the y-axis label names, and the values are dictionaries themselves that contain options specific to that series. When this option is missing, it falls back on the old-style of per-series options comingled with global options." + }, + "plugins": { + "default": "[]", + "labels": ["Configuration"], + "type": "Array<plugin>", + "description": "Defines per-graph plug-ins. Useful for per-graph customization" + } +} +; // </JSON> +// NOTE: in addition to parsing as JS, this snippet is expected to be valid +// JSON. This assumption cannot be checked in JS, but it will be checked when +// documentation is generated by the generate-documentation.py script. For the +// most part, this just means that you should always use double quotes. + +// Do a quick sanity check on the options reference. +(function() { + "use strict"; + var warn = function(msg) { if (window.console) window.console.warn(msg); }; + var flds = ['type', 'default', 'description']; + var valid_cats = [ + 'Annotations', + 'Axis display', + 'Chart labels', + 'CSV parsing', + 'Callbacks', + 'Data', + 'Data Line display', + 'Data Series Colors', + 'Error Bars', + 'Grid', + 'Interactive Elements', + 'Legend', + 'Overall display', + 'Rolling Averages', + 'Series', + 'Value display/formatting', + 'Zooming', + 'Debugging', + 'Configuration', + 'Deprecated' + ]; + var i; + var cats = {}; + for (i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true; + + for (var k in Dygraph.OPTIONS_REFERENCE) { + if (!Dygraph.OPTIONS_REFERENCE.hasOwnProperty(k)) continue; + var op = Dygraph.OPTIONS_REFERENCE[k]; + for (i = 0; i < flds.length; i++) { + if (!op.hasOwnProperty(flds[i])) { + warn('Option ' + k + ' missing "' + flds[i] + '" property'); + } else if (typeof(op[flds[i]]) != 'string') { + warn(k + '.' + flds[i] + ' must be of type string'); + } + } + var labels = op.labels; + if (typeof(labels) !== 'object') { + warn('Option "' + k + '" is missing a "labels": [...] option'); + } else { + for (i = 0; i < labels.length; i++) { + if (!cats.hasOwnProperty(labels[i])) { + warn('Option "' + k + '" has label "' + labels[i] + + '", which is invalid.'); + } + } + } + } +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-options.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-options.js new file mode 100644 index 00000000..0f90086c --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-options.js @@ -0,0 +1,384 @@ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview DygraphOptions is responsible for parsing and returning information about options. + * + * Still tightly coupled to Dygraphs, we could remove some of that, you know. + */ + +var DygraphOptions = (function() { + +/*jshint sub:true */ +/*global Dygraph:false */ +"use strict"; + +/* + * Interesting member variables: (REMOVING THIS LIST AS I CLOSURIZE) + * global_ - global attributes (common among all graphs, AIUI) + * user - attributes set by the user + * series_ - { seriesName -> { idx, yAxis, options }} + */ + +/** + * This parses attributes into an object that can be easily queried. + * + * It doesn't necessarily mean that all options are available, specifically + * if labels are not yet available, since those drive details of the per-series + * and per-axis options. + * + * @param {Dygraph} dygraph The chart to which these options belong. + * @constructor + */ +var DygraphOptions = function(dygraph) { + /** + * The dygraph. + * @type {!Dygraph} + */ + this.dygraph_ = dygraph; + + /** + * Array of axis index to { series : [ series names ] , options : { axis-specific options. } + * @type {Array.<{series : Array.<string>, options : Object}>} @private + */ + this.yAxes_ = []; + + /** + * Contains x-axis specific options, which are stored in the options key. + * This matches the yAxes_ object structure (by being a dictionary with an + * options element) allowing for shared code. + * @type {options: Object} @private + */ + this.xAxis_ = {}; + this.series_ = {}; + + // Once these two objects are initialized, you can call get(); + this.global_ = this.dygraph_.attrs_; + this.user_ = this.dygraph_.user_attrs_ || {}; + + /** + * A list of series in columnar order. + * @type {Array.<string>} + */ + this.labels_ = []; + + this.highlightSeries_ = this.get("highlightSeriesOpts") || {}; + this.reparseSeries(); +}; + +/** + * Not optimal, but does the trick when you're only using two axes. + * If we move to more axes, this can just become a function. + * + * @type {Object.<number>} + * @private + */ +DygraphOptions.AXIS_STRING_MAPPINGS_ = { + 'y' : 0, + 'Y' : 0, + 'y1' : 0, + 'Y1' : 0, + 'y2' : 1, + 'Y2' : 1 +}; + +/** + * @param {string|number} axis + * @private + */ +DygraphOptions.axisToIndex_ = function(axis) { + if (typeof(axis) == "string") { + if (DygraphOptions.AXIS_STRING_MAPPINGS_.hasOwnProperty(axis)) { + return DygraphOptions.AXIS_STRING_MAPPINGS_[axis]; + } + throw "Unknown axis : " + axis; + } + if (typeof(axis) == "number") { + if (axis === 0 || axis === 1) { + return axis; + } + throw "Dygraphs only supports two y-axes, indexed from 0-1."; + } + if (axis) { + throw "Unknown axis : " + axis; + } + // No axis specification means axis 0. + return 0; +}; + +/** + * Reparses options that are all related to series. This typically occurs when + * options are either updated, or source data has been made available. + * + * TODO(konigsberg): The method name is kind of weak; fix. + */ +DygraphOptions.prototype.reparseSeries = function() { + var labels = this.get("labels"); + if (!labels) { + return; // -- can't do more for now, will parse after getting the labels. + } + + this.labels_ = labels.slice(1); + + this.yAxes_ = [ { series : [], options : {}} ]; // Always one axis at least. + this.xAxis_ = { options : {} }; + this.series_ = {}; + + // Traditionally, per-series options were specified right up there with the options. For instance + // { + // labels: [ "X", "foo", "bar" ], + // pointSize: 3, + // foo : {}, // options for foo + // bar : {} // options for bar + // } + // + // Moving forward, series really should be specified in the series element, separating them. + // like so: + // + // { + // labels: [ "X", "foo", "bar" ], + // pointSize: 3, + // series : { + // foo : {}, // options for foo + // bar : {} // options for bar + // } + // } + // + // So, if series is found, it's expected to contain per-series data, otherwise we fall + // back. + var oldStyleSeries = !this.user_["series"]; + + if (oldStyleSeries) { + var axisId = 0; // 0-offset; there's always one. + // Go through once, add all the series, and for those with {} axis options, add a new axis. + for (var idx = 0; idx < this.labels_.length; idx++) { + var seriesName = this.labels_[idx]; + + var optionsForSeries = this.user_[seriesName] || {}; + + var yAxis = 0; + var axis = optionsForSeries["axis"]; + if (typeof(axis) == 'object') { + yAxis = ++axisId; + this.yAxes_[yAxis] = { series : [ seriesName ], options : axis }; + } + + // Associate series without axis options with axis 0. + if (!axis) { // undefined + this.yAxes_[0].series.push(seriesName); + } + + this.series_[seriesName] = { idx: idx, yAxis: yAxis, options : optionsForSeries }; + } + + // Go through one more time and assign series to an axis defined by another + // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } } + for (var idx = 0; idx < this.labels_.length; idx++) { + var seriesName = this.labels_[idx]; + var optionsForSeries = this.series_[seriesName]["options"]; + var axis = optionsForSeries["axis"]; + + if (typeof(axis) == 'string') { + if (!this.series_.hasOwnProperty(axis)) { + Dygraph.error("Series " + seriesName + " wants to share a y-axis with " + + "series " + axis + ", which does not define its own axis."); + return; + } + var yAxis = this.series_[axis].yAxis; + this.series_[seriesName].yAxis = yAxis; + this.yAxes_[yAxis].series.push(seriesName); + } + } + } else { + for (var idx = 0; idx < this.labels_.length; idx++) { + var seriesName = this.labels_[idx]; + var optionsForSeries = this.user_.series[seriesName] || {}; + var yAxis = DygraphOptions.axisToIndex_(optionsForSeries["axis"]); + + this.series_[seriesName] = { + idx: idx, + yAxis: yAxis, + options : optionsForSeries }; + + if (!this.yAxes_[yAxis]) { + this.yAxes_[yAxis] = { series : [ seriesName ], options : {} }; + } else { + this.yAxes_[yAxis].series.push(seriesName); + } + } + } + + var axis_opts = this.user_["axes"] || {}; + Dygraph.update(this.yAxes_[0].options, axis_opts["y"] || {}); + if (this.yAxes_.length > 1) { + Dygraph.update(this.yAxes_[1].options, axis_opts["y2"] || {}); + } + Dygraph.update(this.xAxis_.options, axis_opts["x"] || {}); +}; + +/** + * Get a global value. + * + * @param {string} name the name of the option. + */ +DygraphOptions.prototype.get = function(name) { + var result = this.getGlobalUser_(name); + if (result !== null) { + return result; + } + return this.getGlobalDefault_(name); +}; + +DygraphOptions.prototype.getGlobalUser_ = function(name) { + if (this.user_.hasOwnProperty(name)) { + return this.user_[name]; + } + return null; +}; + +DygraphOptions.prototype.getGlobalDefault_ = function(name) { + if (this.global_.hasOwnProperty(name)) { + return this.global_[name]; + } + if (Dygraph.DEFAULT_ATTRS.hasOwnProperty(name)) { + return Dygraph.DEFAULT_ATTRS[name]; + } + return null; +}; + +/** + * Get a value for a specific axis. If there is no specific value for the axis, + * the global value is returned. + * + * @param {string} name the name of the option. + * @param {string|number} axis the axis to search. Can be the string representation + * ("y", "y2") or the axis number (0, 1). + */ +DygraphOptions.prototype.getForAxis = function(name, axis) { + var axisIdx; + var axisString; + + // Since axis can be a number or a string, straighten everything out here. + if (typeof(axis) == 'number') { + axisIdx = axis; + axisString = axisIdx === 0 ? "y" : "y2"; + } else { + if (axis == "y1") { axis = "y"; } // Standardize on 'y'. Is this bad? I think so. + if (axis == "y") { + axisIdx = 0; + } else if (axis == "y2") { + axisIdx = 1; + } else if (axis == "x") { + axisIdx = -1; // simply a placeholder for below. + } else { + throw "Unknown axis " + axis; + } + axisString = axis; + } + + var userAxis = (axisIdx == -1) ? this.xAxis_ : this.yAxes_[axisIdx]; + + // Search the user-specified axis option first. + if (userAxis) { // This condition could be removed if we always set up this.yAxes_ for y2. + var axisOptions = userAxis.options; + if (axisOptions.hasOwnProperty(name)) { + return axisOptions[name]; + } + } + + // User-specified global options second. + var result = this.getGlobalUser_(name); + if (result !== null) { + return result; + } + + // Default axis options third. + var defaultAxisOptions = Dygraph.DEFAULT_ATTRS.axes[axisString]; + if (defaultAxisOptions.hasOwnProperty(name)) { + return defaultAxisOptions[name]; + } + + // Default global options last. + return this.getGlobalDefault_(name); +}; + +/** + * Get a value for a specific series. If there is no specific value for the series, + * the value for the axis is returned (and afterwards, the global value.) + * + * @param {string} name the name of the option. + * @param {string} series the series to search. + */ +DygraphOptions.prototype.getForSeries = function(name, series) { + // Honors indexes as series. + if (series === this.dygraph_.getHighlightSeries()) { + if (this.highlightSeries_.hasOwnProperty(name)) { + return this.highlightSeries_[name]; + } + } + + if (!this.series_.hasOwnProperty(series)) { + throw "Unknown series: " + series; + } + + var seriesObj = this.series_[series]; + var seriesOptions = seriesObj["options"]; + if (seriesOptions.hasOwnProperty(name)) { + return seriesOptions[name]; + } + + return this.getForAxis(name, seriesObj["yAxis"]); +}; + +/** + * Returns the number of y-axes on the chart. + * @return {number} the number of axes. + */ +DygraphOptions.prototype.numAxes = function() { + return this.yAxes_.length; +}; + +/** + * Return the y-axis for a given series, specified by name. + */ +DygraphOptions.prototype.axisForSeries = function(series) { + return this.series_[series].yAxis; +}; + +/** + * Returns the options for the specified axis. + */ +// TODO(konigsberg): this is y-axis specific. Support the x axis. +DygraphOptions.prototype.axisOptions = function(yAxis) { + return this.yAxes_[yAxis].options; +}; + +/** + * Return the series associated with an axis. + */ +DygraphOptions.prototype.seriesForAxis = function(yAxis) { + return this.yAxes_[yAxis].series; +}; + +/** + * Return the list of all series, in their columnar order. + */ +DygraphOptions.prototype.seriesNames = function() { + return this.labels_; +}; + +/* Are we using this? */ +/** + * Return the index of the specified series. + * @param {string} series the series name. + */ +DygraphOptions.prototype.indexOfSeries = function(series) { + return this.series_[series].idx; +}; + +return DygraphOptions; + +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-plugin-base.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-plugin-base.js new file mode 100644 index 00000000..7f758a9a --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-plugin-base.js @@ -0,0 +1,4 @@ +/*global Dygraph:false */ + +// Namespace for plugins. Load this before plugins/*.js files. +Dygraph.Plugins = {}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-plugin-install.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-plugin-install.js new file mode 100644 index 00000000..806a9274 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-plugin-install.js @@ -0,0 +1,19 @@ +/*global Dygraph:false */ + +// This file defines the ordering of the plugins. +// +// The ordering is from most-general to most-specific. +// This means that, in an event cascade, plugins which have registered for that +// event will be called in reverse order. +// +// This is most relevant for plugins which register a layout event, e.g. +// Axes, Legend and ChartLabels. + +Dygraph.PLUGINS.push( + Dygraph.Plugins.Legend, + Dygraph.Plugins.Axes, + Dygraph.Plugins.RangeSelector, // Has to be before ChartLabels so that its callbacks are called after ChartLabels' callbacks. + Dygraph.Plugins.ChartLabels, + Dygraph.Plugins.Annotations, + Dygraph.Plugins.Grid +); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-tickers.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-tickers.js new file mode 100644 index 00000000..f4778b12 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-tickers.js @@ -0,0 +1,487 @@ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Description of this file. + * @author danvk@google.com (Dan Vanderkam) + * + * A ticker is a function with the following interface: + * + * function(a, b, pixels, options_view, dygraph, forced_values); + * -> [ { v: tick1_v, label: tick1_label[, label_v: label_v1] }, + * { v: tick2_v, label: tick2_label[, label_v: label_v2] }, + * ... + * ] + * + * The returned value is called a "tick list". + * + * Arguments + * --------- + * + * [a, b] is the range of the axis for which ticks are being generated. For a + * numeric axis, these will simply be numbers. For a date axis, these will be + * millis since epoch (convertable to Date objects using "new Date(a)" and "new + * Date(b)"). + * + * opts provides access to chart- and axis-specific options. It can be used to + * access number/date formatting code/options, check for a log scale, etc. + * + * pixels is the length of the axis in pixels. opts('pixelsPerLabel') is the + * minimum amount of space to be allotted to each label. For instance, if + * pixels=400 and opts('pixelsPerLabel')=40 then the ticker should return + * between zero and ten (400/40) ticks. + * + * dygraph is the Dygraph object for which an axis is being constructed. + * + * forced_values is used for secondary y-axes. The tick positions are typically + * set by the primary y-axis, so the secondary y-axis has no choice in where to + * put these. It simply has to generate labels for these data values. + * + * Tick lists + * ---------- + * Typically a tick will have both a grid/tick line and a label at one end of + * that line (at the bottom for an x-axis, at left or right for the y-axis). + * + * A tick may be missing one of these two components: + * - If "label_v" is specified instead of "v", then there will be no tick or + * gridline, just a label. + * - Similarly, if "label" is not specified, then there will be a gridline + * without a label. + * + * This flexibility is useful in a few situations: + * - For log scales, some of the tick lines may be too close to all have labels. + * - For date scales where years are being displayed, it is desirable to display + * tick marks at the beginnings of years but labels (e.g. "2006") in the + * middle of the years. + */ + +/*jshint globalstrict:true, sub:true */ +/*global Dygraph:false */ +"use strict"; + +/** @typedef {Array.<{v:number, label:string, label_v:(string|undefined)}>} */ +Dygraph.TickList = undefined; // the ' = undefined' keeps jshint happy. + +/** @typedef {function( + * number, + * number, + * number, + * function(string):*, + * Dygraph=, + * Array.<number>= + * ): Dygraph.TickList} + */ +Dygraph.Ticker = undefined; // the ' = undefined' keeps jshint happy. + +/** @type {Dygraph.Ticker} */ +Dygraph.numericLinearTicks = function(a, b, pixels, opts, dygraph, vals) { + var nonLogscaleOpts = function(opt) { + if (opt === 'logscale') return false; + return opts(opt); + }; + return Dygraph.numericTicks(a, b, pixels, nonLogscaleOpts, dygraph, vals); +}; + +/** @type {Dygraph.Ticker} */ +Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) { + var pixels_per_tick = /** @type{number} */(opts('pixelsPerLabel')); + var ticks = []; + var i, j, tickV, nTicks; + if (vals) { + for (i = 0; i < vals.length; i++) { + ticks.push({v: vals[i]}); + } + } else { + // TODO(danvk): factor this log-scale block out into a separate function. + if (opts("logscale")) { + nTicks = Math.floor(pixels / pixels_per_tick); + var minIdx = Dygraph.binarySearch(a, Dygraph.PREFERRED_LOG_TICK_VALUES, 1); + var maxIdx = Dygraph.binarySearch(b, Dygraph.PREFERRED_LOG_TICK_VALUES, -1); + if (minIdx == -1) { + minIdx = 0; + } + if (maxIdx == -1) { + maxIdx = Dygraph.PREFERRED_LOG_TICK_VALUES.length - 1; + } + // Count the number of tick values would appear, if we can get at least + // nTicks / 4 accept them. + var lastDisplayed = null; + if (maxIdx - minIdx >= nTicks / 4) { + for (var idx = maxIdx; idx >= minIdx; idx--) { + var tickValue = Dygraph.PREFERRED_LOG_TICK_VALUES[idx]; + var pixel_coord = Math.log(tickValue / a) / Math.log(b / a) * pixels; + var tick = { v: tickValue }; + if (lastDisplayed === null) { + lastDisplayed = { + tickValue : tickValue, + pixel_coord : pixel_coord + }; + } else { + if (Math.abs(pixel_coord - lastDisplayed.pixel_coord) >= pixels_per_tick) { + lastDisplayed = { + tickValue : tickValue, + pixel_coord : pixel_coord + }; + } else { + tick.label = ""; + } + } + ticks.push(tick); + } + // Since we went in backwards order. + ticks.reverse(); + } + } + + // ticks.length won't be 0 if the log scale function finds values to insert. + if (ticks.length === 0) { + // Basic idea: + // Try labels every 1, 2, 5, 10, 20, 50, 100, etc. + // Calculate the resulting tick spacing (i.e. this.height_ / nTicks). + // The first spacing greater than pixelsPerYLabel is what we use. + // TODO(danvk): version that works on a log scale. + var kmg2 = opts("labelsKMG2"); + var mults, base; + if (kmg2) { + mults = [1, 2, 4, 8, 16, 32, 64, 128, 256]; + base = 16; + } else { + mults = [1, 2, 5, 10, 20, 50, 100]; + base = 10; + } + + // Get the maximum number of permitted ticks based on the + // graph's pixel size and pixels_per_tick setting. + var max_ticks = Math.ceil(pixels / pixels_per_tick); + + // Now calculate the data unit equivalent of this tick spacing. + // Use abs() since graphs may have a reversed Y axis. + var units_per_tick = Math.abs(b - a) / max_ticks; + + // Based on this, get a starting scale which is the largest + // integer power of the chosen base (10 or 16) that still remains + // below the requested pixels_per_tick spacing. + var base_power = Math.floor(Math.log(units_per_tick) / Math.log(base)); + var base_scale = Math.pow(base, base_power); + + // Now try multiples of the starting scale until we find one + // that results in tick marks spaced sufficiently far apart. + // The "mults" array should cover the range 1 .. base^2 to + // adjust for rounding and edge effects. + var scale, low_val, high_val, spacing; + for (j = 0; j < mults.length; j++) { + scale = base_scale * mults[j]; + low_val = Math.floor(a / scale) * scale; + high_val = Math.ceil(b / scale) * scale; + nTicks = Math.abs(high_val - low_val) / scale; + spacing = pixels / nTicks; + if (spacing > pixels_per_tick) break; + } + + // Construct the set of ticks. + // Allow reverse y-axis if it's explicitly requested. + if (low_val > high_val) scale *= -1; + for (i = 0; i < nTicks; i++) { + tickV = low_val + i * scale; + ticks.push( {v: tickV} ); + } + } + } + + var formatter = /**@type{AxisLabelFormatter}*/(opts('axisLabelFormatter')); + + // Add labels to the ticks. + for (i = 0; i < ticks.length; i++) { + if (ticks[i].label !== undefined) continue; // Use current label. + // TODO(danvk): set granularity to something appropriate here. + ticks[i].label = formatter(ticks[i].v, 0, opts, dygraph); + } + + return ticks; +}; + + +/** @type {Dygraph.Ticker} */ +Dygraph.dateTicker = function(a, b, pixels, opts, dygraph, vals) { + var chosen = Dygraph.pickDateTickGranularity(a, b, pixels, opts); + + if (chosen >= 0) { + return Dygraph.getDateAxis(a, b, chosen, opts, dygraph); + } else { + // this can happen if self.width_ is zero. + return []; + } +}; + +// Time granularity enumeration +// TODO(danvk): make this an @enum +Dygraph.SECONDLY = 0; +Dygraph.TWO_SECONDLY = 1; +Dygraph.FIVE_SECONDLY = 2; +Dygraph.TEN_SECONDLY = 3; +Dygraph.THIRTY_SECONDLY = 4; +Dygraph.MINUTELY = 5; +Dygraph.TWO_MINUTELY = 6; +Dygraph.FIVE_MINUTELY = 7; +Dygraph.TEN_MINUTELY = 8; +Dygraph.THIRTY_MINUTELY = 9; +Dygraph.HOURLY = 10; +Dygraph.TWO_HOURLY = 11; +Dygraph.SIX_HOURLY = 12; +Dygraph.DAILY = 13; +Dygraph.WEEKLY = 14; +Dygraph.MONTHLY = 15; +Dygraph.QUARTERLY = 16; +Dygraph.BIANNUAL = 17; +Dygraph.ANNUAL = 18; +Dygraph.DECADAL = 19; +Dygraph.CENTENNIAL = 20; +Dygraph.NUM_GRANULARITIES = 21; + +/** @type {Array.<number>} */ +Dygraph.SHORT_SPACINGS = []; +Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY] = 1000 * 1; +Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY] = 1000 * 2; +Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY] = 1000 * 5; +Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY] = 1000 * 10; +Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY] = 1000 * 30; +Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY] = 1000 * 60; +Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY] = 1000 * 60 * 2; +Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY] = 1000 * 60 * 5; +Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY] = 1000 * 60 * 10; +Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY] = 1000 * 60 * 30; +Dygraph.SHORT_SPACINGS[Dygraph.HOURLY] = 1000 * 3600; +Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY] = 1000 * 3600 * 2; +Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY] = 1000 * 3600 * 6; +Dygraph.SHORT_SPACINGS[Dygraph.DAILY] = 1000 * 86400; +Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY] = 1000 * 604800; + +/** + * A collection of objects specifying where it is acceptable to place tick + * marks for granularities larger than WEEKLY. + * 'months' is an array of month indexes on which to place tick marks. + * 'year_mod' ticks are placed when year % year_mod = 0. + * @type {Array.<Object>} + */ +Dygraph.LONG_TICK_PLACEMENTS = []; +Dygraph.LONG_TICK_PLACEMENTS[Dygraph.MONTHLY] = { + months : [0,1,2,3,4,5,6,7,8,9,10,11], + year_mod : 1 +}; +Dygraph.LONG_TICK_PLACEMENTS[Dygraph.QUARTERLY] = { + months: [0,3,6,9], + year_mod: 1 +}; +Dygraph.LONG_TICK_PLACEMENTS[Dygraph.BIANNUAL] = { + months: [0,6], + year_mod: 1 +}; +Dygraph.LONG_TICK_PLACEMENTS[Dygraph.ANNUAL] = { + months: [0], + year_mod: 1 +}; +Dygraph.LONG_TICK_PLACEMENTS[Dygraph.DECADAL] = { + months: [0], + year_mod: 10 +}; +Dygraph.LONG_TICK_PLACEMENTS[Dygraph.CENTENNIAL] = { + months: [0], + year_mod: 100 +}; + +/** + * This is a list of human-friendly values at which to show tick marks on a log + * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so: + * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ... + * NOTE: this assumes that Dygraph.LOG_SCALE = 10. + * @type {Array.<number>} + */ +Dygraph.PREFERRED_LOG_TICK_VALUES = function() { + var vals = []; + for (var power = -39; power <= 39; power++) { + var range = Math.pow(10, power); + for (var mult = 1; mult <= 9; mult++) { + var val = range * mult; + vals.push(val); + } + } + return vals; +}(); + +/** + * Determine the correct granularity of ticks on a date axis. + * + * @param {number} a Left edge of the chart (ms) + * @param {number} b Right edge of the chart (ms) + * @param {number} pixels Size of the chart in the relevant dimension (width). + * @param {function(string):*} opts Function mapping from option name -> + * value. + * @return {number} The appropriate axis granularity for this chart. See the + * enumeration of possible values in dygraph-tickers.js. + */ +Dygraph.pickDateTickGranularity = function(a, b, pixels, opts) { + var pixels_per_tick = /** @type{number} */(opts('pixelsPerLabel')); + for (var i = 0; i < Dygraph.NUM_GRANULARITIES; i++) { + var num_ticks = Dygraph.numDateTicks(a, b, i); + if (pixels / num_ticks >= pixels_per_tick) { + return i; + } + } + return -1; +}; + +/** + * @param {number} start_time + * @param {number} end_time + * @param {number} granularity (one of the granularities enumerated above) + * @return {number} Number of ticks that would result. + */ +Dygraph.numDateTicks = function(start_time, end_time, granularity) { + if (granularity < Dygraph.MONTHLY) { + // Generate one tick mark for every fixed interval of time. + var spacing = Dygraph.SHORT_SPACINGS[granularity]; + return Math.floor(0.5 + 1.0 * (end_time - start_time) / spacing); + } else { + var tickPlacement = Dygraph.LONG_TICK_PLACEMENTS[granularity]; + + var msInYear = 365.2524 * 24 * 3600 * 1000; + var num_years = 1.0 * (end_time - start_time) / msInYear; + return Math.floor(0.5 + 1.0 * num_years * tickPlacement.months.length / tickPlacement.year_mod); + } +}; + +/** + * @param {number} start_time + * @param {number} end_time + * @param {number} granularity (one of the granularities enumerated above) + * @param {function(string):*} opts Function mapping from option name -> value. + * @param {Dygraph=} dg + * @return {!Dygraph.TickList} + */ +Dygraph.getDateAxis = function(start_time, end_time, granularity, opts, dg) { + var formatter = /** @type{AxisLabelFormatter} */( + opts("axisLabelFormatter")); + var ticks = []; + var t; + + if (granularity < Dygraph.MONTHLY) { + // Generate one tick mark for every fixed interval of time. + var spacing = Dygraph.SHORT_SPACINGS[granularity]; + + // Find a time less than start_time which occurs on a "nice" time boundary + // for this granularity. + var g = spacing / 1000; + var d = new Date(start_time); + Dygraph.setDateSameTZ(d, {ms: 0}); + + var x; + if (g <= 60) { // seconds + x = d.getSeconds(); + Dygraph.setDateSameTZ(d, {s: x - x % g}); + } else { + Dygraph.setDateSameTZ(d, {s: 0}); + g /= 60; + if (g <= 60) { // minutes + x = d.getMinutes(); + Dygraph.setDateSameTZ(d, {m: x - x % g}); + } else { + Dygraph.setDateSameTZ(d, {m: 0}); + g /= 60; + + if (g <= 24) { // days + x = d.getHours(); + d.setHours(x - x % g); + } else { + d.setHours(0); + g /= 24; + + if (g == 7) { // one week + d.setDate(d.getDate() - d.getDay()); + } + } + } + } + start_time = d.getTime(); + + // For spacings coarser than two-hourly, we want to ignore daylight + // savings transitions to get consistent ticks. For finer-grained ticks, + // it's essential to show the DST transition in all its messiness. + var start_offset_min = new Date(start_time).getTimezoneOffset(); + var check_dst = (spacing >= Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]); + + for (t = start_time; t <= end_time; t += spacing) { + d = new Date(t); + + // This ensures that we stay on the same hourly "rhythm" across + // daylight savings transitions. Without this, the ticks could get off + // by an hour. See tests/daylight-savings.html or issue 147. + if (check_dst && d.getTimezoneOffset() != start_offset_min) { + var delta_min = d.getTimezoneOffset() - start_offset_min; + t += delta_min * 60 * 1000; + d = new Date(t); + start_offset_min = d.getTimezoneOffset(); + + // Check whether we've backed into the previous timezone again. + // This can happen during a "spring forward" transition. In this case, + // it's best to skip this tick altogether (we may be shooting for a + // non-existent time like the 2AM that's skipped) and go to the next + // one. + if (new Date(t + spacing).getTimezoneOffset() != start_offset_min) { + t += spacing; + d = new Date(t); + start_offset_min = d.getTimezoneOffset(); + } + } + + ticks.push({ v:t, + label: formatter(d, granularity, opts, dg) + }); + } + } else { + // Display a tick mark on the first of a set of months of each year. + // Years get a tick mark iff y % year_mod == 0. This is useful for + // displaying a tick mark once every 10 years, say, on long time scales. + var months; + var year_mod = 1; // e.g. to only print one point every 10 years. + + if (granularity < Dygraph.NUM_GRANULARITIES) { + months = Dygraph.LONG_TICK_PLACEMENTS[granularity].months; + year_mod = Dygraph.LONG_TICK_PLACEMENTS[granularity].year_mod; + } else { + Dygraph.warn("Span of dates is too long"); + } + + var start_year = new Date(start_time).getFullYear(); + var end_year = new Date(end_time).getFullYear(); + var zeropad = Dygraph.zeropad; + for (var i = start_year; i <= end_year; i++) { + if (i % year_mod !== 0) continue; + for (var j = 0; j < months.length; j++) { + var date_str = i + "/" + zeropad(1 + months[j]) + "/01"; + t = Dygraph.dateStrToMillis(date_str); + if (t < start_time || t > end_time) continue; + ticks.push({ v:t, + label: formatter(new Date(t), granularity, opts, dg) + }); + } + } + } + + return ticks; +}; + +// These are set here so that this file can be included after dygraph.js +// or independently. +if (Dygraph && + Dygraph.DEFAULT_ATTRS && + Dygraph.DEFAULT_ATTRS['axes'] && + Dygraph.DEFAULT_ATTRS['axes']['x'] && + Dygraph.DEFAULT_ATTRS['axes']['y'] && + Dygraph.DEFAULT_ATTRS['axes']['y2']) { + Dygraph.DEFAULT_ATTRS['axes']['x']['ticker'] = Dygraph.dateTicker; + Dygraph.DEFAULT_ATTRS['axes']['y']['ticker'] = Dygraph.numericTicks; + Dygraph.DEFAULT_ATTRS['axes']['y2']['ticker'] = Dygraph.numericTicks; +} diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-utils.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-utils.js new file mode 100644 index 00000000..1ec1795d --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-utils.js @@ -0,0 +1,1305 @@ +/** + * @license + * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview This file contains utility functions used by dygraphs. These + * are typically static (i.e. not related to any particular dygraph). Examples + * include date/time formatting functions, basic algorithms (e.g. binary + * search) and generic DOM-manipulation functions. + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false, G_vmlCanvasManager:false, Node:false, printStackTrace: false */ +"use strict"; + +Dygraph.LOG_SCALE = 10; +Dygraph.LN_TEN = Math.log(Dygraph.LOG_SCALE); + +/** + * @private + * @param {number} x + * @return {number} + */ +Dygraph.log10 = function(x) { + return Math.log(x) / Dygraph.LN_TEN; +}; + +// Various logging levels. +Dygraph.DEBUG = 1; +Dygraph.INFO = 2; +Dygraph.WARNING = 3; +Dygraph.ERROR = 3; + +// Set this to log stack traces on warnings, etc. +// This requires stacktrace.js, which is up to you to provide. +// A copy can be found in the dygraphs repo, or at +// https://github.com/eriwen/javascript-stacktrace +Dygraph.LOG_STACK_TRACES = false; + +/** A dotted line stroke pattern. */ +Dygraph.DOTTED_LINE = [2, 2]; +/** A dashed line stroke pattern. */ +Dygraph.DASHED_LINE = [7, 3]; +/** A dot dash stroke pattern. */ +Dygraph.DOT_DASH_LINE = [7, 2, 2, 2]; + +/** + * Log an error on the JS console at the given severity. + * @param {number} severity One of Dygraph.{DEBUG,INFO,WARNING,ERROR} + * @param {string} message The message to log. + * @private + */ +Dygraph.log = function(severity, message) { + var st; + if (typeof(printStackTrace) != 'undefined') { + try { + // Remove uninteresting bits: logging functions and paths. + st = printStackTrace({guess:false}); + while (st[0].indexOf("stacktrace") != -1) { + st.splice(0, 1); + } + + st.splice(0, 2); + for (var i = 0; i < st.length; i++) { + st[i] = st[i].replace(/\([^)]*\/(.*)\)/, '@$1') + .replace(/\@.*\/([^\/]*)/, '@$1') + .replace('[object Object].', ''); + } + var top_msg = st.splice(0, 1)[0]; + message += ' (' + top_msg.replace(/^.*@ ?/, '') + ')'; + } catch(e) { + // Oh well, it was worth a shot! + } + } + + if (typeof(window.console) != 'undefined') { + // In older versions of Firefox, only console.log is defined. + var console = window.console; + var log = function(console, method, msg) { + if (method && typeof(method) == 'function') { + method.call(console, msg); + } else { + console.log(msg); + } + }; + + switch (severity) { + case Dygraph.DEBUG: + log(console, console.debug, 'dygraphs: ' + message); + break; + case Dygraph.INFO: + log(console, console.info, 'dygraphs: ' + message); + break; + case Dygraph.WARNING: + log(console, console.warn, 'dygraphs: ' + message); + break; + case Dygraph.ERROR: + log(console, console.error, 'dygraphs: ' + message); + break; + } + } + + if (Dygraph.LOG_STACK_TRACES) { + window.console.log(st.join('\n')); + } +}; + +/** + * @param {string} message + * @private + */ +Dygraph.info = function(message) { + Dygraph.log(Dygraph.INFO, message); +}; +/** + * @param {string} message + * @private + */ +Dygraph.prototype.info = Dygraph.info; + +/** + * @param {string} message + * @private + */ +Dygraph.warn = function(message) { + Dygraph.log(Dygraph.WARNING, message); +}; +/** + * @param {string} message + * @private + */ +Dygraph.prototype.warn = Dygraph.warn; + +/** + * @param {string} message + */ +Dygraph.error = function(message) { + Dygraph.log(Dygraph.ERROR, message); +}; +/** + * @param {string} message + * @private + */ +Dygraph.prototype.error = Dygraph.error; + +/** + * Return the 2d context for a dygraph canvas. + * + * This method is only exposed for the sake of replacing the function in + * automated tests, e.g. + * + * var oldFunc = Dygraph.getContext(); + * Dygraph.getContext = function(canvas) { + * var realContext = oldFunc(canvas); + * return new Proxy(realContext); + * }; + * @param {!HTMLCanvasElement} canvas + * @return {!CanvasRenderingContext2D} + * @private + */ +Dygraph.getContext = function(canvas) { + return /** @type{!CanvasRenderingContext2D}*/(canvas.getContext("2d")); +}; + +/** + * Add an event handler. This smooths a difference between IE and the rest of + * the world. + * @param { !Element } elem The element to add the event to. + * @param { string } type The type of the event, e.g. 'click' or 'mousemove'. + * @param { function(Event):(boolean|undefined) } fn The function to call + * on the event. The function takes one parameter: the event object. + * @private + */ +Dygraph.addEvent = function addEvent(elem, type, fn) { + if (elem.addEventListener) { + elem.addEventListener(type, fn, false); + } else { + elem[type+fn] = function(){fn(window.event);}; + elem.attachEvent('on'+type, elem[type+fn]); + } +}; + +/** + * Add an event handler. This event handler is kept until the graph is + * destroyed with a call to graph.destroy(). + * + * @param { !Element } elem The element to add the event to. + * @param { string } type The type of the event, e.g. 'click' or 'mousemove'. + * @param { function(Event):(boolean|undefined) } fn The function to call + * on the event. The function takes one parameter: the event object. + * @private + */ +Dygraph.prototype.addAndTrackEvent = function(elem, type, fn) { + Dygraph.addEvent(elem, type, fn); + this.registeredEvents_.push({ elem : elem, type : type, fn : fn }); +}; + +/** + * Remove an event handler. This smooths a difference between IE and the rest + * of the world. + * @param {!Element} elem The element to add the event to. + * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. + * @param {function(Event):(boolean|undefined)} fn The function to call + * on the event. The function takes one parameter: the event object. + * @private + */ +Dygraph.removeEvent = function(elem, type, fn) { + if (elem.removeEventListener) { + elem.removeEventListener(type, fn, false); + } else { + try { + elem.detachEvent('on'+type, elem[type+fn]); + } catch(e) { + // We only detach event listeners on a "best effort" basis in IE. See: + // http://stackoverflow.com/questions/2553632/detachevent-not-working-with-named-inline-functions + } + elem[type+fn] = null; + } +}; + +Dygraph.prototype.removeTrackedEvents_ = function() { + if (this.registeredEvents_) { + for (var idx = 0; idx < this.registeredEvents_.length; idx++) { + var reg = this.registeredEvents_[idx]; + Dygraph.removeEvent(reg.elem, reg.type, reg.fn); + } + } + + this.registeredEvents_ = []; +}; + +/** + * Cancels further processing of an event. This is useful to prevent default + * browser actions, e.g. highlighting text on a double-click. + * Based on the article at + * http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel + * @param { !Event } e The event whose normal behavior should be canceled. + * @private + */ +Dygraph.cancelEvent = function(e) { + e = e ? e : window.event; + if (e.stopPropagation) { + e.stopPropagation(); + } + if (e.preventDefault) { + e.preventDefault(); + } + e.cancelBubble = true; + e.cancel = true; + e.returnValue = false; + return false; +}; + +/** + * Convert hsv values to an rgb(r,g,b) string. Taken from MochiKit.Color. This + * is used to generate default series colors which are evenly spaced on the + * color wheel. + * @param { number } hue Range is 0.0-1.0. + * @param { number } saturation Range is 0.0-1.0. + * @param { number } value Range is 0.0-1.0. + * @return { string } "rgb(r,g,b)" where r, g and b range from 0-255. + * @private + */ +Dygraph.hsvToRGB = function (hue, saturation, value) { + var red; + var green; + var blue; + if (saturation === 0) { + red = value; + green = value; + blue = value; + } else { + var i = Math.floor(hue * 6); + var f = (hue * 6) - i; + var p = value * (1 - saturation); + var q = value * (1 - (saturation * f)); + var t = value * (1 - (saturation * (1 - f))); + switch (i) { + case 1: red = q; green = value; blue = p; break; + case 2: red = p; green = value; blue = t; break; + case 3: red = p; green = q; blue = value; break; + case 4: red = t; green = p; blue = value; break; + case 5: red = value; green = p; blue = q; break; + case 6: // fall through + case 0: red = value; green = t; blue = p; break; + } + } + red = Math.floor(255 * red + 0.5); + green = Math.floor(255 * green + 0.5); + blue = Math.floor(255 * blue + 0.5); + return 'rgb(' + red + ',' + green + ',' + blue + ')'; +}; + +// The following functions are from quirksmode.org with a modification for Safari from +// http://blog.firetree.net/2005/07/04/javascript-find-position/ +// http://www.quirksmode.org/js/findpos.html +// ... and modifications to support scrolling divs. + +/** + * Find the x-coordinate of the supplied object relative to the left side + * of the page. + * TODO(danvk): change obj type from Node -> !Node + * @param {Node} obj + * @return {number} + * @private + */ +Dygraph.findPosX = function(obj) { + var curleft = 0; + if(obj.offsetParent) { + var copyObj = obj; + while(1) { + // NOTE: the if statement here is for IE8. + var borderLeft = "0"; + if (window.getComputedStyle) { + borderLeft = window.getComputedStyle(copyObj, null).borderLeft || "0"; + } + curleft += parseInt(borderLeft, 10) ; + curleft += copyObj.offsetLeft; + if(!copyObj.offsetParent) { + break; + } + copyObj = copyObj.offsetParent; + } + } else if(obj.x) { + curleft += obj.x; + } + // This handles the case where the object is inside a scrolled div. + while(obj && obj != document.body) { + curleft -= obj.scrollLeft; + obj = obj.parentNode; + } + return curleft; +}; + +/** + * Find the y-coordinate of the supplied object relative to the top of the + * page. + * TODO(danvk): change obj type from Node -> !Node + * TODO(danvk): consolidate with findPosX and return an {x, y} object. + * @param {Node} obj + * @return {number} + * @private + */ +Dygraph.findPosY = function(obj) { + var curtop = 0; + if(obj.offsetParent) { + var copyObj = obj; + while(1) { + // NOTE: the if statement here is for IE8. + var borderTop = "0"; + if (window.getComputedStyle) { + borderTop = window.getComputedStyle(copyObj, null).borderTop || "0"; + } + curtop += parseInt(borderTop, 10) ; + curtop += copyObj.offsetTop; + if(!copyObj.offsetParent) { + break; + } + copyObj = copyObj.offsetParent; + } + } else if(obj.y) { + curtop += obj.y; + } + // This handles the case where the object is inside a scrolled div. + while(obj && obj != document.body) { + curtop -= obj.scrollTop; + obj = obj.parentNode; + } + return curtop; +}; + +/** + * Returns the x-coordinate of the event in a coordinate system where the + * top-left corner of the page (not the window) is (0,0). + * Taken from MochiKit.Signal + * @param {!Event} e + * @return {number} + * @private + */ +Dygraph.pageX = function(e) { + if (e.pageX) { + return (!e.pageX || e.pageX < 0) ? 0 : e.pageX; + } else { + var de = document.documentElement; + var b = document.body; + return e.clientX + + (de.scrollLeft || b.scrollLeft) - + (de.clientLeft || 0); + } +}; + +/** + * Returns the y-coordinate of the event in a coordinate system where the + * top-left corner of the page (not the window) is (0,0). + * Taken from MochiKit.Signal + * @param {!Event} e + * @return {number} + * @private + */ +Dygraph.pageY = function(e) { + if (e.pageY) { + return (!e.pageY || e.pageY < 0) ? 0 : e.pageY; + } else { + var de = document.documentElement; + var b = document.body; + return e.clientY + + (de.scrollTop || b.scrollTop) - + (de.clientTop || 0); + } +}; + +/** + * This returns true unless the parameter is 0, null, undefined or NaN. + * TODO(danvk): rename this function to something like 'isNonZeroNan'. + * + * @param {number} x The number to consider. + * @return {boolean} Whether the number is zero or NaN. + * @private + */ +Dygraph.isOK = function(x) { + return !!x && !isNaN(x); +}; + +/** + * @param { {x:?number,y:?number,yval:?number} } p The point to consider, valid + * points are {x, y} objects + * @param { boolean } allowNaNY Treat point with y=NaN as valid + * @return { boolean } Whether the point has numeric x and y. + * @private + */ +Dygraph.isValidPoint = function(p, allowNaNY) { + if (!p) return false; // null or undefined object + if (p.yval === null) return false; // missing point + if (p.x === null || p.x === undefined) return false; + if (p.y === null || p.y === undefined) return false; + if (isNaN(p.x) || (!allowNaNY && isNaN(p.y))) return false; + return true; +}; + +/** + * Number formatting function which mimicks the behavior of %g in printf, i.e. + * either exponential or fixed format (without trailing 0s) is used depending on + * the length of the generated string. The advantage of this format is that + * there is a predictable upper bound on the resulting string length, + * significant figures are not dropped, and normal numbers are not displayed in + * exponential notation. + * + * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g. + * It creates strings which are too long for absolute values between 10^-4 and + * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for + * output examples. + * + * @param {number} x The number to format + * @param {number=} opt_precision The precision to use, default 2. + * @return {string} A string formatted like %g in printf. The max generated + * string length should be precision + 6 (e.g 1.123e+300). + */ +Dygraph.floatFormat = function(x, opt_precision) { + // Avoid invalid precision values; [1, 21] is the valid range. + var p = Math.min(Math.max(1, opt_precision || 2), 21); + + // This is deceptively simple. The actual algorithm comes from: + // + // Max allowed length = p + 4 + // where 4 comes from 'e+n' and '.'. + // + // Length of fixed format = 2 + y + p + // where 2 comes from '0.' and y = # of leading zeroes. + // + // Equating the two and solving for y yields y = 2, or 0.00xxxx which is + // 1.0e-3. + // + // Since the behavior of toPrecision() is identical for larger numbers, we + // don't have to worry about the other bound. + // + // Finally, the argument for toExponential() is the number of trailing digits, + // so we take off 1 for the value before the '.'. + return (Math.abs(x) < 1.0e-3 && x !== 0.0) ? + x.toExponential(p - 1) : x.toPrecision(p); +}; + +/** + * Converts '9' to '09' (useful for dates) + * @param {number} x + * @return {string} + * @private + */ +Dygraph.zeropad = function(x) { + if (x < 10) return "0" + x; else return "" + x; +}; + +/** + * Return a string version of the hours, minutes and seconds portion of a date. + * + * @param {number} date The JavaScript date (ms since epoch) + * @return {string} A time of the form "HH:MM:SS" + * @private + */ +Dygraph.hmsString_ = function(date) { + var zeropad = Dygraph.zeropad; + var d = new Date(date); + if (d.getSeconds()) { + return zeropad(d.getHours()) + ":" + + zeropad(d.getMinutes()) + ":" + + zeropad(d.getSeconds()); + } else { + return zeropad(d.getHours()) + ":" + zeropad(d.getMinutes()); + } +}; + +/** + * Round a number to the specified number of digits past the decimal point. + * @param {number} num The number to round + * @param {number} places The number of decimals to which to round + * @return {number} The rounded number + * @private + */ +Dygraph.round_ = function(num, places) { + var shift = Math.pow(10, places); + return Math.round(num * shift)/shift; +}; + +/** + * Implementation of binary search over an array. + * Currently does not work when val is outside the range of arry's values. + * @param {number} val the value to search for + * @param {Array.<number>} arry is the value over which to search + * @param {number} abs If abs > 0, find the lowest entry greater than val + * If abs < 0, find the highest entry less than val. + * If abs == 0, find the entry that equals val. + * @param {number=} low The first index in arry to consider (optional) + * @param {number=} high The last index in arry to consider (optional) + * @return {number} Index of the element, or -1 if it isn't found. + * @private + */ +Dygraph.binarySearch = function(val, arry, abs, low, high) { + if (low === null || low === undefined || + high === null || high === undefined) { + low = 0; + high = arry.length - 1; + } + if (low > high) { + return -1; + } + if (abs === null || abs === undefined) { + abs = 0; + } + var validIndex = function(idx) { + return idx >= 0 && idx < arry.length; + }; + var mid = parseInt((low + high) / 2, 10); + var element = arry[mid]; + var idx; + if (element == val) { + return mid; + } else if (element > val) { + if (abs > 0) { + // Accept if element > val, but also if prior element < val. + idx = mid - 1; + if (validIndex(idx) && arry[idx] < val) { + return mid; + } + } + return Dygraph.binarySearch(val, arry, abs, low, mid - 1); + } else if (element < val) { + if (abs < 0) { + // Accept if element < val, but also if prior element > val. + idx = mid + 1; + if (validIndex(idx) && arry[idx] > val) { + return mid; + } + } + return Dygraph.binarySearch(val, arry, abs, mid + 1, high); + } + return -1; // can't actually happen, but makes closure compiler happy +}; + +/** + * Parses a date, returning the number of milliseconds since epoch. This can be + * passed in as an xValueParser in the Dygraph constructor. + * TODO(danvk): enumerate formats that this understands. + * + * @param {string} dateStr A date in a variety of possible string formats. + * @return {number} Milliseconds since epoch. + * @private + */ +Dygraph.dateParser = function(dateStr) { + var dateStrSlashed; + var d; + + // Let the system try the format first, with one caveat: + // YYYY-MM-DD[ HH:MM:SS] is interpreted as UTC by a variety of browsers. + // dygraphs displays dates in local time, so this will result in surprising + // inconsistencies. But if you specify "T" or "Z" (i.e. YYYY-MM-DDTHH:MM:SS), + // then you probably know what you're doing, so we'll let you go ahead. + // Issue: http://code.google.com/p/dygraphs/issues/detail?id=255 + if (dateStr.search("-") == -1 || + dateStr.search("T") != -1 || dateStr.search("Z") != -1) { + d = Dygraph.dateStrToMillis(dateStr); + if (d && !isNaN(d)) return d; + } + + if (dateStr.search("-") != -1) { // e.g. '2009-7-12' or '2009-07-12' + dateStrSlashed = dateStr.replace("-", "/", "g"); + while (dateStrSlashed.search("-") != -1) { + dateStrSlashed = dateStrSlashed.replace("-", "/"); + } + d = Dygraph.dateStrToMillis(dateStrSlashed); + } else if (dateStr.length == 8) { // e.g. '20090712' + // TODO(danvk): remove support for this format. It's confusing. + dateStrSlashed = dateStr.substr(0,4) + "/" + dateStr.substr(4,2) + "/" + + dateStr.substr(6,2); + d = Dygraph.dateStrToMillis(dateStrSlashed); + } else { + // Any format that Date.parse will accept, e.g. "2009/07/12" or + // "2009/07/12 12:34:56" + d = Dygraph.dateStrToMillis(dateStr); + } + + if (!d || isNaN(d)) { + Dygraph.error("Couldn't parse " + dateStr + " as a date"); + } + return d; +}; + +/** + * This is identical to JavaScript's built-in Date.parse() method, except that + * it doesn't get replaced with an incompatible method by aggressive JS + * libraries like MooTools or Joomla. + * @param {string} str The date string, e.g. "2011/05/06" + * @return {number} millis since epoch + * @private + */ +Dygraph.dateStrToMillis = function(str) { + return new Date(str).getTime(); +}; + +// These functions are all based on MochiKit. +/** + * Copies all the properties from o to self. + * + * @param {!Object} self + * @param {!Object} o + * @return {!Object} + */ +Dygraph.update = function(self, o) { + if (typeof(o) != 'undefined' && o !== null) { + for (var k in o) { + if (o.hasOwnProperty(k)) { + self[k] = o[k]; + } + } + } + return self; +}; + +/** + * Copies all the properties from o to self. + * + * @param {!Object} self + * @param {!Object} o + * @return {!Object} + * @private + */ +Dygraph.updateDeep = function (self, o) { + // Taken from http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object + function isNode(o) { + return ( + typeof Node === "object" ? o instanceof Node : + typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string" + ); + } + + if (typeof(o) != 'undefined' && o !== null) { + for (var k in o) { + if (o.hasOwnProperty(k)) { + if (o[k] === null) { + self[k] = null; + } else if (Dygraph.isArrayLike(o[k])) { + self[k] = o[k].slice(); + } else if (isNode(o[k])) { + // DOM objects are shallowly-copied. + self[k] = o[k]; + } else if (typeof(o[k]) == 'object') { + if (typeof(self[k]) != 'object' || self[k] === null) { + self[k] = {}; + } + Dygraph.updateDeep(self[k], o[k]); + } else { + self[k] = o[k]; + } + } + } + } + return self; +}; + +/** + * @param {Object} o + * @return {boolean} + * @private + */ +Dygraph.isArrayLike = function(o) { + var typ = typeof(o); + if ( + (typ != 'object' && !(typ == 'function' && + typeof(o.item) == 'function')) || + o === null || + typeof(o.length) != 'number' || + o.nodeType === 3 + ) { + return false; + } + return true; +}; + +/** + * @param {Object} o + * @return {boolean} + * @private + */ +Dygraph.isDateLike = function (o) { + if (typeof(o) != "object" || o === null || + typeof(o.getTime) != 'function') { + return false; + } + return true; +}; + +/** + * Note: this only seems to work for arrays. + * @param {!Array} o + * @return {!Array} + * @private + */ +Dygraph.clone = function(o) { + // TODO(danvk): figure out how MochiKit's version works + var r = []; + for (var i = 0; i < o.length; i++) { + if (Dygraph.isArrayLike(o[i])) { + r.push(Dygraph.clone(o[i])); + } else { + r.push(o[i]); + } + } + return r; +}; + +/** + * Create a new canvas element. This is more complex than a simple + * document.createElement("canvas") because of IE and excanvas. + * + * @return {!HTMLCanvasElement} + * @private + */ +Dygraph.createCanvas = function() { + var canvas = document.createElement("canvas"); + + var isIE = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (isIE && (typeof(G_vmlCanvasManager) != 'undefined')) { + canvas = G_vmlCanvasManager.initElement( + /**@type{!HTMLCanvasElement}*/(canvas)); + } + + return canvas; +}; + +/** + * Checks whether the user is on an Android browser. + * Android does not fully support the <canvas> tag, e.g. w/r/t/ clipping. + * @return {boolean} + * @private + */ +Dygraph.isAndroid = function() { + return (/Android/).test(navigator.userAgent); +}; + + +/** + * TODO(danvk): use @template here when it's better supported for classes. + * @param {!Array} array + * @param {number} start + * @param {number} length + * @param {function(!Array,?):boolean=} predicate + * @constructor + */ +Dygraph.Iterator = function(array, start, length, predicate) { + start = start || 0; + length = length || array.length; + this.hasNext = true; // Use to identify if there's another element. + this.peek = null; // Use for look-ahead + this.start_ = start; + this.array_ = array; + this.predicate_ = predicate; + this.end_ = Math.min(array.length, start + length); + this.nextIdx_ = start - 1; // use -1 so initial advance works. + this.next(); // ignoring result. +}; + +/** + * @return {Object} + */ +Dygraph.Iterator.prototype.next = function() { + if (!this.hasNext) { + return null; + } + var obj = this.peek; + + var nextIdx = this.nextIdx_ + 1; + var found = false; + while (nextIdx < this.end_) { + if (!this.predicate_ || this.predicate_(this.array_, nextIdx)) { + this.peek = this.array_[nextIdx]; + found = true; + break; + } + nextIdx++; + } + this.nextIdx_ = nextIdx; + if (!found) { + this.hasNext = false; + this.peek = null; + } + return obj; +}; + +/** + * Returns a new iterator over array, between indexes start and + * start + length, and only returns entries that pass the accept function + * + * @param {!Array} array the array to iterate over. + * @param {number} start the first index to iterate over, 0 if absent. + * @param {number} length the number of elements in the array to iterate over. + * This, along with start, defines a slice of the array, and so length + * doesn't imply the number of elements in the iterator when accept doesn't + * always accept all values. array.length when absent. + * @param {function(?):boolean=} opt_predicate a function that takes + * parameters array and idx, which returns true when the element should be + * returned. If omitted, all elements are accepted. + * @private + */ +Dygraph.createIterator = function(array, start, length, opt_predicate) { + return new Dygraph.Iterator(array, start, length, opt_predicate); +}; + +// Shim layer with setTimeout fallback. +// From: http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// Should be called with the window context: +// Dygraph.requestAnimFrame.call(window, function() {}) +Dygraph.requestAnimFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + }; +})(); + +/** + * Call a function at most maxFrames times at an attempted interval of + * framePeriodInMillis, then call a cleanup function once. repeatFn is called + * once immediately, then at most (maxFrames - 1) times asynchronously. If + * maxFrames==1, then cleanup_fn() is also called synchronously. This function + * is used to sequence animation. + * @param {function(number)} repeatFn Called repeatedly -- takes the frame + * number (from 0 to maxFrames-1) as an argument. + * @param {number} maxFrames The max number of times to call repeatFn + * @param {number} framePeriodInMillis Max requested time between frames. + * @param {function()} cleanupFn A function to call after all repeatFn calls. + * @private + */ +Dygraph.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis, + cleanupFn) { + var frameNumber = 0; + var previousFrameNumber; + var startTime = new Date().getTime(); + repeatFn(frameNumber); + if (maxFrames == 1) { + cleanupFn(); + return; + } + var maxFrameArg = maxFrames - 1; + + (function loop() { + if (frameNumber >= maxFrames) return; + Dygraph.requestAnimFrame.call(window, function() { + // Determine which frame to draw based on the delay so far. Will skip + // frames if necessary. + var currentTime = new Date().getTime(); + var delayInMillis = currentTime - startTime; + previousFrameNumber = frameNumber; + frameNumber = Math.floor(delayInMillis / framePeriodInMillis); + var frameDelta = frameNumber - previousFrameNumber; + // If we predict that the subsequent repeatFn call will overshoot our + // total frame target, so our last call will cause a stutter, then jump to + // the last call immediately. If we're going to cause a stutter, better + // to do it faster than slower. + var predictOvershootStutter = (frameNumber + frameDelta) > maxFrameArg; + if (predictOvershootStutter || (frameNumber >= maxFrameArg)) { + repeatFn(maxFrameArg); // Ensure final call with maxFrameArg. + cleanupFn(); + } else { + if (frameDelta !== 0) { // Don't call repeatFn with duplicate frames. + repeatFn(frameNumber); + } + loop(); + } + }); + })(); +}; + +/** + * This function will scan the option list and determine if they + * require us to recalculate the pixel positions of each point. + * @param {!Array.<string>} labels a list of options to check. + * @param {!Object} attrs + * @return {boolean} true if the graph needs new points else false. + * @private + */ +Dygraph.isPixelChangingOptionList = function(labels, attrs) { + // A whitelist of options that do not change pixel positions. + var pixelSafeOptions = { + 'annotationClickHandler': true, + 'annotationDblClickHandler': true, + 'annotationMouseOutHandler': true, + 'annotationMouseOverHandler': true, + 'axisLabelColor': true, + 'axisLineColor': true, + 'axisLineWidth': true, + 'clickCallback': true, + 'digitsAfterDecimal': true, + 'drawCallback': true, + 'drawHighlightPointCallback': true, + 'drawPoints': true, + 'drawPointCallback': true, + 'drawXGrid': true, + 'drawYGrid': true, + 'fillAlpha': true, + 'gridLineColor': true, + 'gridLineWidth': true, + 'hideOverlayOnMouseOut': true, + 'highlightCallback': true, + 'highlightCircleSize': true, + 'interactionModel': true, + 'isZoomedIgnoreProgrammaticZoom': true, + 'labelsDiv': true, + 'labelsDivStyles': true, + 'labelsDivWidth': true, + 'labelsKMB': true, + 'labelsKMG2': true, + 'labelsSeparateLines': true, + 'labelsShowZeroValues': true, + 'legend': true, + 'maxNumberWidth': true, + 'panEdgeFraction': true, + 'pixelsPerYLabel': true, + 'pointClickCallback': true, + 'pointSize': true, + 'rangeSelectorPlotFillColor': true, + 'rangeSelectorPlotStrokeColor': true, + 'showLabelsOnHighlight': true, + 'showRoller': true, + 'sigFigs': true, + 'strokeWidth': true, + 'underlayCallback': true, + 'unhighlightCallback': true, + 'xAxisLabelFormatter': true, + 'xTicker': true, + 'xValueFormatter': true, + 'yAxisLabelFormatter': true, + 'yValueFormatter': true, + 'zoomCallback': true + }; + + // Assume that we do not require new points. + // This will change to true if we actually do need new points. + var requiresNewPoints = false; + + // Create a dictionary of series names for faster lookup. + // If there are no labels, then the dictionary stays empty. + var seriesNamesDictionary = { }; + if (labels) { + for (var i = 1; i < labels.length; i++) { + seriesNamesDictionary[labels[i]] = true; + } + } + + // Iterate through the list of updated options. + for (var property in attrs) { + // Break early if we already know we need new points from a previous option. + if (requiresNewPoints) { + break; + } + if (attrs.hasOwnProperty(property)) { + // Find out of this field is actually a series specific options list. + if (seriesNamesDictionary[property]) { + // This property value is a list of options for this series. + // If any of these sub properties are not pixel safe, set the flag. + for (var subProperty in attrs[property]) { + // Break early if we already know we need new points from a previous option. + if (requiresNewPoints) { + break; + } + if (attrs[property].hasOwnProperty(subProperty) && !pixelSafeOptions[subProperty]) { + requiresNewPoints = true; + } + } + // If this was not a series specific option list, check if its a pixel changing property. + } else if (!pixelSafeOptions[property]) { + requiresNewPoints = true; + } + } + } + + return requiresNewPoints; +}; + +/** + * Compares two arrays to see if they are equal. If either parameter is not an + * array it will return false. Does a shallow compare + * Dygraph.compareArrays([[1,2], [3, 4]], [[1,2], [3,4]]) === false. + * @param {!Array.<T>} array1 first array + * @param {!Array.<T>} array2 second array + * @return {boolean} True if both parameters are arrays, and contents are equal. + * @template T + */ +Dygraph.compareArrays = function(array1, array2) { + if (!Dygraph.isArrayLike(array1) || !Dygraph.isArrayLike(array2)) { + return false; + } + if (array1.length !== array2.length) { + return false; + } + for (var i = 0; i < array1.length; i++) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; +}; + +/** + * @param {!CanvasRenderingContext2D} ctx the canvas context + * @param {number} sides the number of sides in the shape. + * @param {number} radius the radius of the image. + * @param {number} cx center x coordate + * @param {number} cy center y coordinate + * @param {number=} rotationRadians the shift of the initial angle, in radians. + * @param {number=} delta the angle shift for each line. If missing, creates a + * regular polygon. + * @private + */ +Dygraph.regularShape_ = function( + ctx, sides, radius, cx, cy, rotationRadians, delta) { + rotationRadians = rotationRadians || 0; + delta = delta || Math.PI * 2 / sides; + + ctx.beginPath(); + var initialAngle = rotationRadians; + var angle = initialAngle; + + var computeCoordinates = function() { + var x = cx + (Math.sin(angle) * radius); + var y = cy + (-Math.cos(angle) * radius); + return [x, y]; + }; + + var initialCoordinates = computeCoordinates(); + var x = initialCoordinates[0]; + var y = initialCoordinates[1]; + ctx.moveTo(x, y); + + for (var idx = 0; idx < sides; idx++) { + angle = (idx == sides - 1) ? initialAngle : (angle + delta); + var coords = computeCoordinates(); + ctx.lineTo(coords[0], coords[1]); + } + ctx.fill(); + ctx.stroke(); +}; + +/** + * TODO(danvk): be more specific on the return type. + * @param {number} sides + * @param {number=} rotationRadians + * @param {number=} delta + * @return {Function} + * @private + */ +Dygraph.shapeFunction_ = function(sides, rotationRadians, delta) { + return function(g, name, ctx, cx, cy, color, radius) { + ctx.strokeStyle = color; + ctx.fillStyle = "white"; + Dygraph.regularShape_(ctx, sides, radius, cx, cy, rotationRadians, delta); + }; +}; + +Dygraph.Circles = { + DEFAULT : function(g, name, ctx, canvasx, canvasy, color, radius) { + ctx.beginPath(); + ctx.fillStyle = color; + ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false); + ctx.fill(); + }, + TRIANGLE : Dygraph.shapeFunction_(3), + SQUARE : Dygraph.shapeFunction_(4, Math.PI / 4), + DIAMOND : Dygraph.shapeFunction_(4), + PENTAGON : Dygraph.shapeFunction_(5), + HEXAGON : Dygraph.shapeFunction_(6), + CIRCLE : function(g, name, ctx, cx, cy, color, radius) { + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.fillStyle = "white"; + ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false); + ctx.fill(); + ctx.stroke(); + }, + STAR : Dygraph.shapeFunction_(5, 0, 4 * Math.PI / 5), + PLUS : function(g, name, ctx, cx, cy, color, radius) { + ctx.strokeStyle = color; + + ctx.beginPath(); + ctx.moveTo(cx + radius, cy); + ctx.lineTo(cx - radius, cy); + ctx.closePath(); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(cx, cy + radius); + ctx.lineTo(cx, cy - radius); + ctx.closePath(); + ctx.stroke(); + }, + EX : function(g, name, ctx, cx, cy, color, radius) { + ctx.strokeStyle = color; + + ctx.beginPath(); + ctx.moveTo(cx + radius, cy + radius); + ctx.lineTo(cx - radius, cy - radius); + ctx.closePath(); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(cx + radius, cy - radius); + ctx.lineTo(cx - radius, cy + radius); + ctx.closePath(); + ctx.stroke(); + } +}; + +/** + * To create a "drag" interaction, you typically register a mousedown event + * handler on the element where the drag begins. In that handler, you register a + * mouseup handler on the window to determine when the mouse is released, + * wherever that release happens. This works well, except when the user releases + * the mouse over an off-domain iframe. In that case, the mouseup event is + * handled by the iframe and never bubbles up to the window handler. + * + * To deal with this issue, we cover iframes with high z-index divs to make sure + * they don't capture mouseup. + * + * Usage: + * element.addEventListener('mousedown', function() { + * var tarper = new Dygraph.IFrameTarp(); + * tarper.cover(); + * var mouseUpHandler = function() { + * ... + * window.removeEventListener(mouseUpHandler); + * tarper.uncover(); + * }; + * window.addEventListener('mouseup', mouseUpHandler); + * }; + * + * @constructor + */ +Dygraph.IFrameTarp = function() { + /** @type {Array.<!HTMLDivElement>} */ + this.tarps = []; +}; + +/** + * Find all the iframes in the document and cover them with high z-index + * transparent divs. + */ +Dygraph.IFrameTarp.prototype.cover = function() { + var iframes = document.getElementsByTagName("iframe"); + for (var i = 0; i < iframes.length; i++) { + var iframe = iframes[i]; + var x = Dygraph.findPosX(iframe), + y = Dygraph.findPosY(iframe), + width = iframe.offsetWidth, + height = iframe.offsetHeight; + + var div = document.createElement("div"); + div.style.position = "absolute"; + div.style.left = x + 'px'; + div.style.top = y + 'px'; + div.style.width = width + 'px'; + div.style.height = height + 'px'; + div.style.zIndex = 999; + document.body.appendChild(div); + this.tarps.push(div); + } +}; + +/** + * Remove all the iframe covers. You should call this in a mouseup handler. + */ +Dygraph.IFrameTarp.prototype.uncover = function() { + for (var i = 0; i < this.tarps.length; i++) { + this.tarps[i].parentNode.removeChild(this.tarps[i]); + } + this.tarps = []; +}; + +/** + * Determine whether |data| is delimited by CR, CRLF, LF, LFCR. + * @param {string} data + * @return {?string} the delimiter that was detected (or null on failure). + */ +Dygraph.detectLineDelimiter = function(data) { + for (var i = 0; i < data.length; i++) { + var code = data.charAt(i); + if (code === '\r') { + // Might actually be "\r\n". + if (((i + 1) < data.length) && (data.charAt(i + 1) === '\n')) { + return '\r\n'; + } + return code; + } + if (code === '\n') { + // Might actually be "\n\r". + if (((i + 1) < data.length) && (data.charAt(i + 1) === '\r')) { + return '\n\r'; + } + return code; + } + } + + return null; +}; + +/** + * Is one node contained by another? + * @param {Node} containee The contained node. + * @param {Node} container The container node. + * @return {boolean} Whether containee is inside (or equal to) container. + * @private + */ +Dygraph.isNodeContainedBy = function(containee, container) { + if (container === null || containee === null) { + return false; + } + var containeeNode = /** @type {Node} */ (containee); + while (containeeNode && containeeNode !== container) { + containeeNode = containeeNode.parentNode; + } + return (containeeNode === container); +}; + + +// This masks some numeric issues in older versions of Firefox, +// where 1.0/Math.pow(10,2) != Math.pow(10,-2). +/** @type {function(number,number):number} */ +Dygraph.pow = function(base, exp) { + if (exp < 0) { + return 1.0 / Math.pow(base, -exp); + } + return Math.pow(base, exp); +}; + +// For Dygraph.setDateSameTZ, below. +Dygraph.dateSetters = { + ms: Date.prototype.setMilliseconds, + s: Date.prototype.setSeconds, + m: Date.prototype.setMinutes, + h: Date.prototype.setHours +}; + +/** + * This is like calling d.setSeconds(), d.setMinutes(), etc, except that it + * adjusts for time zone changes to keep the date/time parts consistent. + * + * For example, d.getSeconds(), d.getMinutes() and d.getHours() will all be + * the same before/after you call setDateSameTZ(d, {ms: 0}). The same is not + * true if you call d.setMilliseconds(0). + * + * @type {function(!Date, Object.<number>)} + */ +Dygraph.setDateSameTZ = function(d, parts) { + var tz = d.getTimezoneOffset(); + for (var k in parts) { + if (!parts.hasOwnProperty(k)) continue; + var setter = Dygraph.dateSetters[k]; + if (!setter) throw "Invalid setter: " + k; + setter.call(d, parts[k]); + if (d.getTimezoneOffset() != tz) { + d.setTime(d.getTime() + (tz - d.getTimezoneOffset()) * 60 * 1000); + } + } +}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph.js new file mode 100644 index 00000000..1ee711ca --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/dygraph.js @@ -0,0 +1,3857 @@ +/** + * @license + * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview Creates an interactive, zoomable graph based on a CSV file or + * string. Dygraph can handle multiple series with or without error bars. The + * date/value ranges will be automatically set. Dygraph uses the + * <canvas> tag, so it only works in FF1.5+. + * @author danvdk@gmail.com (Dan Vanderkam) + + Usage: + <div id="graphdiv" style="width:800px; height:500px;"></div> + <script type="text/javascript"> + new Dygraph(document.getElementById("graphdiv"), + "datafile.csv", // CSV file with headers + { }); // options + </script> + + The CSV file is of the form + + Date,SeriesA,SeriesB,SeriesC + YYYYMMDD,A1,B1,C1 + YYYYMMDD,A2,B2,C2 + + If the 'errorBars' option is set in the constructor, the input should be of + the form + Date,SeriesA,SeriesB,... + YYYYMMDD,A1,sigmaA1,B1,sigmaB1,... + YYYYMMDD,A2,sigmaA2,B2,sigmaB2,... + + If the 'fractions' option is set, the input should be of the form: + + Date,SeriesA,SeriesB,... + YYYYMMDD,A1/B1,A2/B2,... + YYYYMMDD,A1/B1,A2/B2,... + + And error bars will be calculated automatically using a binomial distribution. + + For further documentation and examples, see http://dygraphs.com/ + + */ + +/*jshint globalstrict: true */ +/*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false */ +"use strict"; + +/** + * Creates an interactive, zoomable chart. + * + * @constructor + * @param {div | String} div A div or the id of a div into which to construct + * the chart. + * @param {String | Function} file A file containing CSV data or a function + * that returns this data. The most basic expected format for each line is + * "YYYY/MM/DD,val1,val2,...". For more information, see + * http://dygraphs.com/data.html. + * @param {Object} attrs Various other attributes, e.g. errorBars determines + * whether the input data contains error ranges. For a complete list of + * options, see http://dygraphs.com/options.html. + */ +var Dygraph = function(div, data, opts, opt_fourth_param) { + if (opt_fourth_param !== undefined) { + // Old versions of dygraphs took in the series labels as a constructor + // parameter. This doesn't make sense anymore, but it's easy to continue + // to support this usage. + this.warn("Using deprecated four-argument dygraph constructor"); + this.__old_init__(div, data, opts, opt_fourth_param); + } else { + this.__init__(div, data, opts); + } +}; + +Dygraph.NAME = "Dygraph"; +Dygraph.VERSION = "1.2"; +Dygraph.__repr__ = function() { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +/** + * Returns information about the Dygraph class. + */ +Dygraph.toString = function() { + return this.__repr__(); +}; + +// Various default values +Dygraph.DEFAULT_ROLL_PERIOD = 1; +Dygraph.DEFAULT_WIDTH = 480; +Dygraph.DEFAULT_HEIGHT = 320; + +// For max 60 Hz. animation: +Dygraph.ANIMATION_STEPS = 12; +Dygraph.ANIMATION_DURATION = 200; + +// Label constants for the labelsKMB and labelsKMG2 options. +// (i.e. '100000' -> '100K') +Dygraph.KMB_LABELS = [ 'K', 'M', 'B', 'T', 'Q' ]; +Dygraph.KMG2_BIG_LABELS = [ 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ]; +Dygraph.KMG2_SMALL_LABELS = [ 'm', 'u', 'n', 'p', 'f', 'a', 'z', 'y' ]; + +// These are defined before DEFAULT_ATTRS so that it can refer to them. +/** + * @private + * Return a string version of a number. This respects the digitsAfterDecimal + * and maxNumberWidth options. + * @param {Number} x The number to be formatted + * @param {Dygraph} opts An options view + * @param {String} name The name of the point's data series + * @param {Dygraph} g The dygraph object + */ +Dygraph.numberValueFormatter = function(x, opts, pt, g) { + var sigFigs = opts('sigFigs'); + + if (sigFigs !== null) { + // User has opted for a fixed number of significant figures. + return Dygraph.floatFormat(x, sigFigs); + } + + var digits = opts('digitsAfterDecimal'); + var maxNumberWidth = opts('maxNumberWidth'); + + var kmb = opts('labelsKMB'); + var kmg2 = opts('labelsKMG2'); + + var label; + + // switch to scientific notation if we underflow or overflow fixed display. + if (x !== 0.0 && + (Math.abs(x) >= Math.pow(10, maxNumberWidth) || + Math.abs(x) < Math.pow(10, -digits))) { + label = x.toExponential(digits); + } else { + label = '' + Dygraph.round_(x, digits); + } + + if (kmb || kmg2) { + var k; + var k_labels = []; + var m_labels = []; + if (kmb) { + k = 1000; + k_labels = Dygraph.KMB_LABELS; + } + if (kmg2) { + if (kmb) Dygraph.warn("Setting both labelsKMB and labelsKMG2. Pick one!"); + k = 1024; + k_labels = Dygraph.KMG2_BIG_LABELS; + m_labels = Dygraph.KMG2_SMALL_LABELS; + } + + var absx = Math.abs(x); + var n = Dygraph.pow(k, k_labels.length); + for (var j = k_labels.length - 1; j >= 0; j--, n /= k) { + if (absx >= n) { + label = Dygraph.round_(x / n, digits) + k_labels[j]; + break; + } + } + if (kmg2) { + // TODO(danvk): clean up this logic. Why so different than kmb? + var x_parts = String(x.toExponential()).split('e-'); + if (x_parts.length === 2 && x_parts[1] >= 3 && x_parts[1] <= 24) { + if (x_parts[1] % 3 > 0) { + label = Dygraph.round_(x_parts[0] / + Dygraph.pow(10, (x_parts[1] % 3)), + digits); + } else { + label = Number(x_parts[0]).toFixed(2); + } + label += m_labels[Math.floor(x_parts[1] / 3) - 1]; + } + } + } + + return label; +}; + +/** + * variant for use as an axisLabelFormatter. + * @private + */ +Dygraph.numberAxisLabelFormatter = function(x, granularity, opts, g) { + return Dygraph.numberValueFormatter(x, opts, g); +}; + +/** + * Convert a JS date (millis since epoch) to YYYY/MM/DD + * @param {Number} date The JavaScript date (ms since epoch) + * @return {String} A date of the form "YYYY/MM/DD" + * @private + */ +Dygraph.dateString_ = function(date) { + var zeropad = Dygraph.zeropad; + var d = new Date(date); + + // Get the year: + var year = "" + d.getFullYear(); + // Get a 0 padded month string + var month = zeropad(d.getMonth() + 1); //months are 0-offset, sigh + // Get a 0 padded day string + var day = zeropad(d.getDate()); + + var ret = ""; + var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); + if (frac) ret = " " + Dygraph.hmsString_(date); + + return year + "/" + month + "/" + day + ret; +}; + +/** + * Convert a JS date to a string appropriate to display on an axis that + * is displaying values at the stated granularity. + * @param {Date} date The date to format + * @param {Number} granularity One of the Dygraph granularity constants + * @return {String} The formatted date + * @private + */ +Dygraph.dateAxisFormatter = function(date, granularity) { + if (granularity >= Dygraph.DECADAL) { + return date.strftime('%Y'); + } else if (granularity >= Dygraph.MONTHLY) { + return date.strftime('%b %y'); + } else { + var frac = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() + date.getMilliseconds(); + if (frac === 0 || granularity >= Dygraph.DAILY) { + return new Date(date.getTime() + 3600*1000).strftime('%d%b'); + } else { + return Dygraph.hmsString_(date.getTime()); + } + } +}; + +/** + * Standard plotters. These may be used by clients. + * Available plotters are: + * - Dygraph.Plotters.linePlotter: draws central lines (most common) + * - Dygraph.Plotters.errorPlotter: draws error bars + * - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph) + * + * By default, the plotter is [fillPlotter, errorPlotter, linePlotter]. + * This causes all the lines to be drawn over all the fills/error bars. + */ +Dygraph.Plotters = DygraphCanvasRenderer._Plotters; + + +// Default attribute values. +Dygraph.DEFAULT_ATTRS = { + highlightCircleSize: 3, + highlightSeriesOpts: null, + highlightSeriesBackgroundAlpha: 0.5, + + labelsDivWidth: 250, + labelsDivStyles: { + // TODO(danvk): move defaults from createStatusMessage_ here. + }, + labelsSeparateLines: false, + labelsShowZeroValues: true, + labelsKMB: false, + labelsKMG2: false, + showLabelsOnHighlight: true, + + digitsAfterDecimal: 2, + maxNumberWidth: 6, + sigFigs: null, + + strokeWidth: 1.0, + strokeBorderWidth: 0, + strokeBorderColor: "white", + + axisTickSize: 3, + axisLabelFontSize: 14, + xAxisLabelWidth: 50, + yAxisLabelWidth: 50, + rightGap: 5, + + showRoller: false, + xValueParser: Dygraph.dateParser, + + delimiter: ',', + + sigma: 2.0, + errorBars: false, + fractions: false, + wilsonInterval: true, // only relevant if fractions is true + customBars: false, + fillGraph: false, + fillAlpha: 0.15, + connectSeparatedPoints: false, + + stackedGraph: false, + stackedGraphNaNFill: 'all', + hideOverlayOnMouseOut: true, + + // TODO(danvk): support 'onmouseover' and 'never', and remove synonyms. + legend: 'onmouseover', // the only relevant value at the moment is 'always'. + + stepPlot: false, + avoidMinZero: false, + xRangePad: 0, + yRangePad: null, + drawAxesAtZero: false, + + // Sizes of the various chart labels. + titleHeight: 28, + xLabelHeight: 18, + yLabelWidth: 18, + + drawXAxis: true, + drawYAxis: true, + axisLineColor: "black", + axisLineWidth: 0.3, + gridLineWidth: 0.3, + axisLabelColor: "black", + axisLabelFont: "Arial", // TODO(danvk): is this implemented? + axisLabelWidth: 50, + drawYGrid: true, + drawXGrid: true, + gridLineColor: "rgb(128,128,128)", + + interactionModel: null, // will be set to Dygraph.Interaction.defaultModel + animatedZooms: false, // (for now) + + // Range selector options + showRangeSelector: false, + rangeSelectorHeight: 40, + rangeSelectorPlotStrokeColor: "#808FAB", + rangeSelectorPlotFillColor: "#A7B1C4", + + // The ordering here ensures that central lines always appear above any + // fill bars/error bars. + plotter: [ + Dygraph.Plotters.fillPlotter, + Dygraph.Plotters.errorPlotter, + Dygraph.Plotters.linePlotter + ], + + plugins: [ ], + + // per-axis options + axes: { + x: { + pixelsPerLabel: 60, + axisLabelFormatter: Dygraph.dateAxisFormatter, + valueFormatter: Dygraph.dateString_, + drawGrid: true, + independentTicks: true, + ticker: null // will be set in dygraph-tickers.js + }, + y: { + pixelsPerLabel: 30, + valueFormatter: Dygraph.numberValueFormatter, + axisLabelFormatter: Dygraph.numberAxisLabelFormatter, + drawGrid: true, + independentTicks: true, + ticker: null // will be set in dygraph-tickers.js + }, + y2: { + pixelsPerLabel: 30, + valueFormatter: Dygraph.numberValueFormatter, + axisLabelFormatter: Dygraph.numberAxisLabelFormatter, + drawGrid: false, + independentTicks: false, + ticker: null // will be set in dygraph-tickers.js + } + } +}; + +// Directions for panning and zooming. Use bit operations when combined +// values are possible. +Dygraph.HORIZONTAL = 1; +Dygraph.VERTICAL = 2; + +// Installed plugins, in order of precedence (most-general to most-specific). +// Plugins are installed after they are defined, in plugins/install.js. +Dygraph.PLUGINS = [ +]; + +// Used for initializing annotation CSS rules only once. +Dygraph.addedAnnotationCSS = false; + +Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) { + // Labels is no longer a constructor parameter, since it's typically set + // directly from the data source. It also conains a name for the x-axis, + // which the previous constructor form did not. + if (labels !== null) { + var new_labels = ["Date"]; + for (var i = 0; i < labels.length; i++) new_labels.push(labels[i]); + Dygraph.update(attrs, { 'labels': new_labels }); + } + this.__init__(div, file, attrs); +}; + +/** + * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit + * and context <canvas> inside of it. See the constructor for details. + * on the parameters. + * @param {Element} div the Element to render the graph into. + * @param {String | Function} file Source data + * @param {Object} attrs Miscellaneous other options + * @private + */ +Dygraph.prototype.__init__ = function(div, file, attrs) { + // Hack for IE: if we're using excanvas and the document hasn't finished + // loading yet (and hence may not have initialized whatever it needs to + // initialize), then keep calling this routine periodically until it has. + if (/MSIE/.test(navigator.userAgent) && !window.opera && + typeof(G_vmlCanvasManager) != 'undefined' && + document.readyState != 'complete') { + var self = this; + setTimeout(function() { self.__init__(div, file, attrs); }, 100); + return; + } + + // Support two-argument constructor + if (attrs === null || attrs === undefined) { attrs = {}; } + + attrs = Dygraph.mapLegacyOptions_(attrs); + + if (typeof(div) == 'string') { + div = document.getElementById(div); + } + + if (!div) { + Dygraph.error("Constructing dygraph with a non-existent div!"); + return; + } + + this.isUsingExcanvas_ = typeof(G_vmlCanvasManager) != 'undefined'; + + // Copy the important bits into the object + // TODO(danvk): most of these should just stay in the attrs_ dictionary. + this.maindiv_ = div; + this.file_ = file; + this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD; + this.previousVerticalX_ = -1; + this.fractions_ = attrs.fractions || false; + this.dateWindow_ = attrs.dateWindow || null; + + this.is_initial_draw_ = true; + this.annotations_ = []; + + // Zoomed indicators - These indicate when the graph has been zoomed and on what axis. + this.zoomed_x_ = false; + this.zoomed_y_ = false; + + // Clear the div. This ensure that, if multiple dygraphs are passed the same + // div, then only one will be drawn. + div.innerHTML = ""; + + // For historical reasons, the 'width' and 'height' options trump all CSS + // rules _except_ for an explicit 'width' or 'height' on the div. + // As an added convenience, if the div has zero height (like <div></div> does + // without any styles), then we use a default height/width. + if (div.style.width === '' && attrs.width) { + div.style.width = attrs.width + "px"; + } + if (div.style.height === '' && attrs.height) { + div.style.height = attrs.height + "px"; + } + if (div.style.height === '' && div.clientHeight === 0) { + div.style.height = Dygraph.DEFAULT_HEIGHT + "px"; + if (div.style.width === '') { + div.style.width = Dygraph.DEFAULT_WIDTH + "px"; + } + } + // These will be zero if the dygraph's div is hidden. In that case, + // use the user-specified attributes if present. If not, use zero + // and assume the user will call resize to fix things later. + this.width_ = div.clientWidth || attrs.width || 0; + this.height_ = div.clientHeight || attrs.height || 0; + + // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_. + if (attrs.stackedGraph) { + attrs.fillGraph = true; + // TODO(nikhilk): Add any other stackedGraph checks here. + } + + // DEPRECATION WARNING: All option processing should be moved from + // attrs_ and user_attrs_ to options_, which holds all this information. + // + // Dygraphs has many options, some of which interact with one another. + // To keep track of everything, we maintain two sets of options: + // + // this.user_attrs_ only options explicitly set by the user. + // this.attrs_ defaults, options derived from user_attrs_, data. + // + // Options are then accessed this.attr_('attr'), which first looks at + // user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent + // defaults without overriding behavior that the user specifically asks for. + this.user_attrs_ = {}; + Dygraph.update(this.user_attrs_, attrs); + + // This sequence ensures that Dygraph.DEFAULT_ATTRS is never modified. + this.attrs_ = {}; + Dygraph.updateDeep(this.attrs_, Dygraph.DEFAULT_ATTRS); + + this.boundaryIds_ = []; + this.setIndexByName_ = {}; + this.datasetIndex_ = []; + + this.registeredEvents_ = []; + this.eventListeners_ = {}; + + this.attributes_ = new DygraphOptions(this); + + // Create the containing DIV and other interactive elements + this.createInterface_(); + + // Activate plugins. + this.plugins_ = []; + var plugins = Dygraph.PLUGINS.concat(this.getOption('plugins')); + for (var i = 0; i < plugins.length; i++) { + var Plugin = plugins[i]; + var pluginInstance = new Plugin(); + var pluginDict = { + plugin: pluginInstance, + events: {}, + options: {}, + pluginOptions: {} + }; + + var handlers = pluginInstance.activate(this); + for (var eventName in handlers) { + // TODO(danvk): validate eventName. + pluginDict.events[eventName] = handlers[eventName]; + } + + this.plugins_.push(pluginDict); + } + + // At this point, plugins can no longer register event handlers. + // Construct a map from event -> ordered list of [callback, plugin]. + for (var i = 0; i < this.plugins_.length; i++) { + var plugin_dict = this.plugins_[i]; + for (var eventName in plugin_dict.events) { + if (!plugin_dict.events.hasOwnProperty(eventName)) continue; + var callback = plugin_dict.events[eventName]; + + var pair = [plugin_dict.plugin, callback]; + if (!(eventName in this.eventListeners_)) { + this.eventListeners_[eventName] = [pair]; + } else { + this.eventListeners_[eventName].push(pair); + } + } + } + + this.createDragInterface_(); + + this.start_(); +}; + +/** + * Triggers a cascade of events to the various plugins which are interested in them. + * Returns true if the "default behavior" should be performed, i.e. if none of + * the event listeners called event.preventDefault(). + * @private + */ +Dygraph.prototype.cascadeEvents_ = function(name, extra_props) { + if (!(name in this.eventListeners_)) return true; + + // QUESTION: can we use objects & prototypes to speed this up? + var e = { + dygraph: this, + cancelable: false, + defaultPrevented: false, + preventDefault: function() { + if (!e.cancelable) throw "Cannot call preventDefault on non-cancelable event."; + e.defaultPrevented = true; + }, + propagationStopped: false, + stopPropagation: function() { + e.propagationStopped = true; + } + }; + Dygraph.update(e, extra_props); + + var callback_plugin_pairs = this.eventListeners_[name]; + if (callback_plugin_pairs) { + for (var i = callback_plugin_pairs.length - 1; i >= 0; i--) { + var plugin = callback_plugin_pairs[i][0]; + var callback = callback_plugin_pairs[i][1]; + callback.call(plugin, e); + if (e.propagationStopped) break; + } + } + return e.defaultPrevented; +}; + +/** + * Returns the zoomed status of the chart for one or both axes. + * + * Axis is an optional parameter. Can be set to 'x' or 'y'. + * + * The zoomed status for an axis is set whenever a user zooms using the mouse + * or when the dateWindow or valueRange are updated (unless the + * isZoomedIgnoreProgrammaticZoom option is also specified). + */ +Dygraph.prototype.isZoomed = function(axis) { + if (axis === null || axis === undefined) { + return this.zoomed_x_ || this.zoomed_y_; + } + if (axis === 'x') return this.zoomed_x_; + if (axis === 'y') return this.zoomed_y_; + throw "axis parameter is [" + axis + "] must be null, 'x' or 'y'."; +}; + +/** + * Returns information about the Dygraph object, including its containing ID. + */ +Dygraph.prototype.toString = function() { + var maindiv = this.maindiv_; + var id = (maindiv && maindiv.id) ? maindiv.id : maindiv; + return "[Dygraph " + id + "]"; +}; + +/** + * @private + * Returns the value of an option. This may be set by the user (either in the + * constructor or by calling updateOptions) or by dygraphs, and may be set to a + * per-series value. + * @param { String } name The name of the option, e.g. 'rollPeriod'. + * @param { String } [seriesName] The name of the series to which the option + * will be applied. If no per-series value of this option is available, then + * the global value is returned. This is optional. + * @return { ... } The value of the option. + */ +Dygraph.prototype.attr_ = function(name, seriesName) { +// <REMOVE_FOR_COMBINED> + if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') { + this.error('Must include options reference JS for testing'); + } else if (!Dygraph.OPTIONS_REFERENCE.hasOwnProperty(name)) { + this.error('Dygraphs is using property ' + name + ', which has no entry ' + + 'in the Dygraphs.OPTIONS_REFERENCE listing.'); + // Only log this error once. + Dygraph.OPTIONS_REFERENCE[name] = true; + } +// </REMOVE_FOR_COMBINED> + return seriesName ? this.attributes_.getForSeries(name, seriesName) : this.attributes_.get(name); +}; + +/** + * Returns the current value for an option, as set in the constructor or via + * updateOptions. You may pass in an (optional) series name to get per-series + * values for the option. + * + * All values returned by this method should be considered immutable. If you + * modify them, there is no guarantee that the changes will be honored or that + * dygraphs will remain in a consistent state. If you want to modify an option, + * use updateOptions() instead. + * + * @param { String } name The name of the option (e.g. 'strokeWidth') + * @param { String } [opt_seriesName] Series name to get per-series values. + * @return { ... } The value of the option. + */ +Dygraph.prototype.getOption = function(name, opt_seriesName) { + return this.attr_(name, opt_seriesName); +}; + +Dygraph.prototype.getOptionForAxis = function(name, axis) { + return this.attributes_.getForAxis(name, axis); +}; + +/** + * @private + * @param String} axis The name of the axis (i.e. 'x', 'y' or 'y2') + * @return { ... } A function mapping string -> option value + */ +Dygraph.prototype.optionsViewForAxis_ = function(axis) { + var self = this; + return function(opt) { + var axis_opts = self.user_attrs_.axes; + if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) { + return axis_opts[axis][opt]; + } + // user-specified attributes always trump defaults, even if they're less + // specific. + if (typeof(self.user_attrs_[opt]) != 'undefined') { + return self.user_attrs_[opt]; + } + + axis_opts = self.attrs_.axes; + if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) { + return axis_opts[axis][opt]; + } + // check old-style axis options + // TODO(danvk): add a deprecation warning if either of these match. + if (axis == 'y' && self.axes_[0].hasOwnProperty(opt)) { + return self.axes_[0][opt]; + } else if (axis == 'y2' && self.axes_[1].hasOwnProperty(opt)) { + return self.axes_[1][opt]; + } + return self.attr_(opt); + }; +}; + +/** + * Returns the current rolling period, as set by the user or an option. + * @return {Number} The number of points in the rolling window + */ +Dygraph.prototype.rollPeriod = function() { + return this.rollPeriod_; +}; + +/** + * Returns the currently-visible x-range. This can be affected by zooming, + * panning or a call to updateOptions. + * Returns a two-element array: [left, right]. + * If the Dygraph has dates on the x-axis, these will be millis since epoch. + */ +Dygraph.prototype.xAxisRange = function() { + return this.dateWindow_ ? this.dateWindow_ : this.xAxisExtremes(); +}; + +/** + * Returns the lower- and upper-bound x-axis values of the + * data set. + */ +Dygraph.prototype.xAxisExtremes = function() { + var pad = this.attr_('xRangePad') / this.plotter_.area.w; + if (this.numRows() === 0) { + return [0 - pad, 1 + pad]; + } + var left = this.rawData_[0][0]; + var right = this.rawData_[this.rawData_.length - 1][0]; + if (pad) { + // Must keep this in sync with dygraph-layout _evaluateLimits() + var range = right - left; + left -= range * pad; + right += range * pad; + } + return [left, right]; +}; + +/** + * Returns the currently-visible y-range for an axis. This can be affected by + * zooming, panning or a call to updateOptions. Axis indices are zero-based. If + * called with no arguments, returns the range of the first axis. + * Returns a two-element array: [bottom, top]. + */ +Dygraph.prototype.yAxisRange = function(idx) { + if (typeof(idx) == "undefined") idx = 0; + if (idx < 0 || idx >= this.axes_.length) { + return null; + } + var axis = this.axes_[idx]; + return [ axis.computedValueRange[0], axis.computedValueRange[1] ]; +}; + +/** + * Returns the currently-visible y-ranges for each axis. This can be affected by + * zooming, panning, calls to updateOptions, etc. + * Returns an array of [bottom, top] pairs, one for each y-axis. + */ +Dygraph.prototype.yAxisRanges = function() { + var ret = []; + for (var i = 0; i < this.axes_.length; i++) { + ret.push(this.yAxisRange(i)); + } + return ret; +}; + +// TODO(danvk): use these functions throughout dygraphs. +/** + * Convert from data coordinates to canvas/div X/Y coordinates. + * If specified, do this conversion for the coordinate system of a particular + * axis. Uses the first axis by default. + * Returns a two-element array: [X, Y] + * + * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord + * instead of toDomCoords(null, y, axis). + */ +Dygraph.prototype.toDomCoords = function(x, y, axis) { + return [ this.toDomXCoord(x), this.toDomYCoord(y, axis) ]; +}; + +/** + * Convert from data x coordinates to canvas/div X coordinate. + * If specified, do this conversion for the coordinate system of a particular + * axis. + * Returns a single value or null if x is null. + */ +Dygraph.prototype.toDomXCoord = function(x) { + if (x === null) { + return null; + } + + var area = this.plotter_.area; + var xRange = this.xAxisRange(); + return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w; +}; + +/** + * Convert from data x coordinates to canvas/div Y coordinate and optional + * axis. Uses the first axis by default. + * + * returns a single value or null if y is null. + */ +Dygraph.prototype.toDomYCoord = function(y, axis) { + var pct = this.toPercentYCoord(y, axis); + + if (pct === null) { + return null; + } + var area = this.plotter_.area; + return area.y + pct * area.h; +}; + +/** + * Convert from canvas/div coords to data coordinates. + * If specified, do this conversion for the coordinate system of a particular + * axis. Uses the first axis by default. + * Returns a two-element array: [X, Y]. + * + * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord + * instead of toDataCoords(null, y, axis). + */ +Dygraph.prototype.toDataCoords = function(x, y, axis) { + return [ this.toDataXCoord(x), this.toDataYCoord(y, axis) ]; +}; + +/** + * Convert from canvas/div x coordinate to data coordinate. + * + * If x is null, this returns null. + */ +Dygraph.prototype.toDataXCoord = function(x) { + if (x === null) { + return null; + } + + var area = this.plotter_.area; + var xRange = this.xAxisRange(); + return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]); +}; + +/** + * Convert from canvas/div y coord to value. + * + * If y is null, this returns null. + * if axis is null, this uses the first axis. + */ +Dygraph.prototype.toDataYCoord = function(y, axis) { + if (y === null) { + return null; + } + + var area = this.plotter_.area; + var yRange = this.yAxisRange(axis); + + if (typeof(axis) == "undefined") axis = 0; + if (!this.axes_[axis].logscale) { + return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]); + } else { + // Computing the inverse of toDomCoord. + var pct = (y - area.y) / area.h; + + // Computing the inverse of toPercentYCoord. The function was arrived at with + // the following steps: + // + // Original calcuation: + // pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0])); + // + // Move denominator to both sides: + // pct * (logr1 - Dygraph.log10(yRange[0])) = logr1 - Dygraph.log10(y); + // + // subtract logr1, and take the negative value. + // logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))) = Dygraph.log10(y); + // + // Swap both sides of the equation, and we can compute the log of the + // return value. Which means we just need to use that as the exponent in + // e^exponent. + // Dygraph.log10(y) = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))); + + var logr1 = Dygraph.log10(yRange[1]); + var exponent = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))); + var value = Math.pow(Dygraph.LOG_SCALE, exponent); + return value; + } +}; + +/** + * Converts a y for an axis to a percentage from the top to the + * bottom of the drawing area. + * + * If the coordinate represents a value visible on the canvas, then + * the value will be between 0 and 1, where 0 is the top of the canvas. + * However, this method will return values outside the range, as + * values can fall outside the canvas. + * + * If y is null, this returns null. + * if axis is null, this uses the first axis. + * + * @param { Number } y The data y-coordinate. + * @param { Number } [axis] The axis number on which the data coordinate lives. + * @return { Number } A fraction in [0, 1] where 0 = the top edge. + */ +Dygraph.prototype.toPercentYCoord = function(y, axis) { + if (y === null) { + return null; + } + if (typeof(axis) == "undefined") axis = 0; + + var yRange = this.yAxisRange(axis); + + var pct; + var logscale = this.attributes_.getForAxis("logscale", axis); + if (!logscale) { + // yRange[1] - y is unit distance from the bottom. + // yRange[1] - yRange[0] is the scale of the range. + // (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom. + pct = (yRange[1] - y) / (yRange[1] - yRange[0]); + } else { + var logr1 = Dygraph.log10(yRange[1]); + pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0])); + } + return pct; +}; + +/** + * Converts an x value to a percentage from the left to the right of + * the drawing area. + * + * If the coordinate represents a value visible on the canvas, then + * the value will be between 0 and 1, where 0 is the left of the canvas. + * However, this method will return values outside the range, as + * values can fall outside the canvas. + * + * If x is null, this returns null. + * @param { Number } x The data x-coordinate. + * @return { Number } A fraction in [0, 1] where 0 = the left edge. + */ +Dygraph.prototype.toPercentXCoord = function(x) { + if (x === null) { + return null; + } + + var xRange = this.xAxisRange(); + return (x - xRange[0]) / (xRange[1] - xRange[0]); +}; + +/** + * Returns the number of columns (including the independent variable). + * @return { Integer } The number of columns. + */ +Dygraph.prototype.numColumns = function() { + if (!this.rawData_) return 0; + return this.rawData_[0] ? this.rawData_[0].length : this.attr_("labels").length; +}; + +/** + * Returns the number of rows (excluding any header/label row). + * @return { Integer } The number of rows, less any header. + */ +Dygraph.prototype.numRows = function() { + if (!this.rawData_) return 0; + return this.rawData_.length; +}; + +/** + * Returns the value in the given row and column. If the row and column exceed + * the bounds on the data, returns null. Also returns null if the value is + * missing. + * @param { Number} row The row number of the data (0-based). Row 0 is the + * first row of data, not a header row. + * @param { Number} col The column number of the data (0-based) + * @return { Number } The value in the specified cell or null if the row/col + * were out of range. + */ +Dygraph.prototype.getValue = function(row, col) { + if (row < 0 || row > this.rawData_.length) return null; + if (col < 0 || col > this.rawData_[row].length) return null; + + return this.rawData_[row][col]; +}; + +/** + * Generates interface elements for the Dygraph: a containing div, a div to + * display the current point, and a textbox to adjust the rolling average + * period. Also creates the Renderer/Layout elements. + * @private + */ +Dygraph.prototype.createInterface_ = function() { + // Create the all-enclosing graph div + var enclosing = this.maindiv_; + + this.graphDiv = document.createElement("div"); + + // TODO(danvk): any other styles that are useful to set here? + this.graphDiv.style.textAlign = 'left'; // This is a CSS "reset" + enclosing.appendChild(this.graphDiv); + + // Create the canvas for interactive parts of the chart. + this.canvas_ = Dygraph.createCanvas(); + this.canvas_.style.position = "absolute"; + + // ... and for static parts of the chart. + this.hidden_ = this.createPlotKitCanvas_(this.canvas_); + + this.resizeElements_(); + + this.canvas_ctx_ = Dygraph.getContext(this.canvas_); + this.hidden_ctx_ = Dygraph.getContext(this.hidden_); + + // The interactive parts of the graph are drawn on top of the chart. + this.graphDiv.appendChild(this.hidden_); + this.graphDiv.appendChild(this.canvas_); + this.mouseEventElement_ = this.createMouseEventElement_(); + + // Create the grapher + this.layout_ = new DygraphLayout(this); + + var dygraph = this; + + this.mouseMoveHandler_ = function(e) { + dygraph.mouseMove_(e); + }; + + this.mouseOutHandler_ = function(e) { + // The mouse has left the chart if: + // 1. e.target is inside the chart + // 2. e.relatedTarget is outside the chart + var target = e.target || e.fromElement; + var relatedTarget = e.relatedTarget || e.toElement; + if (Dygraph.isNodeContainedBy(target, dygraph.graphDiv) && + !Dygraph.isNodeContainedBy(relatedTarget, dygraph.graphDiv)) { + dygraph.mouseOut_(e); + } + }; + + this.addAndTrackEvent(window, 'mouseout', this.mouseOutHandler_); + this.addAndTrackEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_); + + // Don't recreate and register the resize handler on subsequent calls. + // This happens when the graph is resized. + if (!this.resizeHandler_) { + this.resizeHandler_ = function(e) { + dygraph.resize(); + }; + + // Update when the window is resized. + // TODO(danvk): drop frames depending on complexity of the chart. + this.addAndTrackEvent(window, 'resize', this.resizeHandler_); + } +}; + +Dygraph.prototype.resizeElements_ = function() { + this.graphDiv.style.width = this.width_ + "px"; + this.graphDiv.style.height = this.height_ + "px"; + this.canvas_.width = this.width_; + this.canvas_.height = this.height_; + this.canvas_.style.width = this.width_ + "px"; // for IE + this.canvas_.style.height = this.height_ + "px"; // for IE + this.hidden_.width = this.width_; + this.hidden_.height = this.height_; + this.hidden_.style.width = this.width_ + "px"; // for IE + this.hidden_.style.height = this.height_ + "px"; // for IE +}; + +/** + * Detach DOM elements in the dygraph and null out all data references. + * Calling this when you're done with a dygraph can dramatically reduce memory + * usage. See, e.g., the tests/perf.html example. + */ +Dygraph.prototype.destroy = function() { + this.canvas_ctx_.restore(); + this.hidden_ctx_.restore(); + + var removeRecursive = function(node) { + while (node.hasChildNodes()) { + removeRecursive(node.firstChild); + node.removeChild(node.firstChild); + } + }; + + this.removeTrackedEvents_(); + + // remove mouse event handlers (This may not be necessary anymore) + Dygraph.removeEvent(window, 'mouseout', this.mouseOutHandler_); + Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_); + + // remove window handlers + Dygraph.removeEvent(window,'resize',this.resizeHandler_); + this.resizeHandler_ = null; + + removeRecursive(this.maindiv_); + + var nullOut = function(obj) { + for (var n in obj) { + if (typeof(obj[n]) === 'object') { + obj[n] = null; + } + } + }; + // These may not all be necessary, but it can't hurt... + nullOut(this.layout_); + nullOut(this.plotter_); + nullOut(this); +}; + +/** + * Creates the canvas on which the chart will be drawn. Only the Renderer ever + * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots + * or the zoom rectangles) is done on this.canvas_. + * @param {Object} canvas The Dygraph canvas over which to overlay the plot + * @return {Object} The newly-created canvas + * @private + */ +Dygraph.prototype.createPlotKitCanvas_ = function(canvas) { + var h = Dygraph.createCanvas(); + h.style.position = "absolute"; + // TODO(danvk): h should be offset from canvas. canvas needs to include + // some extra area to make it easier to zoom in on the far left and far + // right. h needs to be precisely the plot area, so that clipping occurs. + h.style.top = canvas.style.top; + h.style.left = canvas.style.left; + h.width = this.width_; + h.height = this.height_; + h.style.width = this.width_ + "px"; // for IE + h.style.height = this.height_ + "px"; // for IE + return h; +}; + +/** + * Creates an overlay element used to handle mouse events. + * @return {Object} The mouse event element. + * @private + */ +Dygraph.prototype.createMouseEventElement_ = function() { + if (this.isUsingExcanvas_) { + var elem = document.createElement("div"); + elem.style.position = 'absolute'; + elem.style.backgroundColor = 'white'; + elem.style.filter = 'alpha(opacity=0)'; + elem.style.width = this.width_ + "px"; + elem.style.height = this.height_ + "px"; + this.graphDiv.appendChild(elem); + return elem; + } else { + return this.canvas_; + } +}; + +/** + * Generate a set of distinct colors for the data series. This is done with a + * color wheel. Saturation/Value are customizable, and the hue is + * equally-spaced around the color wheel. If a custom set of colors is + * specified, that is used instead. + * @private + */ +Dygraph.prototype.setColors_ = function() { + var labels = this.getLabels(); + var num = labels.length - 1; + this.colors_ = []; + this.colorsMap_ = {}; + var colors = this.attr_('colors'); + var i; + if (!colors) { + var sat = this.attr_('colorSaturation') || 1.0; + var val = this.attr_('colorValue') || 0.5; + var half = Math.ceil(num / 2); + for (i = 1; i <= num; i++) { + if (!this.visibility()[i-1]) continue; + // alternate colors for high contrast. + var idx = i % 2 ? Math.ceil(i / 2) : (half + i / 2); + var hue = (1.0 * idx/ (1 + num)); + var colorStr = Dygraph.hsvToRGB(hue, sat, val); + this.colors_.push(colorStr); + this.colorsMap_[labels[i]] = colorStr; + } + } else { + for (i = 0; i < num; i++) { + if (!this.visibility()[i]) continue; + var colorStr = colors[i % colors.length]; + this.colors_.push(colorStr); + this.colorsMap_[labels[1 + i]] = colorStr; + } + } +}; + +/** + * Return the list of colors. This is either the list of colors passed in the + * attributes or the autogenerated list of rgb(r,g,b) strings. + * This does not return colors for invisible series. + * @return {Array<string>} The list of colors. + */ +Dygraph.prototype.getColors = function() { + return this.colors_; +}; + +/** + * Returns a few attributes of a series, i.e. its color, its visibility, which + * axis it's assigned to, and its column in the original data. + * Returns null if the series does not exist. + * Otherwise, returns an object with column, visibility, color and axis properties. + * The "axis" property will be set to 1 for y1 and 2 for y2. + * The "column" property can be fed back into getValue(row, column) to get + * values for this series. + */ +Dygraph.prototype.getPropertiesForSeries = function(series_name) { + var idx = -1; + var labels = this.getLabels(); + for (var i = 1; i < labels.length; i++) { + if (labels[i] == series_name) { + idx = i; + break; + } + } + if (idx == -1) return null; + + return { + name: series_name, + column: idx, + visible: this.visibility()[idx - 1], + color: this.colorsMap_[series_name], + axis: 1 + this.attributes_.axisForSeries(series_name) + }; +}; + +/** + * Create the text box to adjust the averaging period + * @private + */ +Dygraph.prototype.createRollInterface_ = function() { + // Create a roller if one doesn't exist already. + if (!this.roller_) { + this.roller_ = document.createElement("input"); + this.roller_.type = "text"; + this.roller_.style.display = "none"; + this.graphDiv.appendChild(this.roller_); + } + + var display = this.attr_('showRoller') ? 'block' : 'none'; + + var area = this.plotter_.area; + var textAttr = { "position": "absolute", + "zIndex": 10, + "top": (area.y + area.h - 25) + "px", + "left": (area.x + 1) + "px", + "display": display + }; + this.roller_.size = "2"; + this.roller_.value = this.rollPeriod_; + for (var name in textAttr) { + if (textAttr.hasOwnProperty(name)) { + this.roller_.style[name] = textAttr[name]; + } + } + + var dygraph = this; + this.roller_.onchange = function() { dygraph.adjustRoll(dygraph.roller_.value); }; +}; + +/** + * @private + * Converts page the x-coordinate of the event to pixel x-coordinates on the + * canvas (i.e. DOM Coords). + */ +Dygraph.prototype.dragGetX_ = function(e, context) { + return Dygraph.pageX(e) - context.px; +}; + +/** + * @private + * Converts page the y-coordinate of the event to pixel y-coordinates on the + * canvas (i.e. DOM Coords). + */ +Dygraph.prototype.dragGetY_ = function(e, context) { + return Dygraph.pageY(e) - context.py; +}; + +/** + * Set up all the mouse handlers needed to capture dragging behavior for zoom + * events. + * @private + */ +Dygraph.prototype.createDragInterface_ = function() { + var context = { + // Tracks whether the mouse is down right now + isZooming: false, + isPanning: false, // is this drag part of a pan? + is2DPan: false, // if so, is that pan 1- or 2-dimensional? + dragStartX: null, // pixel coordinates + dragStartY: null, // pixel coordinates + dragEndX: null, // pixel coordinates + dragEndY: null, // pixel coordinates + dragDirection: null, + prevEndX: null, // pixel coordinates + prevEndY: null, // pixel coordinates + prevDragDirection: null, + cancelNextDblclick: false, // see comment in dygraph-interaction-model.js + + // The value on the left side of the graph when a pan operation starts. + initialLeftmostDate: null, + + // The number of units each pixel spans. (This won't be valid for log + // scales) + xUnitsPerPixel: null, + + // TODO(danvk): update this comment + // The range in second/value units that the viewport encompasses during a + // panning operation. + dateRange: null, + + // Top-left corner of the canvas, in DOM coords + // TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY. + px: 0, + py: 0, + + // Values for use with panEdgeFraction, which limit how far outside the + // graph's data boundaries it can be panned. + boundedDates: null, // [minDate, maxDate] + boundedValues: null, // [[minValue, maxValue] ...] + + // We cover iframes during mouse interactions. See comments in + // dygraph-utils.js for more info on why this is a good idea. + tarp: new Dygraph.IFrameTarp(), + + // contextB is the same thing as this context object but renamed. + initializeMouseDown: function(event, g, contextB) { + // prevents mouse drags from selecting page text. + if (event.preventDefault) { + event.preventDefault(); // Firefox, Chrome, etc. + } else { + event.returnValue = false; // IE + event.cancelBubble = true; + } + + contextB.px = Dygraph.findPosX(g.canvas_); + contextB.py = Dygraph.findPosY(g.canvas_); + contextB.dragStartX = g.dragGetX_(event, contextB); + contextB.dragStartY = g.dragGetY_(event, contextB); + contextB.cancelNextDblclick = false; + contextB.tarp.cover(); + } + }; + + var interactionModel = this.attr_("interactionModel"); + + // Self is the graph. + var self = this; + + // Function that binds the graph and context to the handler. + var bindHandler = function(handler) { + return function(event) { + handler(event, self, context); + }; + }; + + for (var eventName in interactionModel) { + if (!interactionModel.hasOwnProperty(eventName)) continue; + this.addAndTrackEvent(this.mouseEventElement_, eventName, + bindHandler(interactionModel[eventName])); + } + + // If the user releases the mouse button during a drag, but not over the + // canvas, then it doesn't count as a zooming action. + var mouseUpHandler = function(event) { + if (context.isZooming || context.isPanning) { + context.isZooming = false; + context.dragStartX = null; + context.dragStartY = null; + } + + if (context.isPanning) { + context.isPanning = false; + context.draggingDate = null; + context.dateRange = null; + for (var i = 0; i < self.axes_.length; i++) { + delete self.axes_[i].draggingValue; + delete self.axes_[i].dragValueRange; + } + } + + context.tarp.uncover(); + }; + + this.addAndTrackEvent(document, 'mouseup', mouseUpHandler); +}; + +/** + * Draw a gray zoom rectangle over the desired area of the canvas. Also clears + * up any previous zoom rectangles that were drawn. This could be optimized to + * avoid extra redrawing, but it's tricky to avoid interactions with the status + * dots. + * + * @param {Number} direction the direction of the zoom rectangle. Acceptable + * values are Dygraph.HORIZONTAL and Dygraph.VERTICAL. + * @param {Number} startX The X position where the drag started, in canvas + * coordinates. + * @param {Number} endX The current X position of the drag, in canvas coords. + * @param {Number} startY The Y position where the drag started, in canvas + * coordinates. + * @param {Number} endY The current Y position of the drag, in canvas coords. + * @param {Number} prevDirection the value of direction on the previous call to + * this function. Used to avoid excess redrawing + * @param {Number} prevEndX The value of endX on the previous call to this + * function. Used to avoid excess redrawing + * @param {Number} prevEndY The value of endY on the previous call to this + * function. Used to avoid excess redrawing + * @private + */ +Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY, + endY, prevDirection, prevEndX, + prevEndY) { + var ctx = this.canvas_ctx_; + + // Clean up from the previous rect if necessary + if (prevDirection == Dygraph.HORIZONTAL) { + ctx.clearRect(Math.min(startX, prevEndX), this.layout_.getPlotArea().y, + Math.abs(startX - prevEndX), this.layout_.getPlotArea().h); + } else if (prevDirection == Dygraph.VERTICAL) { + ctx.clearRect(this.layout_.getPlotArea().x, Math.min(startY, prevEndY), + this.layout_.getPlotArea().w, Math.abs(startY - prevEndY)); + } + + // Draw a light-grey rectangle to show the new viewing area + if (direction == Dygraph.HORIZONTAL) { + if (endX && startX) { + ctx.fillStyle = "rgba(128,128,128,0.33)"; + ctx.fillRect(Math.min(startX, endX), this.layout_.getPlotArea().y, + Math.abs(endX - startX), this.layout_.getPlotArea().h); + } + } else if (direction == Dygraph.VERTICAL) { + if (endY && startY) { + ctx.fillStyle = "rgba(128,128,128,0.33)"; + ctx.fillRect(this.layout_.getPlotArea().x, Math.min(startY, endY), + this.layout_.getPlotArea().w, Math.abs(endY - startY)); + } + } + + if (this.isUsingExcanvas_) { + this.currentZoomRectArgs_ = [direction, startX, endX, startY, endY, 0, 0, 0]; + } +}; + +/** + * Clear the zoom rectangle (and perform no zoom). + * @private + */ +Dygraph.prototype.clearZoomRect_ = function() { + this.currentZoomRectArgs_ = null; + this.canvas_ctx_.clearRect(0, 0, this.canvas_.width, this.canvas_.height); +}; + +/** + * Zoom to something containing [lowX, highX]. These are pixel coordinates in + * the canvas. The exact zoom window may be slightly larger if there are no data + * points near lowX or highX. Don't confuse this function with doZoomXDates, + * which accepts dates that match the raw data. This function redraws the graph. + * + * @param {Number} lowX The leftmost pixel value that should be visible. + * @param {Number} highX The rightmost pixel value that should be visible. + * @private + */ +Dygraph.prototype.doZoomX_ = function(lowX, highX) { + this.currentZoomRectArgs_ = null; + // Find the earliest and latest dates contained in this canvasx range. + // Convert the call to date ranges of the raw data. + var minDate = this.toDataXCoord(lowX); + var maxDate = this.toDataXCoord(highX); + this.doZoomXDates_(minDate, maxDate); +}; + +/** + * Transition function to use in animations. Returns values between 0.0 + * (totally old values) and 1.0 (totally new values) for each frame. + * @private + */ +Dygraph.zoomAnimationFunction = function(frame, numFrames) { + var k = 1.5; + return (1.0 - Math.pow(k, -frame)) / (1.0 - Math.pow(k, -numFrames)); +}; + +/** + * Zoom to something containing [minDate, maxDate] values. Don't confuse this + * method with doZoomX which accepts pixel coordinates. This function redraws + * the graph. + * + * @param {Number} minDate The minimum date that should be visible. + * @param {Number} maxDate The maximum date that should be visible. + * @private + */ +Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) { + // TODO(danvk): when yAxisRange is null (i.e. "fit to data", the animation + // can produce strange effects. Rather than the y-axis transitioning slowly + // between values, it can jerk around.) + var old_window = this.xAxisRange(); + var new_window = [minDate, maxDate]; + this.zoomed_x_ = true; + var that = this; + this.doAnimatedZoom(old_window, new_window, null, null, function() { + if (that.attr_("zoomCallback")) { + that.attr_("zoomCallback")(minDate, maxDate, that.yAxisRanges()); + } + }); +}; + +/** + * Zoom to something containing [lowY, highY]. These are pixel coordinates in + * the canvas. This function redraws the graph. + * + * @param {Number} lowY The topmost pixel value that should be visible. + * @param {Number} highY The lowest pixel value that should be visible. + * @private + */ +Dygraph.prototype.doZoomY_ = function(lowY, highY) { + this.currentZoomRectArgs_ = null; + // Find the highest and lowest values in pixel range for each axis. + // Note that lowY (in pixels) corresponds to the max Value (in data coords). + // This is because pixels increase as you go down on the screen, whereas data + // coordinates increase as you go up the screen. + var oldValueRanges = this.yAxisRanges(); + var newValueRanges = []; + for (var i = 0; i < this.axes_.length; i++) { + var hi = this.toDataYCoord(lowY, i); + var low = this.toDataYCoord(highY, i); + newValueRanges.push([low, hi]); + } + + this.zoomed_y_ = true; + var that = this; + this.doAnimatedZoom(null, null, oldValueRanges, newValueRanges, function() { + if (that.attr_("zoomCallback")) { + var xRange = that.xAxisRange(); + that.attr_("zoomCallback")(xRange[0], xRange[1], that.yAxisRanges()); + } + }); +}; + +/** + * Reset the zoom to the original view coordinates. This is the same as + * double-clicking on the graph. + */ +Dygraph.prototype.resetZoom = function() { + var dirty = false, dirtyX = false, dirtyY = false; + if (this.dateWindow_ !== null) { + dirty = true; + dirtyX = true; + } + + for (var i = 0; i < this.axes_.length; i++) { + if (typeof(this.axes_[i].valueWindow) !== 'undefined' && this.axes_[i].valueWindow !== null) { + dirty = true; + dirtyY = true; + } + } + + // Clear any selection, since it's likely to be drawn in the wrong place. + this.clearSelection(); + + if (dirty) { + this.zoomed_x_ = false; + this.zoomed_y_ = false; + + var minDate = this.rawData_[0][0]; + var maxDate = this.rawData_[this.rawData_.length - 1][0]; + + // With only one frame, don't bother calculating extreme ranges. + // TODO(danvk): merge this block w/ the code below. + if (!this.attr_("animatedZooms")) { + this.dateWindow_ = null; + for (i = 0; i < this.axes_.length; i++) { + if (this.axes_[i].valueWindow !== null) { + delete this.axes_[i].valueWindow; + } + } + this.drawGraph_(); + if (this.attr_("zoomCallback")) { + this.attr_("zoomCallback")(minDate, maxDate, this.yAxisRanges()); + } + return; + } + + var oldWindow=null, newWindow=null, oldValueRanges=null, newValueRanges=null; + if (dirtyX) { + oldWindow = this.xAxisRange(); + newWindow = [minDate, maxDate]; + } + + if (dirtyY) { + oldValueRanges = this.yAxisRanges(); + // TODO(danvk): this is pretty inefficient + var packed = this.gatherDatasets_(this.rolledSeries_, null); + var extremes = packed.extremes; + + // this has the side-effect of modifying this.axes_. + // this doesn't make much sense in this context, but it's convenient (we + // need this.axes_[*].extremeValues) and not harmful since we'll be + // calling drawGraph_ shortly, which clobbers these values. + this.computeYAxisRanges_(extremes); + + newValueRanges = []; + for (i = 0; i < this.axes_.length; i++) { + var axis = this.axes_[i]; + newValueRanges.push((axis.valueRange !== null && + axis.valueRange !== undefined) ? + axis.valueRange : axis.extremeRange); + } + } + + var that = this; + this.doAnimatedZoom(oldWindow, newWindow, oldValueRanges, newValueRanges, + function() { + that.dateWindow_ = null; + for (var i = 0; i < that.axes_.length; i++) { + if (that.axes_[i].valueWindow !== null) { + delete that.axes_[i].valueWindow; + } + } + if (that.attr_("zoomCallback")) { + that.attr_("zoomCallback")(minDate, maxDate, that.yAxisRanges()); + } + }); + } +}; + +/** + * Combined animation logic for all zoom functions. + * either the x parameters or y parameters may be null. + * @private + */ +Dygraph.prototype.doAnimatedZoom = function(oldXRange, newXRange, oldYRanges, newYRanges, callback) { + var steps = this.attr_("animatedZooms") ? Dygraph.ANIMATION_STEPS : 1; + + var windows = []; + var valueRanges = []; + var step, frac; + + if (oldXRange !== null && newXRange !== null) { + for (step = 1; step <= steps; step++) { + frac = Dygraph.zoomAnimationFunction(step, steps); + windows[step-1] = [oldXRange[0]*(1-frac) + frac*newXRange[0], + oldXRange[1]*(1-frac) + frac*newXRange[1]]; + } + } + + if (oldYRanges !== null && newYRanges !== null) { + for (step = 1; step <= steps; step++) { + frac = Dygraph.zoomAnimationFunction(step, steps); + var thisRange = []; + for (var j = 0; j < this.axes_.length; j++) { + thisRange.push([oldYRanges[j][0]*(1-frac) + frac*newYRanges[j][0], + oldYRanges[j][1]*(1-frac) + frac*newYRanges[j][1]]); + } + valueRanges[step-1] = thisRange; + } + } + + var that = this; + Dygraph.repeatAndCleanup(function(step) { + if (valueRanges.length) { + for (var i = 0; i < that.axes_.length; i++) { + var w = valueRanges[step][i]; + that.axes_[i].valueWindow = [w[0], w[1]]; + } + } + if (windows.length) { + that.dateWindow_ = windows[step]; + } + that.drawGraph_(); + }, steps, Dygraph.ANIMATION_DURATION / steps, callback); +}; + +/** + * Get the current graph's area object. + * + * Returns: {x, y, w, h} + */ +Dygraph.prototype.getArea = function() { + return this.plotter_.area; +}; + +/** + * Convert a mouse event to DOM coordinates relative to the graph origin. + * + * Returns a two-element array: [X, Y]. + */ +Dygraph.prototype.eventToDomCoords = function(event) { + if (event.offsetX && event.offsetY) { + return [ event.offsetX, event.offsetY ]; + } else { + var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_); + var canvasy = Dygraph.pageY(event) - Dygraph.findPosY(this.mouseEventElement_); + return [canvasx, canvasy]; + } +}; + +/** + * Given a canvas X coordinate, find the closest row. + * @param {Number} domX graph-relative DOM X coordinate + * Returns: row number, integer + * @private + */ +Dygraph.prototype.findClosestRow = function(domX) { + var minDistX = Infinity; + var closestRow = -1; + var sets = this.layout_.points; + for (var i = 0; i < sets.length; i++) { + var points = sets[i]; + var len = points.length; + for (var j = 0; j < len; j++) { + var point = points[j]; + if (!Dygraph.isValidPoint(point, true)) continue; + var dist = Math.abs(point.canvasx - domX); + if (dist < minDistX) { + minDistX = dist; + closestRow = point.idx; + } + } + } + + return closestRow; +}; + +/** + * Given canvas X,Y coordinates, find the closest point. + * + * This finds the individual data point across all visible series + * that's closest to the supplied DOM coordinates using the standard + * Euclidean X,Y distance. + * + * @param {Number} domX graph-relative DOM X coordinate + * @param {Number} domY graph-relative DOM Y coordinate + * Returns: {row, seriesName, point} + * @private + */ +Dygraph.prototype.findClosestPoint = function(domX, domY) { + var minDist = Infinity; + var dist, dx, dy, point, closestPoint, closestSeries, closestRow; + for ( var setIdx = this.layout_.points.length - 1 ; setIdx >= 0 ; --setIdx ) { + var points = this.layout_.points[setIdx]; + for (var i = 0; i < points.length; ++i) { + point = points[i]; + if (!Dygraph.isValidPoint(point)) continue; + dx = point.canvasx - domX; + dy = point.canvasy - domY; + dist = dx * dx + dy * dy; + if (dist < minDist) { + minDist = dist; + closestPoint = point; + closestSeries = setIdx; + closestRow = point.idx; + } + } + } + var name = this.layout_.setNames[closestSeries]; + return { + row: closestRow, + seriesName: name, + point: closestPoint + }; +}; + +/** + * Given canvas X,Y coordinates, find the touched area in a stacked graph. + * + * This first finds the X data point closest to the supplied DOM X coordinate, + * then finds the series which puts the Y coordinate on top of its filled area, + * using linear interpolation between adjacent point pairs. + * + * @param {Number} domX graph-relative DOM X coordinate + * @param {Number} domY graph-relative DOM Y coordinate + * Returns: {row, seriesName, point} + * @private + */ +Dygraph.prototype.findStackedPoint = function(domX, domY) { + var row = this.findClosestRow(domX); + var closestPoint, closestSeries; + for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) { + var boundary = this.getLeftBoundary_(setIdx); + var rowIdx = row - boundary; + var points = this.layout_.points[setIdx]; + if (rowIdx >= points.length) continue; + var p1 = points[rowIdx]; + if (!Dygraph.isValidPoint(p1)) continue; + var py = p1.canvasy; + if (domX > p1.canvasx && rowIdx + 1 < points.length) { + // interpolate series Y value using next point + var p2 = points[rowIdx + 1]; + if (Dygraph.isValidPoint(p2)) { + var dx = p2.canvasx - p1.canvasx; + if (dx > 0) { + var r = (domX - p1.canvasx) / dx; + py += r * (p2.canvasy - p1.canvasy); + } + } + } else if (domX < p1.canvasx && rowIdx > 0) { + // interpolate series Y value using previous point + var p0 = points[rowIdx - 1]; + if (Dygraph.isValidPoint(p0)) { + var dx = p1.canvasx - p0.canvasx; + if (dx > 0) { + var r = (p1.canvasx - domX) / dx; + py += r * (p0.canvasy - p1.canvasy); + } + } + } + // Stop if the point (domX, py) is above this series' upper edge + if (setIdx === 0 || py < domY) { + closestPoint = p1; + closestSeries = setIdx; + } + } + var name = this.layout_.setNames[closestSeries]; + return { + row: row, + seriesName: name, + point: closestPoint + }; +}; + +/** + * When the mouse moves in the canvas, display information about a nearby data + * point and draw dots over those points in the data series. This function + * takes care of cleanup of previously-drawn dots. + * @param {Object} event The mousemove event from the browser. + * @private + */ +Dygraph.prototype.mouseMove_ = function(event) { + // This prevents JS errors when mousing over the canvas before data loads. + var points = this.layout_.points; + if (points === undefined || points === null) return; + + var canvasCoords = this.eventToDomCoords(event); + var canvasx = canvasCoords[0]; + var canvasy = canvasCoords[1]; + + var highlightSeriesOpts = this.attr_("highlightSeriesOpts"); + var selectionChanged = false; + if (highlightSeriesOpts && !this.isSeriesLocked()) { + var closest; + if (this.attr_("stackedGraph")) { + closest = this.findStackedPoint(canvasx, canvasy); + } else { + closest = this.findClosestPoint(canvasx, canvasy); + } + selectionChanged = this.setSelection(closest.row, closest.seriesName); + } else { + var idx = this.findClosestRow(canvasx); + selectionChanged = this.setSelection(idx); + } + + var callback = this.attr_("highlightCallback"); + if (callback && selectionChanged) { + callback(event, + this.lastx_, + this.selPoints_, + this.lastRow_, + this.highlightSet_); + } +}; + +/** + * Fetch left offset from the specified set index or if not passed, the + * first defined boundaryIds record (see bug #236). + * @private + */ +Dygraph.prototype.getLeftBoundary_ = function(setIdx) { + if (this.boundaryIds_[setIdx]) { + return this.boundaryIds_[setIdx][0]; + } else { + for (var i = 0; i < this.boundaryIds_.length; i++) { + if (this.boundaryIds_[i] !== undefined) { + return this.boundaryIds_[i][0]; + } + } + return 0; + } +}; + +Dygraph.prototype.animateSelection_ = function(direction) { + var totalSteps = 10; + var millis = 30; + if (this.fadeLevel === undefined) this.fadeLevel = 0; + if (this.animateId === undefined) this.animateId = 0; + var start = this.fadeLevel; + var steps = direction < 0 ? start : totalSteps - start; + if (steps <= 0) { + if (this.fadeLevel) { + this.updateSelection_(1.0); + } + return; + } + + var thisId = ++this.animateId; + var that = this; + Dygraph.repeatAndCleanup( + function(n) { + // ignore simultaneous animations + if (that.animateId != thisId) return; + + that.fadeLevel += direction; + if (that.fadeLevel === 0) { + that.clearSelection(); + } else { + that.updateSelection_(that.fadeLevel / totalSteps); + } + }, + steps, millis, function() {}); +}; + +/** + * Draw dots over the selectied points in the data series. This function + * takes care of cleanup of previously-drawn dots. + * @private + */ +Dygraph.prototype.updateSelection_ = function(opt_animFraction) { + /*var defaultPrevented = */ + this.cascadeEvents_('select', { + selectedX: this.lastx_, + selectedPoints: this.selPoints_ + }); + // TODO(danvk): use defaultPrevented here? + + // Clear the previously drawn vertical, if there is one + var i; + var ctx = this.canvas_ctx_; + if (this.attr_('highlightSeriesOpts')) { + ctx.clearRect(0, 0, this.width_, this.height_); + var alpha = 1.0 - this.attr_('highlightSeriesBackgroundAlpha'); + if (alpha) { + // Activating background fade includes an animation effect for a gradual + // fade. TODO(klausw): make this independently configurable if it causes + // issues? Use a shared preference to control animations? + var animateBackgroundFade = true; + if (animateBackgroundFade) { + if (opt_animFraction === undefined) { + // start a new animation + this.animateSelection_(1); + return; + } + alpha *= opt_animFraction; + } + ctx.fillStyle = 'rgba(255,255,255,' + alpha + ')'; + ctx.fillRect(0, 0, this.width_, this.height_); + } + + // Redraw only the highlighted series in the interactive canvas (not the + // static plot canvas, which is where series are usually drawn). + this.plotter_._renderLineChart(this.highlightSet_, ctx); + } else if (this.previousVerticalX_ >= 0) { + // Determine the maximum highlight circle size. + var maxCircleSize = 0; + var labels = this.attr_('labels'); + for (i = 1; i < labels.length; i++) { + var r = this.attr_('highlightCircleSize', labels[i]); + if (r > maxCircleSize) maxCircleSize = r; + } + var px = this.previousVerticalX_; + ctx.clearRect(px - maxCircleSize - 1, 0, + 2 * maxCircleSize + 2, this.height_); + } + + if (this.isUsingExcanvas_ && this.currentZoomRectArgs_) { + Dygraph.prototype.drawZoomRect_.apply(this, this.currentZoomRectArgs_); + } + + if (this.selPoints_.length > 0) { + // Draw colored circles over the center of each selected point + var canvasx = this.selPoints_[0].canvasx; + ctx.save(); + for (i = 0; i < this.selPoints_.length; i++) { + var pt = this.selPoints_[i]; + if (!Dygraph.isOK(pt.canvasy)) continue; + + var circleSize = this.attr_('highlightCircleSize', pt.name); + var callback = this.attr_("drawHighlightPointCallback", pt.name); + var color = this.plotter_.colors[pt.name]; + if (!callback) { + callback = Dygraph.Circles.DEFAULT; + } + ctx.lineWidth = this.attr_('strokeWidth', pt.name); + ctx.strokeStyle = color; + ctx.fillStyle = color; + callback(this.g, pt.name, ctx, canvasx, pt.canvasy, + color, circleSize, pt.idx); + } + ctx.restore(); + + this.previousVerticalX_ = canvasx; + } +}; + +/** + * Manually set the selected points and display information about them in the + * legend. The selection can be cleared using clearSelection() and queried + * using getSelection(). + * @param { Integer } row number that should be highlighted (i.e. appear with + * hover dots on the chart). Set to false to clear any selection. + * @param { seriesName } optional series name to highlight that series with the + * the highlightSeriesOpts setting. + * @param { locked } optional If true, keep seriesName selected when mousing + * over the graph, disabling closest-series highlighting. Call clearSelection() + * to unlock it. + */ +Dygraph.prototype.setSelection = function(row, opt_seriesName, opt_locked) { + // Extract the points we've selected + this.selPoints_ = []; + + var changed = false; + if (row !== false && row >= 0) { + if (row != this.lastRow_) changed = true; + this.lastRow_ = row; + for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) { + var points = this.layout_.points[setIdx]; + var setRow = row - this.getLeftBoundary_(setIdx); + if (setRow < points.length) { + var point = points[setRow]; + if (point.yval !== null) this.selPoints_.push(point); + } + } + } else { + if (this.lastRow_ >= 0) changed = true; + this.lastRow_ = -1; + } + + if (this.selPoints_.length) { + this.lastx_ = this.selPoints_[0].xval; + } else { + this.lastx_ = -1; + } + + if (opt_seriesName !== undefined) { + if (this.highlightSet_ !== opt_seriesName) changed = true; + this.highlightSet_ = opt_seriesName; + } + + if (opt_locked !== undefined) { + this.lockedSet_ = opt_locked; + } + + if (changed) { + this.updateSelection_(undefined); + } + return changed; +}; + +/** + * The mouse has left the canvas. Clear out whatever artifacts remain + * @param {Object} event the mouseout event from the browser. + * @private + */ +Dygraph.prototype.mouseOut_ = function(event) { + if (this.attr_("unhighlightCallback")) { + this.attr_("unhighlightCallback")(event); + } + + if (this.attr_("hideOverlayOnMouseOut") && !this.lockedSet_) { + this.clearSelection(); + } +}; + +/** + * Clears the current selection (i.e. points that were highlighted by moving + * the mouse over the chart). + */ +Dygraph.prototype.clearSelection = function() { + this.cascadeEvents_('deselect', {}); + + this.lockedSet_ = false; + // Get rid of the overlay data + if (this.fadeLevel) { + this.animateSelection_(-1); + return; + } + this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_); + this.fadeLevel = 0; + this.selPoints_ = []; + this.lastx_ = -1; + this.lastRow_ = -1; + this.highlightSet_ = null; +}; + +/** + * Returns the number of the currently selected row. To get data for this row, + * you can use the getValue method. + * @return { Integer } row number, or -1 if nothing is selected + */ +Dygraph.prototype.getSelection = function() { + if (!this.selPoints_ || this.selPoints_.length < 1) { + return -1; + } + + for (var setIdx = 0; setIdx < this.layout_.points.length; setIdx++) { + var points = this.layout_.points[setIdx]; + for (var row = 0; row < points.length; row++) { + if (points[row].x == this.selPoints_[0].x) { + return points[row].idx; + } + } + } + return -1; +}; + +/** + * Returns the name of the currently-highlighted series. + * Only available when the highlightSeriesOpts option is in use. + */ +Dygraph.prototype.getHighlightSeries = function() { + return this.highlightSet_; +}; + +/** + * Returns true if the currently-highlighted series was locked + * via setSelection(..., seriesName, true). + */ +Dygraph.prototype.isSeriesLocked = function() { + return this.lockedSet_; +}; + +/** + * Fires when there's data available to be graphed. + * @param {String} data Raw CSV data to be plotted + * @private + */ +Dygraph.prototype.loadedEvent_ = function(data) { + this.rawData_ = this.parseCSV_(data); + this.predraw_(); +}; + +/** + * Add ticks on the x-axis representing years, months, quarters, weeks, or days + * @private + */ +Dygraph.prototype.addXTicks_ = function() { + // Determine the correct ticks scale on the x-axis: quarterly, monthly, ... + var range; + if (this.dateWindow_) { + range = [this.dateWindow_[0], this.dateWindow_[1]]; + } else { + range = this.xAxisExtremes(); + } + + var xAxisOptionsView = this.optionsViewForAxis_('x'); + var xTicks = xAxisOptionsView('ticker')( + range[0], + range[1], + this.width_, // TODO(danvk): should be area.width + xAxisOptionsView, + this); + // var msg = 'ticker(' + range[0] + ', ' + range[1] + ', ' + this.width_ + ', ' + this.attr_('pixelsPerXLabel') + ') -> ' + JSON.stringify(xTicks); + // console.log(msg); + this.layout_.setXTicks(xTicks); +}; + +/** + * @private + * Computes the range of the data series (including confidence intervals). + * @param { [Array] } series either [ [x1, y1], [x2, y2], ... ] or + * [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ... + * @return [low, high] + */ +Dygraph.prototype.extremeValues_ = function(series) { + var minY = null, maxY = null, j, y; + + var bars = this.attr_("errorBars") || this.attr_("customBars"); + if (bars) { + // With custom bars, maxY is the max of the high values. + for (j = 0; j < series.length; j++) { + y = series[j][1][0]; + if (y === null || isNaN(y)) continue; + var low = y - series[j][1][1]; + var high = y + series[j][1][2]; + if (low > y) low = y; // this can happen with custom bars, + if (high < y) high = y; // e.g. in tests/custom-bars.html + if (maxY === null || high > maxY) { + maxY = high; + } + if (minY === null || low < minY) { + minY = low; + } + } + } else { + for (j = 0; j < series.length; j++) { + y = series[j][1]; + if (y === null || isNaN(y)) continue; + if (maxY === null || y > maxY) { + maxY = y; + } + if (minY === null || y < minY) { + minY = y; + } + } + } + + return [minY, maxY]; +}; + +/** + * @private + * This function is called once when the chart's data is changed or the options + * dictionary is updated. It is _not_ called when the user pans or zooms. The + * idea is that values derived from the chart's data can be computed here, + * rather than every time the chart is drawn. This includes things like the + * number of axes, rolling averages, etc. + */ +Dygraph.prototype.predraw_ = function() { + var start = new Date(); + + this.layout_.computePlotArea(); + + // TODO(danvk): move more computations out of drawGraph_ and into here. + this.computeYAxes_(); + + // Create a new plotter. + if (this.plotter_) { + this.cascadeEvents_('clearChart'); + this.plotter_.clear(); + } + + if (!this.is_initial_draw_) { + this.canvas_ctx_.restore(); + this.hidden_ctx_.restore(); + } + + this.canvas_ctx_.save(); + this.hidden_ctx_.save(); + + this.plotter_ = new DygraphCanvasRenderer(this, + this.hidden_, + this.hidden_ctx_, + this.layout_); + + // The roller sits in the bottom left corner of the chart. We don't know where + // this will be until the options are available, so it's positioned here. + this.createRollInterface_(); + + this.cascadeEvents_('predraw'); + + // Convert the raw data (a 2D array) into the internal format and compute + // rolling averages. + this.rolledSeries_ = [null]; // x-axis is the first series and it's special + for (var i = 1; i < this.numColumns(); i++) { + // var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong // konigsberg thinks so too. + var logScale = this.attr_('logscale'); + var series = this.extractSeries_(this.rawData_, i, logScale); + series = this.rollingAverage(series, this.rollPeriod_); + this.rolledSeries_.push(series); + } + + // If the data or options have changed, then we'd better redraw. + this.drawGraph_(); + + // This is used to determine whether to do various animations. + var end = new Date(); + this.drawingTimeMs_ = (end - start); +}; + +/** + * Point structure. + * + * xval_* and yval_* are the original unscaled data values, + * while x_* and y_* are scaled to the range (0.0-1.0) for plotting. + * yval_stacked is the cumulative Y value used for stacking graphs, + * and bottom/top/minus/plus are used for error bar graphs. + * + * @typedef {{ + * idx: number, + * name: string, + * x: ?number, + * xval: ?number, + * y_bottom: ?number, + * y: ?number, + * y_stacked: ?number, + * y_top: ?number, + * yval_minus: ?number, + * yval: ?number, + * yval_plus: ?number, + * yval_stacked + * }} + */ +Dygraph.PointType = undefined; + +// TODO(bhs): these loops are a hot-spot for high-point-count charts. In fact, +// on chrome+linux, they are 6 times more expensive than iterating through the +// points and drawing the lines. The brunt of the cost comes from allocating +// the |point| structures. +/** + * Converts a series to a Point array. + * + * @param {Array.<Array.<(?number|Array<?number>)>} series Array where + * series[row] = [x,y] or [x, [y, err]] or [x, [y, yplus, yminus]]. + * @param {boolean} bars True if error bars or custom bars are being drawn. + * @param {string} setName Name of the series. + * @param {number} boundaryIdStart Index offset of the first point, equal to + * the number of skipped points left of the date window minimum (if any). + * @return {Array.<Dygraph.PointType>} List of points for this series. + */ +Dygraph.seriesToPoints_ = function(series, bars, setName, boundaryIdStart) { + var points = []; + for (var i = 0; i < series.length; ++i) { + var item = series[i]; + var yraw = bars ? item[1][0] : item[1]; + var yval = yraw === null ? null : DygraphLayout.parseFloat_(yraw); + var point = { + x: NaN, + y: NaN, + xval: DygraphLayout.parseFloat_(item[0]), + yval: yval, + name: setName, // TODO(danvk): is this really necessary? + idx: i + boundaryIdStart + }; + + if (bars) { + point.y_top = NaN; + point.y_bottom = NaN; + point.yval_minus = DygraphLayout.parseFloat_(item[1][1]); + point.yval_plus = DygraphLayout.parseFloat_(item[1][2]); + } + points.push(point); + } + return points; +}; + + +/** + * Calculates point stacking for stackedGraph=true. + * + * For stacking purposes, interpolate or extend neighboring data across + * NaN values based on stackedGraphNaNFill settings. This is for display + * only, the underlying data value as shown in the legend remains NaN. + * + * @param {Array.<Dygraph.PointType>} points Point array for a single series. + * Updates each Point's yval_stacked property. + * @param {Array.<number>} cumulativeYval Accumulated top-of-graph stacked Y + * values for the series seen so far. Index is the row number. Updated + * based on the current series's values. + * @param {Array.<number>} seriesExtremes Min and max values, updated + * to reflect the stacked values. + * @param {string} fillMethod Interpolation method, one of 'all', 'inside', or + * 'none'. + */ +Dygraph.stackPoints_ = function( + points, cumulativeYval, seriesExtremes, fillMethod) { + var lastXval = null; + var prevPoint = null; + var nextPoint = null; + var nextPointIdx = -1; + + // Find the next stackable point starting from the given index. + var updateNextPoint = function(idx) { + // If we've previously found a non-NaN point and haven't gone past it yet, + // just use that. + if (nextPointIdx >= idx) return; + + // We haven't found a non-NaN point yet or have moved past it, + // look towards the right to find a non-NaN point. + for (var j = idx; j < points.length; ++j) { + // Clear out a previously-found point (if any) since it's no longer + // valid, we shouldn't use it for interpolation anymore. + nextPoint = null; + if (!isNaN(points[j].yval) && points[j].yval !== null) { + nextPointIdx = j; + nextPoint = points[j]; + break; + } + } + }; + + for (var i = 0; i < points.length; ++i) { + var point = points[i]; + var xval = point.xval; + if (cumulativeYval[xval] === undefined) { + cumulativeYval[xval] = 0; + } + + var actualYval = point.yval; + if (isNaN(actualYval) || actualYval === null) { + // Interpolate/extend for stacking purposes if possible. + updateNextPoint(i); + if (prevPoint && nextPoint && fillMethod != 'none') { + // Use linear interpolation between prevPoint and nextPoint. + actualYval = prevPoint.yval + (nextPoint.yval - prevPoint.yval) * + ((xval - prevPoint.xval) / (nextPoint.xval - prevPoint.xval)); + } else if (prevPoint && fillMethod == 'all') { + actualYval = prevPoint.yval; + } else if (nextPoint && fillMethod == 'all') { + actualYval = nextPoint.yval; + } else { + actualYval = 0; + } + } else { + prevPoint = point; + } + + var stackedYval = cumulativeYval[xval]; + if (lastXval != xval) { + // If an x-value is repeated, we ignore the duplicates. + stackedYval += actualYval; + cumulativeYval[xval] = stackedYval; + } + lastXval = xval; + + point.yval_stacked = stackedYval; + + if (stackedYval > seriesExtremes[1]) { + seriesExtremes[1] = stackedYval; + } + if (stackedYval < seriesExtremes[0]) { + seriesExtremes[0] = stackedYval; + } + } +}; + + +/** + * Loop over all fields and create datasets, calculating extreme y-values for + * each series and extreme x-indices as we go. + * + * dateWindow is passed in as an explicit parameter so that we can compute + * extreme values "speculatively", i.e. without actually setting state on the + * dygraph. + * + * @param {Array.<Array.<Array.<(number|Array<number>)>>} rolledSeries, where + * rolledSeries[seriesIndex][row] = raw point, where + * seriesIndex is the column number starting with 1, and + * rawPoint is [x,y] or [x, [y, err]] or [x, [y, yminus, yplus]]. + * @param {?Array.<number>} dateWindow [xmin, xmax] pair, or null. + * @return {{ + * points: Array.<Array.<Dygraph.PointType>>, + * seriesExtremes: Array.<Array.<number>>, + * boundaryIds: Array.<number>}} + * @private + */ +Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) { + var boundaryIds = []; + var points = []; + var cumulativeYval = []; // For stacked series. + var extremes = {}; // series name -> [low, high] + var i, k; + var errorBars = this.attr_("errorBars"); + var customBars = this.attr_("customBars"); + var bars = errorBars || customBars; + var isValueNull = function(sample) { + if (!bars) { + return sample[1] === null; + } else { + return customBars ? sample[1][1] === null : + errorBars ? sample[1][0] === null : false; + } + }; + + // Loop over the fields (series). Go from the last to the first, + // because if they're stacked that's how we accumulate the values. + var num_series = rolledSeries.length - 1; + var series; + for (i = num_series; i >= 1; i--) { + if (!this.visibility()[i - 1]) continue; + + // Prune down to the desired range, if necessary (for zooming) + // Because there can be lines going to points outside of the visible area, + // we actually prune to visible points, plus one on either side. + if (dateWindow) { + series = rolledSeries[i]; + var low = dateWindow[0]; + var high = dateWindow[1]; + + // TODO(danvk): do binary search instead of linear search. + // TODO(danvk): pass firstIdx and lastIdx directly to the renderer. + var firstIdx = null, lastIdx = null; + for (k = 0; k < series.length; k++) { + if (series[k][0] >= low && firstIdx === null) { + firstIdx = k; + } + if (series[k][0] <= high) { + lastIdx = k; + } + } + + if (firstIdx === null) firstIdx = 0; + var correctedFirstIdx = firstIdx; + var isInvalidValue = true; + while (isInvalidValue && correctedFirstIdx > 0) { + correctedFirstIdx--; + isInvalidValue = isValueNull(series[correctedFirstIdx]); + } + + if (lastIdx === null) lastIdx = series.length - 1; + var correctedLastIdx = lastIdx; + isInvalidValue = true; + while (isInvalidValue && correctedLastIdx < series.length - 1) { + correctedLastIdx++; + isInvalidValue = isValueNull(series[correctedLastIdx]); + } + + + if (correctedFirstIdx!==firstIdx) { + firstIdx = correctedFirstIdx; + } + if (correctedLastIdx !== lastIdx) { + lastIdx = correctedLastIdx; + } + + boundaryIds[i-1] = [firstIdx, lastIdx]; + + // .slice's end is exclusive, we want to include lastIdx. + series = series.slice(firstIdx, lastIdx + 1); + } else { + series = rolledSeries[i]; + boundaryIds[i-1] = [0, series.length-1]; + } + + var seriesName = this.attr_("labels")[i]; + var seriesExtremes = this.extremeValues_(series); + + var seriesPoints = Dygraph.seriesToPoints_( + series, bars, seriesName, boundaryIds[i-1][0]); + + if (this.attr_("stackedGraph")) { + Dygraph.stackPoints_(seriesPoints, cumulativeYval, seriesExtremes, + this.attr_("stackedGraphNaNFill")); + } + + extremes[seriesName] = seriesExtremes; + points[i] = seriesPoints; + } + + return { points: points, extremes: extremes, boundaryIds: boundaryIds }; +}; + +/** + * Update the graph with new data. This method is called when the viewing area + * has changed. If the underlying data or options have changed, predraw_ will + * be called before drawGraph_ is called. + * + * @private + */ +Dygraph.prototype.drawGraph_ = function() { + var start = new Date(); + + // This is used to set the second parameter to drawCallback, below. + var is_initial_draw = this.is_initial_draw_; + this.is_initial_draw_ = false; + + this.layout_.removeAllDatasets(); + this.setColors_(); + this.attrs_.pointSize = 0.5 * this.attr_('highlightCircleSize'); + + var packed = this.gatherDatasets_(this.rolledSeries_, this.dateWindow_); + var points = packed.points; + var extremes = packed.extremes; + this.boundaryIds_ = packed.boundaryIds; + + this.setIndexByName_ = {}; + var labels = this.attr_("labels"); + if (labels.length > 0) { + this.setIndexByName_[labels[0]] = 0; + } + var dataIdx = 0; + for (var i = 1; i < points.length; i++) { + this.setIndexByName_[labels[i]] = i; + if (!this.visibility()[i - 1]) continue; + this.layout_.addDataset(labels[i], points[i]); + this.datasetIndex_[i] = dataIdx++; + } + + this.computeYAxisRanges_(extremes); + this.layout_.setYAxes(this.axes_); + + this.addXTicks_(); + + // Save the X axis zoomed status as the updateOptions call will tend to set it erroneously + var tmp_zoomed_x = this.zoomed_x_; + // Tell PlotKit to use this new data and render itself + this.zoomed_x_ = tmp_zoomed_x; + this.layout_.evaluate(); + this.renderGraph_(is_initial_draw); + + if (this.attr_("timingName")) { + var end = new Date(); + Dygraph.info(this.attr_("timingName") + " - drawGraph: " + (end - start) + "ms"); + } +}; + +/** + * This does the work of drawing the chart. It assumes that the layout and axis + * scales have already been set (e.g. by predraw_). + * + * @private + */ +Dygraph.prototype.renderGraph_ = function(is_initial_draw) { + this.cascadeEvents_('clearChart'); + this.plotter_.clear(); + + if (this.attr_('underlayCallback')) { + // NOTE: we pass the dygraph object to this callback twice to avoid breaking + // users who expect a deprecated form of this callback. + this.attr_('underlayCallback')( + this.hidden_ctx_, this.layout_.getPlotArea(), this, this); + } + + var e = { + canvas: this.hidden_, + drawingContext: this.hidden_ctx_ + }; + this.cascadeEvents_('willDrawChart', e); + this.plotter_.render(); + this.cascadeEvents_('didDrawChart', e); + this.lastRow_ = -1; // because plugins/legend.js clears the legend + + // TODO(danvk): is this a performance bottleneck when panning? + // The interaction canvas should already be empty in that situation. + this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width, + this.canvas_.height); + + if (this.attr_("drawCallback") !== null) { + this.attr_("drawCallback")(this, is_initial_draw); + } +}; + +/** + * @private + * Determine properties of the y-axes which are independent of the data + * currently being displayed. This includes things like the number of axes and + * the style of the axes. It does not include the range of each axis and its + * tick marks. + * This fills in this.axes_. + * axes_ = [ { options } ] + * indices are into the axes_ array. + */ +Dygraph.prototype.computeYAxes_ = function() { + // Preserve valueWindow settings if they exist, and if the user hasn't + // specified a new valueRange. + var valueWindows, axis, index, opts, v; + if (this.axes_ !== undefined && this.user_attrs_.hasOwnProperty("valueRange") === false) { + valueWindows = []; + for (index = 0; index < this.axes_.length; index++) { + valueWindows.push(this.axes_[index].valueWindow); + } + } + + // this.axes_ doesn't match this.attributes_.axes_.options. It's used for + // data computation as well as options storage. + // Go through once and add all the axes. + this.axes_ = []; + + for (axis = 0; axis < this.attributes_.numAxes(); axis++) { + // Add a new axis, making a copy of its per-axis options. + opts = { g : this }; + Dygraph.update(opts, this.attributes_.axisOptions(axis)); + this.axes_[axis] = opts; + } + + + // Copy global valueRange option over to the first axis. + // NOTE(konigsberg): Are these two statements necessary? + // I tried removing it. The automated tests pass, and manually + // messing with tests/zoom.html showed no trouble. + v = this.attr_('valueRange'); + if (v) this.axes_[0].valueRange = v; + + if (valueWindows !== undefined) { + // Restore valueWindow settings. + + // When going from two axes back to one, we only restore + // one axis. + var idxCount = Math.min(valueWindows.length, this.axes_.length); + + for (index = 0; index < idxCount; index++) { + this.axes_[index].valueWindow = valueWindows[index]; + } + } + + for (axis = 0; axis < this.axes_.length; axis++) { + if (axis === 0) { + opts = this.optionsViewForAxis_('y' + (axis ? '2' : '')); + v = opts("valueRange"); + if (v) this.axes_[axis].valueRange = v; + } else { // To keep old behavior + var axes = this.user_attrs_.axes; + if (axes && axes.y2) { + v = axes.y2.valueRange; + if (v) this.axes_[axis].valueRange = v; + } + } + } +}; + +/** + * Returns the number of y-axes on the chart. + * @return {Number} the number of axes. + */ +Dygraph.prototype.numAxes = function() { + return this.attributes_.numAxes(); +}; + +/** + * @private + * Returns axis properties for the given series. + * @param { String } setName The name of the series for which to get axis + * properties, e.g. 'Y1'. + * @return { Object } The axis properties. + */ +Dygraph.prototype.axisPropertiesForSeries = function(series) { + // TODO(danvk): handle errors. + return this.axes_[this.attributes_.axisForSeries(series)]; +}; + +/** + * @private + * Determine the value range and tick marks for each axis. + * @param {Object} extremes A mapping from seriesName -> [low, high] + * This fills in the valueRange and ticks fields in each entry of this.axes_. + */ +Dygraph.prototype.computeYAxisRanges_ = function(extremes) { + var isNullUndefinedOrNaN = function(num) { + return isNaN(parseFloat(num)); + }; + var numAxes = this.attributes_.numAxes(); + var ypadCompat, span, series, ypad; + + var p_axis; + + // Compute extreme values, a span and tick marks for each axis. + for (var i = 0; i < numAxes; i++) { + var axis = this.axes_[i]; + var logscale = this.attributes_.getForAxis("logscale", i); + var includeZero = this.attributes_.getForAxis("includeZero", i); + var independentTicks = this.attributes_.getForAxis("independentTicks", i); + series = this.attributes_.seriesForAxis(i); + + // Add some padding. This supports two Y padding operation modes: + // + // - backwards compatible (yRangePad not set): + // 10% padding for automatic Y ranges, but not for user-supplied + // ranges, and move a close-to-zero edge to zero except if + // avoidMinZero is set, since drawing at the edge results in + // invisible lines. Unfortunately lines drawn at the edge of a + // user-supplied range will still be invisible. If logscale is + // set, add a variable amount of padding at the top but + // none at the bottom. + // + // - new-style (yRangePad set by the user): + // always add the specified Y padding. + // + ypadCompat = true; + ypad = 0.1; // add 10% + if (this.attr_('yRangePad') !== null) { + ypadCompat = false; + // Convert pixel padding to ratio + ypad = this.attr_('yRangePad') / this.plotter_.area.h; + } + + if (series.length === 0) { + // If no series are defined or visible then use a reasonable default + axis.extremeRange = [0, 1]; + } else { + // Calculate the extremes of extremes. + var minY = Infinity; // extremes[series[0]][0]; + var maxY = -Infinity; // extremes[series[0]][1]; + var extremeMinY, extremeMaxY; + + for (var j = 0; j < series.length; j++) { + // this skips invisible series + if (!extremes.hasOwnProperty(series[j])) continue; + + // Only use valid extremes to stop null data series' from corrupting the scale. + extremeMinY = extremes[series[j]][0]; + if (extremeMinY !== null) { + minY = Math.min(extremeMinY, minY); + } + extremeMaxY = extremes[series[j]][1]; + if (extremeMaxY !== null) { + maxY = Math.max(extremeMaxY, maxY); + } + } + + // Include zero if requested by the user. + if (includeZero && !logscale) { + if (minY > 0) minY = 0; + if (maxY < 0) maxY = 0; + } + + // Ensure we have a valid scale, otherwise default to [0, 1] for safety. + if (minY == Infinity) minY = 0; + if (maxY == -Infinity) maxY = 1; + + span = maxY - minY; + // special case: if we have no sense of scale, center on the sole value. + if (span === 0) { + if (maxY !== 0) { + span = Math.abs(maxY); + } else { + // ... and if the sole value is zero, use range 0-1. + maxY = 1; + span = 1; + } + } + + var maxAxisY, minAxisY; + if (logscale) { + if (ypadCompat) { + maxAxisY = maxY + ypad * span; + minAxisY = minY; + } else { + var logpad = Math.exp(Math.log(span) * ypad); + maxAxisY = maxY * logpad; + minAxisY = minY / logpad; + } + } else { + maxAxisY = maxY + ypad * span; + minAxisY = minY - ypad * span; + + // Backwards-compatible behavior: Move the span to start or end at zero if it's + // close to zero, but not if avoidMinZero is set. + if (ypadCompat && !this.attr_("avoidMinZero")) { + if (minAxisY < 0 && minY >= 0) minAxisY = 0; + if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0; + } + } + axis.extremeRange = [minAxisY, maxAxisY]; + } + if (axis.valueWindow) { + // This is only set if the user has zoomed on the y-axis. It is never set + // by a user. It takes precedence over axis.valueRange because, if you set + // valueRange, you'd still expect to be able to pan. + axis.computedValueRange = [axis.valueWindow[0], axis.valueWindow[1]]; + } else if (axis.valueRange) { + // This is a user-set value range for this axis. + var y0 = isNullUndefinedOrNaN(axis.valueRange[0]) ? axis.extremeRange[0] : axis.valueRange[0]; + var y1 = isNullUndefinedOrNaN(axis.valueRange[1]) ? axis.extremeRange[1] : axis.valueRange[1]; + if (!ypadCompat) { + if (axis.logscale) { + var logpad = Math.exp(Math.log(span) * ypad); + y0 *= logpad; + y1 /= logpad; + } else { + span = y1 - y0; + y0 -= span * ypad; + y1 += span * ypad; + } + } + axis.computedValueRange = [y0, y1]; + } else { + axis.computedValueRange = axis.extremeRange; + } + + + if (independentTicks) { + axis.independentTicks = independentTicks; + var opts = this.optionsViewForAxis_('y' + (i ? '2' : '')); + var ticker = opts('ticker'); + axis.ticks = ticker(axis.computedValueRange[0], + axis.computedValueRange[1], + this.height_, // TODO(danvk): should be area.height + opts, + this); + // Define the first independent axis as primary axis. + if (!p_axis) p_axis = axis; + } + } + if (p_axis === undefined) { + throw ("Configuration Error: At least one axis has to have the \"independentTicks\" option activated."); + } + // Add ticks. By default, all axes inherit the tick positions of the + // primary axis. However, if an axis is specifically marked as having + // independent ticks, then that is permissible as well. + for (var i = 0; i < numAxes; i++) { + var axis = this.axes_[i]; + + if (!axis.independentTicks) { + var opts = this.optionsViewForAxis_('y' + (i ? '2' : '')); + var ticker = opts('ticker'); + var p_ticks = p_axis.ticks; + var p_scale = p_axis.computedValueRange[1] - p_axis.computedValueRange[0]; + var scale = axis.computedValueRange[1] - axis.computedValueRange[0]; + var tick_values = []; + for (var k = 0; k < p_ticks.length; k++) { + var y_frac = (p_ticks[k].v - p_axis.computedValueRange[0]) / p_scale; + var y_val = axis.computedValueRange[0] + y_frac * scale; + tick_values.push(y_val); + } + + axis.ticks = ticker(axis.computedValueRange[0], + axis.computedValueRange[1], + this.height_, // TODO(danvk): should be area.height + opts, + this, + tick_values); + } + } +}; + +/** + * Extracts one series from the raw data (a 2D array) into an array of (date, + * value) tuples. + * + * This is where undesirable points (i.e. negative values on log scales and + * missing values through which we wish to connect lines) are dropped. + * TODO(danvk): the "missing values" bit above doesn't seem right. + * + * @private + * @param {Array.<Array.<(number|Array<Number>)>>} rawData Input data. Rectangular + * grid of points, where rawData[row][0] is the X value for the row, + * and rawData[row][i] is the Y data for series #i. + * @param {number} i Series index, starting from 1. + * @param {boolean} logScale True if using logarithmic Y scale. + * @return {Array.<Array.<(?number|Array<?number>)>} Series array, where + * series[row] = [x,y] or [x, [y, err]] or [x, [y, yplus, yminus]]. + */ +Dygraph.prototype.extractSeries_ = function(rawData, i, logScale) { + // TODO(danvk): pre-allocate series here. + var series = []; + var errorBars = this.attr_("errorBars"); + var customBars = this.attr_("customBars"); + for (var j = 0; j < rawData.length; j++) { + var x = rawData[j][0]; + var point = rawData[j][i]; + if (logScale) { + // On the log scale, points less than zero do not exist. + // This will create a gap in the chart. + if (errorBars || customBars) { + // point.length is either 2 (errorBars) or 3 (customBars) + for (var k = 0; k < point.length; k++) { + if (point[k] <= 0) { + point = null; + break; + } + } + } else if (point <= 0) { + point = null; + } + } + // Fix null points to fit the display type standard. + if (point !== null) { + series.push([x, point]); + } else { + series.push([x, errorBars ? [null, null] : customBars ? [null, null, null] : point]); + } + } + return series; +}; + +/** + * @private + * Calculates the rolling average of a data set. + * If originalData is [label, val], rolls the average of those. + * If originalData is [label, [, it's interpreted as [value, stddev] + * and the roll is returned in the same form, with appropriately reduced + * stddev for each value. + * Note that this is where fractional input (i.e. '5/10') is converted into + * decimal values. + * @param {Array} originalData The data in the appropriate format (see above) + * @param {Number} rollPeriod The number of points over which to average the + * data + */ +Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) { + rollPeriod = Math.min(rollPeriod, originalData.length); + var rollingData = []; + var sigma = this.attr_("sigma"); + + var low, high, i, j, y, sum, num_ok, stddev; + if (this.fractions_) { + var num = 0; + var den = 0; // numerator/denominator + var mult = 100.0; + for (i = 0; i < originalData.length; i++) { + num += originalData[i][1][0]; + den += originalData[i][1][1]; + if (i - rollPeriod >= 0) { + num -= originalData[i - rollPeriod][1][0]; + den -= originalData[i - rollPeriod][1][1]; + } + + var date = originalData[i][0]; + var value = den ? num / den : 0.0; + if (this.attr_("errorBars")) { + if (this.attr_("wilsonInterval")) { + // For more details on this confidence interval, see: + // http://en.wikipedia.org/wiki/Binomial_confidence_interval + if (den) { + var p = value < 0 ? 0 : value, n = den; + var pm = sigma * Math.sqrt(p*(1-p)/n + sigma*sigma/(4*n*n)); + var denom = 1 + sigma * sigma / den; + low = (p + sigma * sigma / (2 * den) - pm) / denom; + high = (p + sigma * sigma / (2 * den) + pm) / denom; + rollingData[i] = [date, + [p * mult, (p - low) * mult, (high - p) * mult]]; + } else { + rollingData[i] = [date, [0, 0, 0]]; + } + } else { + stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0; + rollingData[i] = [date, [mult * value, mult * stddev, mult * stddev]]; + } + } else { + rollingData[i] = [date, mult * value]; + } + } + } else if (this.attr_("customBars")) { + low = 0; + var mid = 0; + high = 0; + var count = 0; + for (i = 0; i < originalData.length; i++) { + var data = originalData[i][1]; + y = data[1]; + rollingData[i] = [originalData[i][0], [y, y - data[0], data[2] - y]]; + + if (y !== null && !isNaN(y)) { + low += data[0]; + mid += y; + high += data[2]; + count += 1; + } + if (i - rollPeriod >= 0) { + var prev = originalData[i - rollPeriod]; + if (prev[1][1] !== null && !isNaN(prev[1][1])) { + low -= prev[1][0]; + mid -= prev[1][1]; + high -= prev[1][2]; + count -= 1; + } + } + if (count) { + rollingData[i] = [originalData[i][0], [ 1.0 * mid / count, + 1.0 * (mid - low) / count, + 1.0 * (high - mid) / count ]]; + } else { + rollingData[i] = [originalData[i][0], [null, null, null]]; + } + } + } else { + // Calculate the rolling average for the first rollPeriod - 1 points where + // there is not enough data to roll over the full number of points + if (!this.attr_("errorBars")) { + if (rollPeriod == 1) { + return originalData; + } + + for (i = 0; i < originalData.length; i++) { + sum = 0; + num_ok = 0; + for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { + y = originalData[j][1]; + if (y === null || isNaN(y)) continue; + num_ok++; + sum += originalData[j][1]; + } + if (num_ok) { + rollingData[i] = [originalData[i][0], sum / num_ok]; + } else { + rollingData[i] = [originalData[i][0], null]; + } + } + + } else { + for (i = 0; i < originalData.length; i++) { + sum = 0; + var variance = 0; + num_ok = 0; + for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { + y = originalData[j][1][0]; + if (y === null || isNaN(y)) continue; + num_ok++; + sum += originalData[j][1][0]; + variance += Math.pow(originalData[j][1][1], 2); + } + if (num_ok) { + stddev = Math.sqrt(variance) / num_ok; + rollingData[i] = [originalData[i][0], + [sum / num_ok, sigma * stddev, sigma * stddev]]; + } else { + // This explicitly preserves NaNs to aid with "independent series". + // See testRollingAveragePreservesNaNs. + var v = (rollPeriod == 1) ? originalData[i][1][0] : null; + rollingData[i] = [originalData[i][0], [v, v, v]]; + } + } + } + } + + return rollingData; +}; + +/** + * Detects the type of the str (date or numeric) and sets the various + * formatting attributes in this.attrs_ based on this type. + * @param {String} str An x value. + * @private + */ +Dygraph.prototype.detectTypeFromString_ = function(str) { + var isDate = false; + var dashPos = str.indexOf('-'); // could be 2006-01-01 _or_ 1.0e-2 + if ((dashPos > 0 && (str[dashPos-1] != 'e' && str[dashPos-1] != 'E')) || + str.indexOf('/') >= 0 || + isNaN(parseFloat(str))) { + isDate = true; + } else if (str.length == 8 && str > '19700101' && str < '20371231') { + // TODO(danvk): remove support for this format. + isDate = true; + } + + this.setXAxisOptions_(isDate); +}; + +Dygraph.prototype.setXAxisOptions_ = function(isDate) { + if (isDate) { + this.attrs_.xValueParser = Dygraph.dateParser; + this.attrs_.axes.x.valueFormatter = Dygraph.dateString_; + this.attrs_.axes.x.ticker = Dygraph.dateTicker; + this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisFormatter; + } else { + /** @private (shut up, jsdoc!) */ + this.attrs_.xValueParser = function(x) { return parseFloat(x); }; + // TODO(danvk): use Dygraph.numberValueFormatter here? + /** @private (shut up, jsdoc!) */ + this.attrs_.axes.x.valueFormatter = function(x) { return x; }; + this.attrs_.axes.x.ticker = Dygraph.numericLinearTicks; + this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter; + } +}; + +/** + * Parses the value as a floating point number. This is like the parseFloat() + * built-in, but with a few differences: + * - the empty string is parsed as null, rather than NaN. + * - if the string cannot be parsed at all, an error is logged. + * If the string can't be parsed, this method returns null. + * @param {String} x The string to be parsed + * @param {Number} opt_line_no The line number from which the string comes. + * @param {String} opt_line The text of the line from which the string comes. + * @private + */ + +// Parse the x as a float or return null if it's not a number. +Dygraph.prototype.parseFloat_ = function(x, opt_line_no, opt_line) { + var val = parseFloat(x); + if (!isNaN(val)) return val; + + // Try to figure out what happeend. + // If the value is the empty string, parse it as null. + if (/^ *$/.test(x)) return null; + + // If it was actually "NaN", return it as NaN. + if (/^ *nan *$/i.test(x)) return NaN; + + // Looks like a parsing error. + var msg = "Unable to parse '" + x + "' as a number"; + if (opt_line !== null && opt_line_no !== null) { + msg += " on line " + (1+opt_line_no) + " ('" + opt_line + "') of CSV."; + } + this.error(msg); + + return null; +}; + +/** + * @private + * Parses a string in a special csv format. We expect a csv file where each + * line is a date point, and the first field in each line is the date string. + * We also expect that all remaining fields represent series. + * if the errorBars attribute is set, then interpret the fields as: + * date, series1, stddev1, series2, stddev2, ... + * @param {[Object]} data See above. + * + * @return [Object] An array with one entry for each row. These entries + * are an array of cells in that row. The first entry is the parsed x-value for + * the row. The second, third, etc. are the y-values. These can take on one of + * three forms, depending on the CSV and constructor parameters: + * 1. numeric value + * 2. [ value, stddev ] + * 3. [ low value, center value, high value ] + */ +Dygraph.prototype.parseCSV_ = function(data) { + var ret = []; + var line_delimiter = Dygraph.detectLineDelimiter(data); + var lines = data.split(line_delimiter || "\n"); + var vals, j; + + // Use the default delimiter or fall back to a tab if that makes sense. + var delim = this.attr_('delimiter'); + if (lines[0].indexOf(delim) == -1 && lines[0].indexOf('\t') >= 0) { + delim = '\t'; + } + + var start = 0; + if (!('labels' in this.user_attrs_)) { + // User hasn't explicitly set labels, so they're (presumably) in the CSV. + start = 1; + this.attrs_.labels = lines[0].split(delim); // NOTE: _not_ user_attrs_. + this.attributes_.reparseSeries(); + } + var line_no = 0; + + var xParser; + var defaultParserSet = false; // attempt to auto-detect x value type + var expectedCols = this.attr_("labels").length; + var outOfOrder = false; + for (var i = start; i < lines.length; i++) { + var line = lines[i]; + line_no = i; + if (line.length === 0) continue; // skip blank lines + if (line[0] == '#') continue; // skip comment lines + var inFields = line.split(delim); + if (inFields.length < 2) continue; + + var fields = []; + if (!defaultParserSet) { + this.detectTypeFromString_(inFields[0]); + xParser = this.attr_("xValueParser"); + defaultParserSet = true; + } + fields[0] = xParser(inFields[0], this); + + // If fractions are expected, parse the numbers as "A/B" + if (this.fractions_) { + for (j = 1; j < inFields.length; j++) { + // TODO(danvk): figure out an appropriate way to flag parse errors. + vals = inFields[j].split("/"); + if (vals.length != 2) { + this.error('Expected fractional "num/den" values in CSV data ' + + "but found a value '" + inFields[j] + "' on line " + + (1 + i) + " ('" + line + "') which is not of this form."); + fields[j] = [0, 0]; + } else { + fields[j] = [this.parseFloat_(vals[0], i, line), + this.parseFloat_(vals[1], i, line)]; + } + } + } else if (this.attr_("errorBars")) { + // If there are error bars, values are (value, stddev) pairs + if (inFields.length % 2 != 1) { + this.error('Expected alternating (value, stdev.) pairs in CSV data ' + + 'but line ' + (1 + i) + ' has an odd number of values (' + + (inFields.length - 1) + "): '" + line + "'"); + } + for (j = 1; j < inFields.length; j += 2) { + fields[(j + 1) / 2] = [this.parseFloat_(inFields[j], i, line), + this.parseFloat_(inFields[j + 1], i, line)]; + } + } else if (this.attr_("customBars")) { + // Bars are a low;center;high tuple + for (j = 1; j < inFields.length; j++) { + var val = inFields[j]; + if (/^ *$/.test(val)) { + fields[j] = [null, null, null]; + } else { + vals = val.split(";"); + if (vals.length == 3) { + fields[j] = [ this.parseFloat_(vals[0], i, line), + this.parseFloat_(vals[1], i, line), + this.parseFloat_(vals[2], i, line) ]; + } else { + this.warn('When using customBars, values must be either blank ' + + 'or "low;center;high" tuples (got "' + val + + '" on line ' + (1+i)); + } + } + } + } else { + // Values are just numbers + for (j = 1; j < inFields.length; j++) { + fields[j] = this.parseFloat_(inFields[j], i, line); + } + } + if (ret.length > 0 && fields[0] < ret[ret.length - 1][0]) { + outOfOrder = true; + } + + if (fields.length != expectedCols) { + this.error("Number of columns in line " + i + " (" + fields.length + + ") does not agree with number of labels (" + expectedCols + + ") " + line); + } + + // If the user specified the 'labels' option and none of the cells of the + // first row parsed correctly, then they probably double-specified the + // labels. We go with the values set in the option, discard this row and + // log a warning to the JS console. + if (i === 0 && this.attr_('labels')) { + var all_null = true; + for (j = 0; all_null && j < fields.length; j++) { + if (fields[j]) all_null = false; + } + if (all_null) { + this.warn("The dygraphs 'labels' option is set, but the first row of " + + "CSV data ('" + line + "') appears to also contain labels. " + + "Will drop the CSV labels and use the option labels."); + continue; + } + } + ret.push(fields); + } + + if (outOfOrder) { + this.warn("CSV is out of order; order it correctly to speed loading."); + ret.sort(function(a,b) { return a[0] - b[0]; }); + } + + return ret; +}; + +/** + * @private + * The user has provided their data as a pre-packaged JS array. If the x values + * are numeric, this is the same as dygraphs' internal format. If the x values + * are dates, we need to convert them from Date objects to ms since epoch. + * @param {[Object]} data + * @return {[Object]} data with numeric x values. + */ +Dygraph.prototype.parseArray_ = function(data) { + // Peek at the first x value to see if it's numeric. + if (data.length === 0) { + this.error("Can't plot empty data set"); + return null; + } + if (data[0].length === 0) { + this.error("Data set cannot contain an empty row"); + return null; + } + + var i; + if (this.attr_("labels") === null) { + this.warn("Using default labels. Set labels explicitly via 'labels' " + + "in the options parameter"); + this.attrs_.labels = [ "X" ]; + for (i = 1; i < data[0].length; i++) { + this.attrs_.labels.push("Y" + i); // Not user_attrs_. + } + this.attributes_.reparseSeries(); + } else { + var num_labels = this.attr_("labels"); + if (num_labels.length != data[0].length) { + this.error("Mismatch between number of labels (" + num_labels + + ") and number of columns in array (" + data[0].length + ")"); + return null; + } + } + + if (Dygraph.isDateLike(data[0][0])) { + // Some intelligent defaults for a date x-axis. + this.attrs_.axes.x.valueFormatter = Dygraph.dateString_; + this.attrs_.axes.x.ticker = Dygraph.dateTicker; + this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisFormatter; + + // Assume they're all dates. + var parsedData = Dygraph.clone(data); + for (i = 0; i < data.length; i++) { + if (parsedData[i].length === 0) { + this.error("Row " + (1 + i) + " of data is empty"); + return null; + } + if (parsedData[i][0] === null || + typeof(parsedData[i][0].getTime) != 'function' || + isNaN(parsedData[i][0].getTime())) { + this.error("x value in row " + (1 + i) + " is not a Date"); + return null; + } + parsedData[i][0] = parsedData[i][0].getTime(); + } + return parsedData; + } else { + // Some intelligent defaults for a numeric x-axis. + /** @private (shut up, jsdoc!) */ + this.attrs_.axes.x.valueFormatter = function(x) { return x; }; + this.attrs_.axes.x.ticker = Dygraph.numericLinearTicks; + this.attrs_.axes.x.axisLabelFormatter = Dygraph.numberAxisLabelFormatter; + return data; + } +}; + +/** + * Parses a DataTable object from gviz. + * The data is expected to have a first column that is either a date or a + * number. All subsequent columns must be numbers. If there is a clear mismatch + * between this.xValueParser_ and the type of the first column, it will be + * fixed. Fills out rawData_. + * @param {[Object]} data See above. + * @private + */ +Dygraph.prototype.parseDataTable_ = function(data) { + var shortTextForAnnotationNum = function(num) { + // converts [0-9]+ [A-Z][a-z]* + // example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab + // and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz + var shortText = String.fromCharCode(65 /* A */ + num % 26); + num = Math.floor(num / 26); + while ( num > 0 ) { + shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26 ) + shortText.toLowerCase(); + num = Math.floor((num - 1) / 26); + } + return shortText; + }; + + var cols = data.getNumberOfColumns(); + var rows = data.getNumberOfRows(); + + var indepType = data.getColumnType(0); + if (indepType == 'date' || indepType == 'datetime') { + this.attrs_.xValueParser = Dygraph.dateParser; + this.attrs_.axes.x.valueFormatter = Dygraph.dateString_; + this.attrs_.axes.x.ticker = Dygraph.dateTicker; + this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisFormatter; + } else if (indepType == 'number') { + this.attrs_.xValueParser = function(x) { return parseFloat(x); }; + this.attrs_.axes.x.valueFormatter = function(x) { return x; }; + this.attrs_.axes.x.ticker = Dygraph.numericLinearTicks; + this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter; + } else { + this.error("only 'date', 'datetime' and 'number' types are supported for " + + "column 1 of DataTable input (Got '" + indepType + "')"); + return null; + } + + // Array of the column indices which contain data (and not annotations). + var colIdx = []; + var annotationCols = {}; // data index -> [annotation cols] + var hasAnnotations = false; + var i, j; + for (i = 1; i < cols; i++) { + var type = data.getColumnType(i); + if (type == 'number') { + colIdx.push(i); + } else if (type == 'string' && this.attr_('displayAnnotations')) { + // This is OK -- it's an annotation column. + var dataIdx = colIdx[colIdx.length - 1]; + if (!annotationCols.hasOwnProperty(dataIdx)) { + annotationCols[dataIdx] = [i]; + } else { + annotationCols[dataIdx].push(i); + } + hasAnnotations = true; + } else { + this.error("Only 'number' is supported as a dependent type with Gviz." + + " 'string' is only supported if displayAnnotations is true"); + } + } + + // Read column labels + // TODO(danvk): add support back for errorBars + var labels = [data.getColumnLabel(0)]; + for (i = 0; i < colIdx.length; i++) { + labels.push(data.getColumnLabel(colIdx[i])); + if (this.attr_("errorBars")) i += 1; + } + this.attrs_.labels = labels; + cols = labels.length; + + var ret = []; + var outOfOrder = false; + var annotations = []; + for (i = 0; i < rows; i++) { + var row = []; + if (typeof(data.getValue(i, 0)) === 'undefined' || + data.getValue(i, 0) === null) { + this.warn("Ignoring row " + i + + " of DataTable because of undefined or null first column."); + continue; + } + + if (indepType == 'date' || indepType == 'datetime') { + row.push(data.getValue(i, 0).getTime()); + } else { + row.push(data.getValue(i, 0)); + } + if (!this.attr_("errorBars")) { + for (j = 0; j < colIdx.length; j++) { + var col = colIdx[j]; + row.push(data.getValue(i, col)); + if (hasAnnotations && + annotationCols.hasOwnProperty(col) && + data.getValue(i, annotationCols[col][0]) !== null) { + var ann = {}; + ann.series = data.getColumnLabel(col); + ann.xval = row[0]; + ann.shortText = shortTextForAnnotationNum(annotations.length); + ann.text = ''; + for (var k = 0; k < annotationCols[col].length; k++) { + if (k) ann.text += "\n"; + ann.text += data.getValue(i, annotationCols[col][k]); + } + annotations.push(ann); + } + } + + // Strip out infinities, which give dygraphs problems later on. + for (j = 0; j < row.length; j++) { + if (!isFinite(row[j])) row[j] = null; + } + } else { + for (j = 0; j < cols - 1; j++) { + row.push([ data.getValue(i, 1 + 2 * j), data.getValue(i, 2 + 2 * j) ]); + } + } + if (ret.length > 0 && row[0] < ret[ret.length - 1][0]) { + outOfOrder = true; + } + ret.push(row); + } + + if (outOfOrder) { + this.warn("DataTable is out of order; order it correctly to speed loading."); + ret.sort(function(a,b) { return a[0] - b[0]; }); + } + this.rawData_ = ret; + + if (annotations.length > 0) { + this.setAnnotations(annotations, true); + } + this.attributes_.reparseSeries(); +}; + +/** + * Get the CSV data. If it's in a function, call that function. If it's in a + * file, do an XMLHttpRequest to get it. + * @private + */ +Dygraph.prototype.start_ = function() { + var data = this.file_; + + // Functions can return references of all other types. + if (typeof data == 'function') { + data = data(); + } + + if (Dygraph.isArrayLike(data)) { + this.rawData_ = this.parseArray_(data); + this.predraw_(); + } else if (typeof data == 'object' && + typeof data.getColumnRange == 'function') { + // must be a DataTable from gviz. + this.parseDataTable_(data); + this.predraw_(); + } else if (typeof data == 'string') { + // Heuristic: a newline means it's CSV data. Otherwise it's an URL. + var line_delimiter = Dygraph.detectLineDelimiter(data); + if (line_delimiter) { + this.loadedEvent_(data); + } else { + var req = new XMLHttpRequest(); + var caller = this; + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status === 200 || // Normal http + req.status === 0) { // Chrome w/ --allow-file-access-from-files + caller.loadedEvent_(req.responseText); + } + } + }; + + req.open("GET", data, true); + req.send(null); + } + } else { + this.error("Unknown data format: " + (typeof data)); + } +}; + +/** + * Changes various properties of the graph. These can include: + * <ul> + * <li>file: changes the source data for the graph</li> + * <li>errorBars: changes whether the data contains stddev</li> + * </ul> + * + * There's a huge variety of options that can be passed to this method. For a + * full list, see http://dygraphs.com/options.html. + * + * @param {Object} attrs The new properties and values + * @param {Boolean} [block_redraw] Usually the chart is redrawn after every + * call to updateOptions(). If you know better, you can pass true to explicitly + * block the redraw. This can be useful for chaining updateOptions() calls, + * avoiding the occasional infinite loop and preventing redraws when it's not + * necessary (e.g. when updating a callback). + */ +Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) { + if (typeof(block_redraw) == 'undefined') block_redraw = false; + + // mapLegacyOptions_ drops the "file" parameter as a convenience to us. + var file = input_attrs.file; + var attrs = Dygraph.mapLegacyOptions_(input_attrs); + + // TODO(danvk): this is a mess. Move these options into attr_. + if ('rollPeriod' in attrs) { + this.rollPeriod_ = attrs.rollPeriod; + } + if ('dateWindow' in attrs) { + this.dateWindow_ = attrs.dateWindow; + if (!('isZoomedIgnoreProgrammaticZoom' in attrs)) { + this.zoomed_x_ = (attrs.dateWindow !== null); + } + } + if ('valueRange' in attrs && !('isZoomedIgnoreProgrammaticZoom' in attrs)) { + this.zoomed_y_ = (attrs.valueRange !== null); + } + + // TODO(danvk): validate per-series options. + // Supported: + // strokeWidth + // pointSize + // drawPoints + // highlightCircleSize + + // Check if this set options will require new points. + var requiresNewPoints = Dygraph.isPixelChangingOptionList(this.attr_("labels"), attrs); + + Dygraph.updateDeep(this.user_attrs_, attrs); + + this.attributes_.reparseSeries(); + + if (file) { + this.file_ = file; + if (!block_redraw) this.start_(); + } else { + if (!block_redraw) { + if (requiresNewPoints) { + this.predraw_(); + } else { + this.renderGraph_(false); + } + } + } +}; + +/** + * Returns a copy of the options with deprecated names converted into current + * names. Also drops the (potentially-large) 'file' attribute. If the caller is + * interested in that, they should save a copy before calling this. + * @private + */ +Dygraph.mapLegacyOptions_ = function(attrs) { + var my_attrs = {}; + for (var k in attrs) { + if (k == 'file') continue; + if (attrs.hasOwnProperty(k)) my_attrs[k] = attrs[k]; + } + + var set = function(axis, opt, value) { + if (!my_attrs.axes) my_attrs.axes = {}; + if (!my_attrs.axes[axis]) my_attrs.axes[axis] = {}; + my_attrs.axes[axis][opt] = value; + }; + var map = function(opt, axis, new_opt) { + if (typeof(attrs[opt]) != 'undefined') { + Dygraph.warn("Option " + opt + " is deprecated. Use the " + + new_opt + " option for the " + axis + " axis instead. " + + "(e.g. { axes : { " + axis + " : { " + new_opt + " : ... } } } " + + "(see http://dygraphs.com/per-axis.html for more information."); + set(axis, new_opt, attrs[opt]); + delete my_attrs[opt]; + } + }; + + // This maps, e.g., xValueFormater -> axes: { x: { valueFormatter: ... } } + map('xValueFormatter', 'x', 'valueFormatter'); + map('pixelsPerXLabel', 'x', 'pixelsPerLabel'); + map('xAxisLabelFormatter', 'x', 'axisLabelFormatter'); + map('xTicker', 'x', 'ticker'); + map('yValueFormatter', 'y', 'valueFormatter'); + map('pixelsPerYLabel', 'y', 'pixelsPerLabel'); + map('yAxisLabelFormatter', 'y', 'axisLabelFormatter'); + map('yTicker', 'y', 'ticker'); + return my_attrs; +}; + +/** + * Resizes the dygraph. If no parameters are specified, resizes to fill the + * containing div (which has presumably changed size since the dygraph was + * instantiated. If the width/height are specified, the div will be resized. + * + * This is far more efficient than destroying and re-instantiating a + * Dygraph, since it doesn't have to reparse the underlying data. + * + * @param {Number} [width] Width (in pixels) + * @param {Number} [height] Height (in pixels) + */ +Dygraph.prototype.resize = function(width, height) { + if (this.resize_lock) { + return; + } + this.resize_lock = true; + + if ((width === null) != (height === null)) { + this.warn("Dygraph.resize() should be called with zero parameters or " + + "two non-NULL parameters. Pretending it was zero."); + width = height = null; + } + + var old_width = this.width_; + var old_height = this.height_; + + if (width) { + this.maindiv_.style.width = width + "px"; + this.maindiv_.style.height = height + "px"; + this.width_ = width; + this.height_ = height; + } else { + this.width_ = this.maindiv_.clientWidth; + this.height_ = this.maindiv_.clientHeight; + } + + if (old_width != this.width_ || old_height != this.height_) { + // Resizing a canvas erases it, even when the size doesn't change, so + // any resize needs to be followed by a redraw. + this.resizeElements_(); + this.predraw_(); + } + + this.resize_lock = false; +}; + +/** + * Adjusts the number of points in the rolling average. Updates the graph to + * reflect the new averaging period. + * @param {Number} length Number of points over which to average the data. + */ +Dygraph.prototype.adjustRoll = function(length) { + this.rollPeriod_ = length; + this.predraw_(); +}; + +/** + * Returns a boolean array of visibility statuses. + */ +Dygraph.prototype.visibility = function() { + // Do lazy-initialization, so that this happens after we know the number of + // data series. + if (!this.attr_("visibility")) { + this.attrs_.visibility = []; + } + // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs. + while (this.attr_("visibility").length < this.numColumns() - 1) { + this.attrs_.visibility.push(true); + } + return this.attr_("visibility"); +}; + +/** + * Changes the visiblity of a series. + */ +Dygraph.prototype.setVisibility = function(num, value) { + var x = this.visibility(); + if (num < 0 || num >= x.length) { + this.warn("invalid series number in setVisibility: " + num); + } else { + x[num] = value; + this.predraw_(); + } +}; + +/** + * How large of an area will the dygraph render itself in? + * This is used for testing. + * @return A {width: w, height: h} object. + * @private + */ +Dygraph.prototype.size = function() { + return { width: this.width_, height: this.height_ }; +}; + +/** + * Update the list of annotations and redraw the chart. + * See dygraphs.com/annotations.html for more info on how to use annotations. + * @param ann {Array} An array of annotation objects. + * @param suppressDraw {Boolean} Set to "true" to block chart redraw (optional). + */ +Dygraph.prototype.setAnnotations = function(ann, suppressDraw) { + // Only add the annotation CSS rule once we know it will be used. + Dygraph.addAnnotationRule(); + this.annotations_ = ann; + if (!this.layout_) { + this.warn("Tried to setAnnotations before dygraph was ready. " + + "Try setting them in a drawCallback. See " + + "dygraphs.com/tests/annotation.html"); + return; + } + + this.layout_.setAnnotations(this.annotations_); + if (!suppressDraw) { + this.predraw_(); + } +}; + +/** + * Return the list of annotations. + */ +Dygraph.prototype.annotations = function() { + return this.annotations_; +}; + +/** + * Get the list of label names for this graph. The first column is the + * x-axis, so the data series names start at index 1. + * + * Returns null when labels have not yet been defined. + */ +Dygraph.prototype.getLabels = function() { + var labels = this.attr_("labels"); + return labels ? labels.slice() : null; +}; + +/** + * Get the index of a series (column) given its name. The first column is the + * x-axis, so the data series start with index 1. + */ +Dygraph.prototype.indexFromSetName = function(name) { + return this.setIndexByName_[name]; +}; + +/** + * Get the internal dataset index given its name. These are numbered starting from 0, + * and only count visible sets. + * @private + */ +Dygraph.prototype.datasetIndexFromSetName_ = function(name) { + return this.datasetIndex_[this.indexFromSetName(name)]; +}; + +/** + * @private + * Adds a default style for the annotation CSS classes to the document. This is + * only executed when annotations are actually used. It is designed to only be + * called once -- all calls after the first will return immediately. + */ +Dygraph.addAnnotationRule = function() { + // TODO(danvk): move this function into plugins/annotations.js? + if (Dygraph.addedAnnotationCSS) return; + + var rule = "border: 1px solid black; " + + "background-color: white; " + + "text-align: center;"; + + var styleSheetElement = document.createElement("style"); + styleSheetElement.type = "text/css"; + document.getElementsByTagName("head")[0].appendChild(styleSheetElement); + + // Find the first style sheet that we can access. + // We may not add a rule to a style sheet from another domain for security + // reasons. This sometimes comes up when using gviz, since the Google gviz JS + // adds its own style sheets from google.com. + for (var i = 0; i < document.styleSheets.length; i++) { + if (document.styleSheets[i].disabled) continue; + var mysheet = document.styleSheets[i]; + try { + if (mysheet.insertRule) { // Firefox + var idx = mysheet.cssRules ? mysheet.cssRules.length : 0; + mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }", idx); + } else if (mysheet.addRule) { // IE + mysheet.addRule(".dygraphDefaultAnnotation", rule); + } + Dygraph.addedAnnotationCSS = true; + return; + } catch(err) { + // Was likely a security exception. + } + } + + this.warn("Unable to add default annotation CSS rule; display may be off."); +}; + +// Older pages may still use this name. +var DateGraph = Dygraph; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/excanvas.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/excanvas.js new file mode 100644 index 00000000..367764b4 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/excanvas.js @@ -0,0 +1,924 @@ +// 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) +// * Non uniform scaling does not correctly scale strokes. +// * Optimize. There is always room for speed improvements. + +// Only add this code if we do not already have a canvas implementation +if (!document.createElement('canvas').getContext) { + +(function() { + + // alias some functions to make (compiled) code shorter + var m = Math; + var mr = m.round; + var ms = m.sin; + var mc = m.cos; + var abs = m.abs; + var sqrt = m.sqrt; + + // this is used for sub pixel precision + var Z = 10; + var Z2 = Z / 2; + + /** + * This funtion is assigned to the <canvas> elements as element.getContext(). + * @this {HTMLElement} + * @return {CanvasRenderingContext2D_} + */ + function getContext() { + return this.context_ || + (this.context_ = new CanvasRenderingContext2D_(this)); + } + + var slice = Array.prototype.slice; + + /** + * Binds a function to an object. The returned function will always use the + * passed in {@code obj} as {@code this}. + * + * Example: + * + * g = bind(f, obj, a, b) + * g(c, d) // will do f.call(obj, a, b, c, d) + * + * @param {Function} f The function to bind the object to + * @param {Object} obj The object that should act as this when the function + * is called + * @param {*} var_args Rest arguments that will be used as the initial + * arguments when the function is called + * @return {Function} A new function that has bound this + */ + function bind(f, obj, var_args) { + var a = slice.call(arguments, 2); + return function() { + return f.apply(obj, a.concat(slice.call(arguments))); + }; + } + + var G_vmlCanvasManager_ = { + init: function(opt_doc) { + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + var doc = opt_doc || document; + // Create a dummy element so that IE will allow canvas elements to be + // recognized. + doc.createElement('canvas'); + doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); + } + }, + + init_: function(doc) { + // create xmlns + if (!doc.namespaces['g_vml_']) { + doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', + '#default#VML'); + + } + if (!doc.namespaces['g_o_']) { + doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', + '#default#VML'); + } + + // Setup default CSS. Only add one style sheet per document + if (!doc.styleSheets['ex_canvas_']) { + var ss = doc.createStyleSheet(); + ss.owningElement.id = 'ex_canvas_'; + 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)}' + + 'g_o_\\:*{behavior:url(#default#VML)}'; + + } + + // find all canvas elements + var els = doc.getElementsByTagName('canvas'); + for (var i = 0; i < els.length; i++) { + this.initElement(els[i]); + } + }, + + /** + * 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) { + if (!el.getContext) { + + el.getContext = getContext; + + // Remove fallback content. There is no way to hide text nodes so we + // just remove all childNodes. We could hide all elements and remove + // text nodes but who really cares about the fallback content. + el.innerHTML = ''; + + // 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.globalAlpha = o1.globalAlpha; + o2.arcScaleX_ = o1.arcScaleX_; + o2.arcScaleY_ = o1.arcScaleY_; + o2.lineScale_ = o1.lineScale_; + } + + 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 {color: str, alpha: 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; + this.lineScale_ = 1; + } + + var contextPrototype = CanvasRenderingContext2D_.prototype; + contextPrototype.clearRect = function() { + this.element_.innerHTML = ''; + }; + + 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) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.lineTo = function(aX, aY) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); + + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY) { + var p = this.getCoords_(aX, aY); + var cp1 = this.getCoords_(aCP1x, aCP1y); + var cp2 = this.getCoords_(aCP2x, aCP2y); + bezierCurveTo(this, cp1, cp2, p); + }; + + // Helper function that takes the already fixed cordinates. + function bezierCurveTo(self, cp1, cp2, p) { + self.currentPath_.push({ + type: 'bezierCurveTo', + cp1x: cp1.x, + cp1y: cp1.y, + cp2x: cp2.x, + cp2y: cp2.y, + x: p.x, + y: p.y + }); + self.currentX_ = p.x; + self.currentY_ = p.y; + } + + 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 cp = this.getCoords_(aCPx, aCPy); + var p = this.getCoords_(aX, aY); + + var cp1 = { + x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), + y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) + }; + var cp2 = { + x: cp1.x + (p.x - this.currentX_) / 3.0, + y: cp1.y + (p.y - this.currentY_) / 3.0 + }; + + bezierCurveTo(this, cp1, cp2, p); + }; + + 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 + } + + var p = this.getCoords_(aX, aY); + var pStart = this.getCoords_(xStart, yStart); + var pEnd = this.getCoords_(xEnd, yEnd); + + this.currentPath_.push({type: arcType, + x: p.x, + y: p.y, + radius: aRadius, + xStart: pStart.x, + yStart: pStart.y, + xEnd: pEnd.x, + yEnd: pEnd.y}); + + }; + + 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) { + var oldPath = this.currentPath_; + 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(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { + var oldPath = this.currentPath_; + 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(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { + var gradient = new CanvasGradient_('gradient'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + return gradient; + }; + + contextPrototype.createRadialGradient = function(aX0, aY0, aR0, + aX1, aY1, aR1) { + var gradient = new CanvasGradient_('gradientradial'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.r0_ = aR0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + gradient.r1_ = aR1; + 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 Error('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, 'px;height:', H, 'px;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 = m.max(max.x, c2.x, c3.x, c4.x); + max.y = m.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, 'px;', + ' height:', Z * dh, 'px;"', + ' 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.color; + var opacity = a.alpha * this.globalAlpha; + + var W = 10; + var H = 10; + + lineStr.push('<g_vml_:shape', + ' filled="', !!aFill, '"', + ' style="position:absolute;width:', W, 'px;height:', H, 'px;"', + ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"', + ' stroked="', !aFill, '"', + ' 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]; + var c; + + switch (p.type) { + case 'moveTo': + c = p; + lineStr.push(' m ', mr(p.x), ',', mr(p.y)); + break; + case 'lineTo': + lineStr.push(' l ', mr(p.x), ',', mr(p.y)); + break; + case 'close': + lineStr.push(' x '); + p = null; + break; + case 'bezierCurveTo': + lineStr.push(' c ', + mr(p.cp1x), ',', mr(p.cp1y), ',', + mr(p.cp2x), ',', mr(p.cp2y), ',', + mr(p.x), ',', mr(p.y)); + break; + case 'at': + case 'wa': + lineStr.push(' ', p.type, ' ', + mr(p.x - this.arcScaleX_ * p.radius), ',', + mr(p.y - this.arcScaleY_ * p.radius), ' ', + mr(p.x + this.arcScaleX_ * p.radius), ',', + mr(p.y + this.arcScaleY_ * p.radius), ' ', + mr(p.xStart), ',', mr(p.yStart), ' ', + mr(p.xEnd), ',', mr(p.yEnd)); + break; + } + + + // TODO: Following is broken for curves due to + // move to proper paths. + + // Figure out dimensions so we can do gradient fills + // properly + if (p) { + if (min.x == null || p.x < min.x) { + min.x = p.x; + } + if (max.x == null || p.x > max.x) { + max.x = p.x; + } + if (min.y == null || p.y < min.y) { + min.y = p.y; + } + if (max.y == null || p.y > max.y) { + max.y = p.y; + } + } + } + lineStr.push(' ">'); + + if (!aFill) { + var lineWidth = this.lineScale_ * this.lineWidth; + + // VML cannot correctly render a line if the width is less than 1px. + // In that case, we dilute the color to make the line look thinner. + if (lineWidth < 1) { + opacity *= lineWidth; + } + + lineStr.push( + '<g_vml_:stroke', + ' opacity="', opacity, '"', + ' joinstyle="', this.lineJoin, '"', + ' miterlimit="', this.miterLimit, '"', + ' endcap="', processLineCap(this.lineCap), '"', + ' weight="', lineWidth, 'px"', + ' color="', color, '" />' + ); + } else if (typeof this.fillStyle == 'object') { + var fillStyle = this.fillStyle; + var angle = 0; + var focus = {x: 0, y: 0}; + + // additional offset + var shift = 0; + // scale factor for offset + var expansion = 1; + + if (fillStyle.type_ == 'gradient') { + var x0 = fillStyle.x0_ / this.arcScaleX_; + var y0 = fillStyle.y0_ / this.arcScaleY_; + var x1 = fillStyle.x1_ / this.arcScaleX_; + var y1 = fillStyle.y1_ / this.arcScaleY_; + var p0 = this.getCoords_(x0, y0); + var p1 = this.getCoords_(x1, y1); + var dx = p1.x - p0.x; + var dy = p1.y - p0.y; + angle = Math.atan2(dx, dy) * 180 / Math.PI; + + // The angle should be a non-negative number. + if (angle < 0) { + angle += 360; + } + + // Very small angles produce an unexpected result because they are + // converted to a scientific notation string. + if (angle < 1e-6) { + angle = 0; + } + } else { + var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); + var width = max.x - min.x; + var height = max.y - min.y; + focus = { + x: (p0.x - min.x) / width, + y: (p0.y - min.y) / height + }; + + width /= this.arcScaleX_ * Z; + height /= this.arcScaleY_ * Z; + var dimension = m.max(width, height); + shift = 2 * fillStyle.r0_ / dimension; + expansion = 2 * fillStyle.r1_ / dimension - shift; + } + + // We need to sort the color stops in ascending order by offset, + // otherwise IE won't interpret it correctly. + var stops = fillStyle.colors_; + stops.sort(function(cs1, cs2) { + return cs1.offset - cs2.offset; + }); + + var length = stops.length; + var color1 = stops[0].color; + var color2 = stops[length - 1].color; + var opacity1 = stops[0].alpha * this.globalAlpha; + var opacity2 = stops[length - 1].alpha * this.globalAlpha; + + var colors = []; + for (var i = 0; i < length; i++) { + var stop = stops[i]; + colors.push(stop.offset * expansion + shift + ' ' + stop.color); + } + + // When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', + ' method="none" focus="100%"', + ' color="', color1, '"', + ' color2="', color2, '"', + ' colors="', colors.join(','), '"', + ' opacity="', opacity2, '"', + ' g_o_:opacity2="', opacity1, '"', + ' angle="', angle, '"', + ' focusposition="', focus.x, ',', focus.y, '" />'); + } else { + lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, + '" />'); + } + + lineStr.push('</g_vml_:shape>'); + + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); + }; + + contextPrototype.fill = function() { + this.stroke(true); + } + + contextPrototype.closePath = function() { + this.currentPath_.push({type: 'close'}); + }; + + /** + * @private + */ + contextPrototype.getCoords_ = function(aX, aY) { + var m = this.m_; + return { + x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, + y: Z * (aX * m[0][1] + aY * m[1][1] + 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(); + }; + + function matrixIsFinite(m) { + for (var j = 0; j < 3; j++) { + for (var k = 0; k < 2; k++) { + if (!isFinite(m[j][k]) || isNaN(m[j][k])) { + return false; + } + } + } + return true; + } + + function setM(ctx, m, updateLineScale) { + if (!matrixIsFinite(m)) { + return; + } + ctx.m_ = m; + + if (updateLineScale) { + // Get the line scale. + // Determinant of this.m_ means how much the area is enlarged by the + // transformation. So its square root can be used as a scale factor + // for width. + var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; + ctx.lineScale_ = sqrt(abs(det)); + } + } + + contextPrototype.translate = function(aX, aY) { + var m1 = [ + [1, 0, 0], + [0, 1, 0], + [aX, aY, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.rotate = function(aRot) { + var c = mc(aRot); + var s = ms(aRot); + + var m1 = [ + [c, s, 0], + [-s, c, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.scale = function(aX, aY) { + this.arcScaleX_ *= aX; + this.arcScaleY_ *= aY; + var m1 = [ + [aX, 0, 0], + [0, aY, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { + var m1 = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { + var m = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, m, true); + }; + + /******** 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.x0_ = 0; + this.y0_ = 0; + this.r0_ = 0; + this.x1_ = 0; + this.y1_ = 0; + this.r1_ = 0; + this.colors_ = []; + } + + CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { + aColor = processStyle(aColor); + this.colors_.push({offset: aOffset, + color: aColor.color, + alpha: aColor.alpha}); + }; + + function CanvasPattern_() {} + + // set up externs + G_vmlCanvasManager = G_vmlCanvasManager_; + CanvasRenderingContext2D = CanvasRenderingContext2D_; + CanvasGradient = CanvasGradient_; + CanvasPattern = CanvasPattern_; + +})(); + +} // if diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction.js new file mode 100644 index 00000000..23847845 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction.js @@ -0,0 +1,333 @@ + function moveV3(event, g, context) { + if (context.isPanning) { + Dygraph.movePan(event, g, context); + } else if (context.isZooming) { + Dygraph.moveZoom(event, g, context); + } + } + + function upV3(event, g, context) { + if (context.isPanning) { + Dygraph.endPan(event, g, context); + } else if (context.isZooming) { + Dygraph.endZoom(event, g, context); + } + } + +// Take the offset of a mouse event on the dygraph canvas and +// convert it to a pair of percentages from the bottom left. +// (Not top left, bottom is where the lower value is.) +function offsetToPercentage(g, offsetX, offsetY) { + // This is calculating the pixel offset of the leftmost date. + var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0]; + var yar0 = g.yAxisRange(0); + + // This is calculating the pixel of the higest value. (Top pixel) + var yOffset = g.toDomCoords(null, yar0[1])[1]; + + // x y w and h are relative to the corner of the drawing area, + // so that the upper corner of the drawing area is (0, 0). + var x = offsetX - xOffset; + var y = offsetY - yOffset; + + // This is computing the rightmost pixel, effectively defining the + // width. + var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset; + + // This is computing the lowest pixel, effectively defining the height. + var h = g.toDomCoords(null, yar0[0])[1] - yOffset; + + // Percentage from the left. + var xPct = w == 0 ? 0 : (x / w); + // Percentage from the top. + var yPct = h == 0 ? 0 : (y / h); + + // The (1-) part below changes it from "% distance down from the top" + // to "% distance up from the bottom". + return [xPct, (1-yPct)]; +} + +function dblClickV3(event, g, context) { + // Reducing by 20% makes it 80% the original size, which means + // to restore to original size it must grow by 25% + /* + if (!(event.offsetX && event.offsetY)){ + event.offsetX = event.layerX - event.target.offsetLeft; + event.offsetY = event.layerY - event.target.offsetTop; + } + + var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); + var xPct = percentages[0]; + var yPct = percentages[1]; + + if (event.ctrlKey) { + zoom(g, -.25, xPct, yPct); + } else { + zoom(g, +.2, xPct, yPct); + } + */ + restorePositioning(g); +} + +var lastClickedGraph = null; + +function clickV3(event, g, context) { + lastClickedGraph = g; + Dygraph.cancelEvent(event); +} + +function scrollV3(event, g, context) { + if (lastClickedGraph != g) { + return; + } + var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40; + // For me the normalized value shows 0.075 for one click. If I took + // that verbatim, it would be a 7.5%. + var percentage = normal / 50; + + if (!(event.offsetX && event.offsetY)){ + event.offsetX = event.layerX - event.target.offsetLeft; + event.offsetY = event.layerY - event.target.offsetTop; + } + + var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); + var xPct = percentages[0]; + var yPct = percentages[1]; + + zoom(g, percentage, xPct, yPct); + Dygraph.cancelEvent(event); +} + +// Adjusts [x, y] toward each other by zoomInPercentage% +// Split it so the left/bottom axis gets xBias/yBias of that change and +// tight/top gets (1-xBias)/(1-yBias) of that change. +// +// If a bias is missing it splits it down the middle. +function zoom(g, zoomInPercentage, xBias, yBias) { + xBias = xBias || 0.5; + yBias = yBias || 0.5; + function adjustAxis(axis, zoomInPercentage, bias) { + var delta = axis[1] - axis[0]; + var increment = delta * zoomInPercentage; + var foo = [increment * bias, increment * (1-bias)]; + return [ axis[0] + foo[0], axis[1] - foo[1] ]; + } + var yAxes = g.yAxisRanges(); + var newYAxes = []; + for (var i = 0; i < yAxes.length; i++) { + newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias); + } + + g.updateOptions({ + dateWindow: adjustAxis(g.xAxisRange(), zoomInPercentage, xBias), + valueRange: newYAxes[0] + }); +} + +function restorePositioning(g) { + g.updateOptions({ + dateWindow: null, + valueRange: null + }); +} + +function zoom_custom(res) { + var w = g.xAxisRange(); + desired_range = [w[0] - res * 1000, w[0] ]; + animate(); +} + +function reset() { + desired_range = orig_range; + animate(); +} + +var click = 0; +var desired_range = null; +function approach_range() { + if (!desired_range) return; + // go halfway there + var range = g.xAxisRange(); + if (Math.abs(desired_range[0] - range[0]) < 60 && + Math.abs(desired_range[1] - range[1]) < 60) { + if(desired_range[0]>=orig_range[0]) + g.updateOptions({dateWindow: desired_range}); + else { + g.updateOptions({dateWindow: orig_range}); + click = 8; + } + // (do not set another timeout.) + } else { + var new_range; + new_range = [0.5 * (desired_range[0] + range[0]), + 0.5 * (desired_range[1] + range[1])]; + g.updateOptions({dateWindow: new_range}); + animate(); + } +} + +function animate() { + setTimeout(approach_range, 50); +} + + +var v4Active = false; +var v4Canvas = null; + +function downV4(event, g, context) { + context.initializeMouseDown(event, g, context); + + Dygraph.Interaction.startTouch(event, g, context); + + Dygraph.Interaction.moveTouch(event, g, context); + + Dygraph.Interaction.endTouch(event, g, context); + + v4Active = true; + moveV4(event, g, context); // in case the mouse went down on a data point. +} + +var processed = []; + +function moveV4(event, g, context) { + var RANGE = 7; + + if (v4Active) { + var graphPos = Dygraph.findPos(g.graphDiv); + var canvasx = Dygraph.pageX(event) - graphPos.x; + var canvasy = Dygraph.pageY(event) - graphPos.y; + + var rows = g.numRows(); + // Row layout: + // [date, [val1, stdev1], [val2, stdev2]] + for (var row = 0; row < rows; row++) { + var date = g.getValue(row, 0); + var x = g.toDomCoords(date, null)[0]; + var diff = Math.abs(canvasx - x); + if (diff < RANGE) { + for (var col = 1; col < 3; col++) { + // TODO(konigsberg): these will throw exceptions as data is removed. + var vals = g.getValue(row, col); + if (vals == null) { continue; } + var val = vals[0]; + var y = g.toDomCoords(null, val)[1]; + var diff2 = Math.abs(canvasy - y); + if (diff2 < RANGE) { + var found = false; + for (var i in processed) { + var stored = processed[i]; + if(stored[0] == row && stored[1] == col) { + found = true; + break; + } + } + if (!found) { + //processed.push([row, col]); + //drawV4(x, y); + } + return; + } + } + } + } + } +} + +function upV4(event, g, context) { + if (v4Active) { + v4Active = false; + } +} + +function dblClickV4(event, g, context) { + restorePositioning(g); +} + +function drawV4(x, y) { + var ctx = v4Canvas; + + ctx.strokeStyle = "#000000"; + //ctx.fillStyle = "#FFFF00"; + ctx.fillStyle = "#FF0000"; + ctx.beginPath(); + ctx.arc(x,y,5,0,Math.PI*2,true); + ctx.closePath(); + ctx.stroke(); + ctx.fill(); +} + +function captureCanvas(canvas, area, g) { + v4Canvas = canvas; +} + +function newDygraphTouchstart(event, g, context) { +// This right here is what prevents IOS from doing its own zoom/touch behavior +// It stops the node from being selected too +event.preventDefault(); // touch browsers are all nice. + +if (event.touches.length > 1) { +// If the user ever puts two fingers down, it's not a double tap. +context.startTimeForDoubleTapMs = null; +} + +var touches = []; +for (var i = 0; i < event.touches.length; i++) { +var t = event.touches[i]; +// we dispense with 'dragGetX_' because all touchBrowsers support pageX +touches.push({ +pageX: t.pageX, +pageY: t.pageY, +dataX: g.toDataXCoord(t.pageX), +dataY: g.toDataYCoord(t.pageY) +// identifier: t.identifier +}); +} +context.initialTouches = touches; + +if (touches.length == 1) { +// This is just a swipe. +context.initialPinchCenter = touches[0]; +context.touchDirections = { x: true, y: true }; + +// ADDITION - this needs to select the points +//var closestTouchP = g.findClosestPoint(touches[0].pageX,touches[0].pageY); +//if(closestTouchP) { +//var selectionChanged = g.setSelection(closestTouchP.row, closestTouchP.seriesName); +//} +g.mouseMove_(event); + +} else if (touches.length >= 2) { +// It's become a pinch! +// In case there are 3+ touches, we ignore all but the "first" two. + +// only screen coordinates can be averaged (data coords could be log scale). +context.initialPinchCenter = { +pageX: 0.5 * (touches[0].pageX + touches[1].pageX), +pageY: 0.5 * (touches[0].pageY + touches[1].pageY), + +// TODO(danvk): remove +dataX: 0.5 * (touches[0].dataX + touches[1].dataX), +dataY: 0.5 * (touches[0].dataY + touches[1].dataY) +}; + +// Make pinches in a 45-degree swath around either axis 1-dimensional zooms. +var initialAngle = 180 / Math.PI * Math.atan2( +context.initialPinchCenter.pageY - touches[0].pageY, +touches[0].pageX - context.initialPinchCenter.pageX); + +// use symmetry to get it into the first quadrant. +initialAngle = Math.abs(initialAngle); +if (initialAngle > 90) initialAngle = 90 - initialAngle; + +context.touchDirections = { +x: (initialAngle < (90 - 45/2)), +y: (initialAngle > 45/2) +}; +} + +// save the full x & y ranges. +context.initialRange = { +x: g.xAxisRange(), +y: g.yAxisRange() +}; +}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction.min.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction.min.js new file mode 100644 index 00000000..e3f736a8 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction.min.js @@ -0,0 +1 @@ +function moveV3(e,t,n){if(n.isPanning){Dygraph.movePan(e,t,n)}else if(n.isZooming){Dygraph.moveZoom(e,t,n)}}function upV3(e,t,n){if(n.isPanning){Dygraph.endPan(e,t,n)}else if(n.isZooming){Dygraph.endZoom(e,t,n)}}function offsetToPercentage(e,t,n){var r=e.toDomCoords(e.xAxisRange()[0],null)[0];var i=e.yAxisRange(0);var s=e.toDomCoords(null,i[1])[1];var o=t-r;var u=n-s;var a=e.toDomCoords(e.xAxisRange()[1],null)[0]-r;var f=e.toDomCoords(null,i[0])[1]-s;var l=a==0?0:o/a;var c=f==0?0:u/f;return[l,1-c]}function dblClickV3(e,t,n){restorePositioning(t)}function clickV3(e,t,n){lastClickedGraph=t;Dygraph.cancelEvent(e)}function scrollV3(e,t,n){if(lastClickedGraph!=t){return}var r=e.detail?e.detail*-1:e.wheelDelta/40;var i=r/50;if(!(e.offsetX&&e.offsetY)){e.offsetX=e.layerX-e.target.offsetLeft;e.offsetY=e.layerY-e.target.offsetTop}var s=offsetToPercentage(t,e.offsetX,e.offsetY);var o=s[0];var u=s[1];zoom(t,i,o,u);Dygraph.cancelEvent(e)}function zoom(e,t,n,r){function i(e,t,n){var r=e[1]-e[0];var i=r*t;var s=[i*n,i*(1-n)];return[e[0]+s[0],e[1]-s[1]]}n=n||.5;r=r||.5;var s=e.yAxisRanges();var o=[];for(var u=0;u<s.length;u++){o[u]=i(s[u],t,r)}e.updateOptions({dateWindow:i(e.xAxisRange(),t,n),valueRange:o[0]})}function restorePositioning(e){e.updateOptions({dateWindow:null,valueRange:null})}function zoom_custom(e){var t=g.xAxisRange();desired_range=[t[0]-e*1e3,t[0]];animate()}function reset(){desired_range=orig_range;animate()}function approach_range(){if(!desired_range)return;var e=g.xAxisRange();if(Math.abs(desired_range[0]-e[0])<60&&Math.abs(desired_range[1]-e[1])<60){if(desired_range[0]>=orig_range[0])g.updateOptions({dateWindow:desired_range});else{g.updateOptions({dateWindow:orig_range});click=8}}else{var t;t=[.5*(desired_range[0]+e[0]),.5*(desired_range[1]+e[1])];g.updateOptions({dateWindow:t});animate()}}function animate(){setTimeout(approach_range,50)}function downV4(e,t,n){n.initializeMouseDown(e,t,n);Dygraph.Interaction.startTouch(e,t,n);Dygraph.Interaction.moveTouch(e,t,n);Dygraph.Interaction.endTouch(e,t,n);v4Active=true;moveV4(e,t,n)}function moveV4(e,t,n){var r=7;if(v4Active){var i=Dygraph.findPos(t.graphDiv);var s=Dygraph.pageX(e)-i.x;var o=Dygraph.pageY(e)-i.y;var u=t.numRows();for(var a=0;a<u;a++){var f=t.getValue(a,0);var l=t.toDomCoords(f,null)[0];var c=Math.abs(s-l);if(c<r){for(var h=1;h<3;h++){var p=t.getValue(a,h);if(p==null){continue}var d=p[0];var v=t.toDomCoords(null,d)[1];var m=Math.abs(o-v);if(m<r){var g=false;for(var y in processed){var b=processed[y];if(b[0]==a&&b[1]==h){g=true;break}}if(!g){}return}}}}}}function upV4(e,t,n){if(v4Active){v4Active=false}}function dblClickV4(e,t,n){restorePositioning(t)}function drawV4(e,t){var n=v4Canvas;n.strokeStyle="#000000";n.fillStyle="#FF0000";n.beginPath();n.arc(e,t,5,0,Math.PI*2,true);n.closePath();n.stroke();n.fill()}function captureCanvas(e,t,n){v4Canvas=e}function newDygraphTouchstart(e,t,n){e.preventDefault();if(e.touches.length>1){n.startTimeForDoubleTapMs=null}var r=[];for(var i=0;i<e.touches.length;i++){var s=e.touches[i];r.push({pageX:s.pageX,pageY:s.pageY,dataX:t.toDataXCoord(s.pageX),dataY:t.toDataYCoord(s.pageY)})}n.initialTouches=r;if(r.length==1){n.initialPinchCenter=r[0];n.touchDirections={x:true,y:true};t.mouseMove_(e)}else if(r.length>=2){n.initialPinchCenter={pageX:.5*(r[0].pageX+r[1].pageX),pageY:.5*(r[0].pageY+r[1].pageY),dataX:.5*(r[0].dataX+r[1].dataX),dataY:.5*(r[0].dataY+r[1].dataY)};var o=180/Math.PI*Math.atan2(n.initialPinchCenter.pageY-r[0].pageY,r[0].pageX-n.initialPinchCenter.pageX);o=Math.abs(o);if(o>90)o=90-o;n.touchDirections={x:o<90-45/2,y:o>45/2}}n.initialRange={x:t.xAxisRange(),y:t.yAxisRange()}}var lastClickedGraph=null;var click=0;var desired_range=null;var v4Active=false;var v4Canvas=null;var processed=[];
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction_sun.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction_sun.js new file mode 100644 index 00000000..92a0dbd7 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/interaction_sun.js @@ -0,0 +1,303 @@ +// Code for a variety of interaction models. Used in interaction.html, but split out from +// that file so they can be tested in isolation. +// +function downV3(event, g, context) { + context.initializeMouseDown(event, g, context); + if (event.altKey || event.shiftKey) { + Dygraph.startZoom(event, g, context); + } else { + Dygraph.startPan(event, g, context); + } +} + +function moveV3(event, g, context) { + if (context.isPanning) { + Dygraph.movePan(event, g, context); + } else if (context.isZooming) { + Dygraph.moveZoom(event, g, context); + } +} + +function upV3(event, g, context) { + if (context.isPanning) { + Dygraph.endPan(event, g, context); + } else if (context.isZooming) { + Dygraph.endZoom(event, g, context); + } +} + +// Take the offset of a mouse event on the dygraph canvas and +// convert it to a pair of percentages from the bottom left. +// (Not top left, bottom is where the lower value is.) +function offsetToPercentage(g, offsetX, offsetY) { + // This is calculating the pixel offset of the leftmost date. + var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0]; + var yar0 = g.yAxisRange(0); + + // This is calculating the pixel of the higest value. (Top pixel) + var yOffset = g.toDomCoords(null, yar0[1])[1]; + + // x y w and h are relative to the corner of the drawing area, + // so that the upper corner of the drawing area is (0, 0). + var x = offsetX - xOffset; + var y = offsetY - yOffset; + + // This is computing the rightmost pixel, effectively defining the + // width. + var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset; + + // This is computing the lowest pixel, effectively defining the height. + var h = g.toDomCoords(null, yar0[0])[1] - yOffset; + + // Percentage from the left. + var xPct = w == 0 ? 0 : (x / w); + // Percentage from the top. + var yPct = h == 0 ? 0 : (y / h); + + // The (1-) part below changes it from "% distance down from the top" + // to "% distance up from the bottom". + return [xPct, (1-yPct)]; +} + +function dblClickV3(event, g, context) { + // Reducing by 20% makes it 80% the original size, which means + // to restore to original size it must grow by 25% + + if (!(event.offsetX && event.offsetY)){ + event.offsetX = event.layerX - event.target.offsetLeft; + event.offsetY = event.layerY - event.target.offsetTop; + } + + var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); + var xPct = percentages[0]; + var yPct = percentages[1]; + + if (event.ctrlKey) { + zoom(g, -.25, xPct, yPct); + } else { + zoom(g, +.2, xPct, yPct); + } +} + +var lastClickedGraph = null; + +function clickV3(event, g, context) { + lastClickedGraph = g; + Dygraph.cancelEvent(event); +} + +function scrollV3(event, g, context) { + if (lastClickedGraph != g) { + return; + } + var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40; + // For me the normalized value shows 0.075 for one click. If I took + // that verbatim, it would be a 7.5%. + var percentage = normal / 50; + + if (!(event.offsetX && event.offsetY)){ + event.offsetX = event.layerX - event.target.offsetLeft; + event.offsetY = event.layerY - event.target.offsetTop; + } + + var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); + var xPct = percentages[0]; + var yPct = percentages[1]; + + zoom(g, percentage, xPct, yPct); + Dygraph.cancelEvent(event); +} + +// Adjusts [x, y] toward each other by zoomInPercentage% +// Split it so the left/bottom axis gets xBias/yBias of that change and +// tight/top gets (1-xBias)/(1-yBias) of that change. +// +// If a bias is missing it splits it down the middle. +function zoom(g, zoomInPercentage, xBias, yBias) { + xBias = xBias || 0.5; + yBias = yBias || 0.5; + function adjustAxis(axis, zoomInPercentage, bias) { + var delta = axis[1] - axis[0]; + var increment = delta * zoomInPercentage; + var foo = [increment * bias, increment * (1-bias)]; + return [ axis[0] + foo[0], axis[1] - foo[1] ]; + } + var yAxes = g.yAxisRanges(); + var newYAxes = []; + for (var i = 0; i < yAxes.length; i++) { + newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias); + } + + g.updateOptions({ + dateWindow: adjustAxis(g.xAxisRange(), zoomInPercentage, xBias), + valueRange: newYAxes[0] + }); +} + +var v4Active = false; +var v4Canvas = null; + +function downV4(event, g, context) { + context.initializeMouseDown(event, g, context); + + Dygraph.Interaction.startTouch(event, g, context); + + Dygraph.Interaction.moveTouch(event, g, context); + + Dygraph.Interaction.endTouch(event, g, context); + + v4Active = true; + moveV4(event, g, context); // in case the mouse went down on a data point. +} + +var processed = []; + +function moveV4(event, g, context) { + var RANGE = 7; + + if (v4Active) { + var graphPos = Dygraph.findPos(g.graphDiv); + var canvasx = Dygraph.pageX(event) - graphPos.x; + var canvasy = Dygraph.pageY(event) - graphPos.y; + + var rows = g.numRows(); + // Row layout: + // [date, [val1, stdev1], [val2, stdev2]] + for (var row = 0; row < rows; row++) { + var date = g.getValue(row, 0); + var x = g.toDomCoords(date, null)[0]; + var diff = Math.abs(canvasx - x); + if (diff < RANGE) { + for (var col = 1; col < 3; col++) { + // TODO(konigsberg): these will throw exceptions as data is removed. + var vals = g.getValue(row, col); + if (vals == null) { continue; } + var val = vals[0]; + var y = g.toDomCoords(null, val)[1]; + var diff2 = Math.abs(canvasy - y); + if (diff2 < RANGE) { + var found = false; + for (var i in processed) { + var stored = processed[i]; + if(stored[0] == row && stored[1] == col) { + found = true; + break; + } + } + if (!found) { + //processed.push([row, col]); + //drawV4(x, y); + } + return; + } + } + } + } + } +} + +function upV4(event, g, context) { + if (v4Active) { + v4Active = false; + } +} + +function dblClickV4(event, g, context) { + restorePositioning(g); +} + +function drawV4(x, y) { + var ctx = v4Canvas; + + ctx.strokeStyle = "#000000"; + //ctx.fillStyle = "#FFFF00"; + ctx.fillStyle = "#FF0000"; + ctx.beginPath(); + ctx.arc(x,y,5,0,Math.PI*2,true); + ctx.closePath(); + ctx.stroke(); + ctx.fill(); +} + +function captureCanvas(canvas, area, g) { + v4Canvas = canvas; +} + +function restorePositioning(g) { + g.updateOptions({ + dateWindow: null, + valueRange: null + }); +} + +function newDygraphTouchstart(event, g, context) { +// This right here is what prevents IOS from doing its own zoom/touch behavior +// It stops the node from being selected too +event.preventDefault(); // touch browsers are all nice. + +if (event.touches.length > 1) { +// If the user ever puts two fingers down, it's not a double tap. +context.startTimeForDoubleTapMs = null; +} + +var touches = []; +for (var i = 0; i < event.touches.length; i++) { +var t = event.touches[i]; +// we dispense with 'dragGetX_' because all touchBrowsers support pageX +touches.push({ +pageX: t.pageX, +pageY: t.pageY, +dataX: g.toDataXCoord(t.pageX), +dataY: g.toDataYCoord(t.pageY) +// identifier: t.identifier +}); +} +context.initialTouches = touches; + +if (touches.length == 1) { +// This is just a swipe. +context.initialPinchCenter = touches[0]; +context.touchDirections = { x: true, y: true }; + +// ADDITION - this needs to select the points +//var closestTouchP = g.findClosestPoint(touches[0].pageX,touches[0].pageY); +//if(closestTouchP) { +//var selectionChanged = g.setSelection(closestTouchP.row, closestTouchP.seriesName); +//} +g.mouseMove_(event); + +} else if (touches.length >= 2) { +// It's become a pinch! +// In case there are 3+ touches, we ignore all but the "first" two. + +// only screen coordinates can be averaged (data coords could be log scale). +context.initialPinchCenter = { +pageX: 0.5 * (touches[0].pageX + touches[1].pageX), +pageY: 0.5 * (touches[0].pageY + touches[1].pageY), + +// TODO(danvk): remove +dataX: 0.5 * (touches[0].dataX + touches[1].dataX), +dataY: 0.5 * (touches[0].dataY + touches[1].dataY) +}; + +// Make pinches in a 45-degree swath around either axis 1-dimensional zooms. +var initialAngle = 180 / Math.PI * Math.atan2( +context.initialPinchCenter.pageY - touches[0].pageY, +touches[0].pageX - context.initialPinchCenter.pageX); + +// use symmetry to get it into the first quadrant. +initialAngle = Math.abs(initialAngle); +if (initialAngle > 90) initialAngle = 90 - initialAngle; + +context.touchDirections = { +x: (initialAngle < (90 - 45/2)), +y: (initialAngle > 45/2) +}; +} + +// save the full x & y ranges. +context.initialRange = { +x: g.xAxisRange(), +y: g.yAxisRange() +}; +};
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/moment.min.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/moment.min.js new file mode 100644 index 00000000..62b1697b --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/moment.min.js @@ -0,0 +1,6 @@ +// moment.js +// version : 2.1.0 +// author : Tim Wood +// license : MIT +// momentjs.com +!function(t){function e(t,e){return function(n){return u(t.call(this,n),e)}}function n(t,e){return function(n){return this.lang().ordinal(t.call(this,n),e)}}function s(){}function i(t){a(this,t)}function r(t){var e=t.years||t.year||t.y||0,n=t.months||t.month||t.M||0,s=t.weeks||t.week||t.w||0,i=t.days||t.day||t.d||0,r=t.hours||t.hour||t.h||0,a=t.minutes||t.minute||t.m||0,o=t.seconds||t.second||t.s||0,u=t.milliseconds||t.millisecond||t.ms||0;this._input=t,this._milliseconds=u+1e3*o+6e4*a+36e5*r,this._days=i+7*s,this._months=n+12*e,this._data={},this._bubble()}function a(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function o(t){return 0>t?Math.ceil(t):Math.floor(t)}function u(t,e){for(var n=t+"";n.length<e;)n="0"+n;return n}function h(t,e,n,s){var i,r,a=e._milliseconds,o=e._days,u=e._months;a&&t._d.setTime(+t._d+a*n),(o||u)&&(i=t.minute(),r=t.hour()),o&&t.date(t.date()+o*n),u&&t.month(t.month()+u*n),a&&!s&&H.updateOffset(t),(o||u)&&(t.minute(i),t.hour(r))}function d(t){return"[object Array]"===Object.prototype.toString.call(t)}function c(t,e){var n,s=Math.min(t.length,e.length),i=Math.abs(t.length-e.length),r=0;for(n=0;s>n;n++)~~t[n]!==~~e[n]&&r++;return r+i}function f(t){return t?ie[t]||t.toLowerCase().replace(/(.)s$/,"$1"):t}function l(t,e){return e.abbr=t,x[t]||(x[t]=new s),x[t].set(e),x[t]}function _(t){if(!t)return H.fn._lang;if(!x[t]&&A)try{require("./lang/"+t)}catch(e){return H.fn._lang}return x[t]}function m(t){return t.match(/\[.*\]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function y(t){var e,n,s=t.match(E);for(e=0,n=s.length;n>e;e++)s[e]=ue[s[e]]?ue[s[e]]:m(s[e]);return function(i){var r="";for(e=0;n>e;e++)r+=s[e]instanceof Function?s[e].call(i,t):s[e];return r}}function M(t,e){function n(e){return t.lang().longDateFormat(e)||e}for(var s=5;s--&&N.test(e);)e=e.replace(N,n);return re[e]||(re[e]=y(e)),re[e](t)}function g(t,e){switch(t){case"DDDD":return V;case"YYYY":return X;case"YYYYY":return $;case"S":case"SS":case"SSS":case"DDD":return I;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return R;case"a":case"A":return _(e._l)._meridiemParse;case"X":return B;case"Z":case"ZZ":return j;case"T":return q;case"MM":case"DD":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return J;default:return new RegExp(t.replace("\\",""))}}function p(t){var e=(j.exec(t)||[])[0],n=(e+"").match(ee)||["-",0,0],s=+(60*n[1])+~~n[2];return"+"===n[0]?-s:s}function D(t,e,n){var s,i=n._a;switch(t){case"M":case"MM":i[1]=null==e?0:~~e-1;break;case"MMM":case"MMMM":s=_(n._l).monthsParse(e),null!=s?i[1]=s:n._isValid=!1;break;case"D":case"DD":case"DDD":case"DDDD":null!=e&&(i[2]=~~e);break;case"YY":i[0]=~~e+(~~e>68?1900:2e3);break;case"YYYY":case"YYYYY":i[0]=~~e;break;case"a":case"A":n._isPm=_(n._l).isPM(e);break;case"H":case"HH":case"h":case"hh":i[3]=~~e;break;case"m":case"mm":i[4]=~~e;break;case"s":case"ss":i[5]=~~e;break;case"S":case"SS":case"SSS":i[6]=~~(1e3*("0."+e));break;case"X":n._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":n._useUTC=!0,n._tzm=p(e)}null==e&&(n._isValid=!1)}function Y(t){var e,n,s=[];if(!t._d){for(e=0;7>e;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];s[3]+=~~((t._tzm||0)/60),s[4]+=~~((t._tzm||0)%60),n=new Date(0),t._useUTC?(n.setUTCFullYear(s[0],s[1],s[2]),n.setUTCHours(s[3],s[4],s[5],s[6])):(n.setFullYear(s[0],s[1],s[2]),n.setHours(s[3],s[4],s[5],s[6])),t._d=n}}function w(t){var e,n,s=t._f.match(E),i=t._i;for(t._a=[],e=0;e<s.length;e++)n=(g(s[e],t).exec(i)||[])[0],n&&(i=i.slice(i.indexOf(n)+n.length)),ue[s[e]]&&D(s[e],n,t);i&&(t._il=i),t._isPm&&t._a[3]<12&&(t._a[3]+=12),t._isPm===!1&&12===t._a[3]&&(t._a[3]=0),Y(t)}function k(t){var e,n,s,r,o,u=99;for(r=0;r<t._f.length;r++)e=a({},t),e._f=t._f[r],w(e),n=new i(e),o=c(e._a,n.toArray()),n._il&&(o+=n._il.length),u>o&&(u=o,s=n);a(t,s)}function v(t){var e,n=t._i,s=K.exec(n);if(s){for(t._f="YYYY-MM-DD"+(s[2]||" "),e=0;4>e;e++)if(te[e][1].exec(n)){t._f+=te[e][0];break}j.exec(n)&&(t._f+=" Z"),w(t)}else t._d=new Date(n)}function T(e){var n=e._i,s=G.exec(n);n===t?e._d=new Date:s?e._d=new Date(+s[1]):"string"==typeof n?v(e):d(n)?(e._a=n.slice(0),Y(e)):e._d=n instanceof Date?new Date(+n):new Date(n)}function b(t,e,n,s,i){return i.relativeTime(e||1,!!n,t,s)}function S(t,e,n){var s=W(Math.abs(t)/1e3),i=W(s/60),r=W(i/60),a=W(r/24),o=W(a/365),u=45>s&&["s",s]||1===i&&["m"]||45>i&&["mm",i]||1===r&&["h"]||22>r&&["hh",r]||1===a&&["d"]||25>=a&&["dd",a]||45>=a&&["M"]||345>a&&["MM",W(a/30)]||1===o&&["y"]||["yy",o];return u[2]=e,u[3]=t>0,u[4]=n,b.apply({},u)}function F(t,e,n){var s,i=n-e,r=n-t.day();return r>i&&(r-=7),i-7>r&&(r+=7),s=H(t).add("d",r),{week:Math.ceil(s.dayOfYear()/7),year:s.year()}}function O(t){var e=t._i,n=t._f;return null===e||""===e?null:("string"==typeof e&&(t._i=e=_().preparse(e)),H.isMoment(e)?(t=a({},e),t._d=new Date(+e._d)):n?d(n)?k(t):w(t):T(t),new i(t))}function z(t,e){H.fn[t]=H.fn[t+"s"]=function(t){var n=this._isUTC?"UTC":"";return null!=t?(this._d["set"+n+e](t),H.updateOffset(this),this):this._d["get"+n+e]()}}function C(t){H.duration.fn[t]=function(){return this._data[t]}}function L(t,e){H.duration.fn["as"+t]=function(){return+this/e}}for(var H,P,U="2.1.0",W=Math.round,x={},A="undefined"!=typeof module&&module.exports,G=/^\/?Date\((\-?\d+)/i,Z=/(\-)?(\d*)?\.?(\d+)\:(\d+)\:(\d+)\.?(\d{3})?/,E=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,N=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,J=/\d\d?/,I=/\d{1,3}/,V=/\d{3}/,X=/\d{1,4}/,$=/[+\-]?\d{1,6}/,R=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,j=/Z|[\+\-]\d\d:?\d\d/i,q=/T/i,B=/[\+\-]?\d+(\.\d{1,3})?/,K=/^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,Q="YYYY-MM-DDTHH:mm:ssZ",te=[["HH:mm:ss.S",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ee=/([\+\-]|\d\d)/gi,ne="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),se={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},ie={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",w:"week",M:"month",y:"year"},re={},ae="DDD w W M D d".split(" "),oe="M D H h m s w W".split(" "),ue={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t)},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return u(this.year()%100,2)},YYYY:function(){return u(this.year(),4)},YYYYY:function(){return u(this.year(),5)},gg:function(){return u(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return u(this.weekYear(),5)},GG:function(){return u(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return u(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return~~(this.milliseconds()/100)},SS:function(){return u(~~(this.milliseconds()/10),2)},SSS:function(){return u(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(t/60),2)+":"+u(~~t%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(10*t/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}};ae.length;)P=ae.pop(),ue[P+"o"]=n(ue[P],P);for(;oe.length;)P=oe.pop(),ue[P+P]=e(ue[P],2);for(ue.DDDD=e(ue.DDD,3),s.prototype={set:function(t){var e,n;for(n in t)e=t[n],"function"==typeof e?this[n]=e:this["_"+n]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,n,s;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(n=H([2e3,e]),s="^"+this.months(n,"")+"|^"+this.monthsShort(n,""),this._monthsParse[e]=new RegExp(s.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},weekdaysParse:function(t){var e,n,s;for(this._weekdaysParse||(this._weekdaysParse=[]),e=0;7>e;e++)if(this._weekdaysParse[e]||(n=H([2e3,1]).day(e),s="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[e]=new RegExp(s.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},isPM:function(t){return"p"===(t+"").toLowerCase()[0]},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var n=this._calendar[t];return"function"==typeof n?n.apply(e):n},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(t,e,n,s){var i=this._relativeTime[n];return"function"==typeof i?i(t,e,n,s):i.replace(/%d/i,t)},pastFuture:function(t,e){var n=this._relativeTime[t>0?"future":"past"];return"function"==typeof n?n(e):n.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return F(t,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6}},H=function(t,e,n){return O({_i:t,_f:e,_l:n,_isUTC:!1})},H.utc=function(t,e,n){return O({_useUTC:!0,_isUTC:!0,_l:n,_i:t,_f:e})},H.unix=function(t){return H(1e3*t)},H.duration=function(t,e){var n,s,i=H.isDuration(t),a="number"==typeof t,o=i?t._input:a?{}:t,u=Z.exec(t);return a?e?o[e]=t:o.milliseconds=t:u&&(n="-"===u[1]?-1:1,o={y:0,d:~~u[2]*n,h:~~u[3]*n,m:~~u[4]*n,s:~~u[5]*n,ms:~~u[6]*n}),s=new r(o),i&&t.hasOwnProperty("_lang")&&(s._lang=t._lang),s},H.version=U,H.defaultFormat=Q,H.updateOffset=function(){},H.lang=function(t,e){return t?(e?l(t,e):x[t]||_(t),H.duration.fn._lang=H.fn._lang=_(t),void 0):H.fn._lang._abbr},H.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),_(t)},H.isMoment=function(t){return t instanceof i},H.isDuration=function(t){return t instanceof r},H.fn=i.prototype={clone:function(){return H(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return M(H(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var t=this;return[t.year(),t.month(),t.date(),t.hours(),t.minutes(),t.seconds(),t.milliseconds()]},isValid:function(){return null==this._isValid&&(this._isValid=this._a?!c(this._a,(this._isUTC?H.utc(this._a):H(this._a)).toArray()):!isNaN(this._d.getTime())),!!this._isValid},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(t){var e=M(this,t||H.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var n;return n="string"==typeof t?H.duration(+e,t):H.duration(t,e),h(this,n,1),this},subtract:function(t,e){var n;return n="string"==typeof t?H.duration(+e,t):H.duration(t,e),h(this,n,-1),this},diff:function(t,e,n){var s,i,r=this._isUTC?H(t).zone(this._offset||0):H(t).local(),a=6e4*(this.zone()-r.zone());return e=f(e),"year"===e||"month"===e?(s=432e5*(this.daysInMonth()+r.daysInMonth()),i=12*(this.year()-r.year())+(this.month()-r.month()),i+=(this-H(this).startOf("month")-(r-H(r).startOf("month")))/s,i-=6e4*(this.zone()-H(this).startOf("month").zone()-(r.zone()-H(r).startOf("month").zone()))/s,"year"===e&&(i/=12)):(s=this-r,i="second"===e?s/1e3:"minute"===e?s/6e4:"hour"===e?s/36e5:"day"===e?(s-a)/864e5:"week"===e?(s-a)/6048e5:s),n?i:o(i)},from:function(t,e){return H.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(H(),t)},calendar:function(){var t=this.diff(H().startOf("day"),"days",!0),e=-6>t?"sameElse":-1>t?"lastWeek":0>t?"lastDay":1>t?"sameDay":2>t?"nextDay":7>t?"nextWeek":"sameElse";return this.format(this.lang().calendar(e,this))},isLeapYear:function(){var t=this.year();return 0===t%4&&0!==t%100||0===t%400},isDST:function(){return this.zone()<this.clone().month(0).zone()||this.zone()<this.clone().month(5).zone()},day:function(t){var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?"string"==typeof t&&(t=this.lang().weekdaysParse(t),"number"!=typeof t)?this:this.add({d:t-e}):e},month:function(t){var e,n=this._isUTC?"UTC":"";return null!=t?"string"==typeof t&&(t=this.lang().monthsParse(t),"number"!=typeof t)?this:(e=this.date(),this.date(1),this._d["set"+n+"Month"](t),this.date(Math.min(e,this.daysInMonth())),H.updateOffset(this),this):this._d["get"+n+"Month"]()},startOf:function(t){switch(t=f(t)){case"year":this.month(0);case"month":this.date(1);case"week":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===t&&this.weekday(0),this},endOf:function(t){return this.startOf(t).add(t,1).subtract("ms",1)},isAfter:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)>+H(t).startOf(e)},isBefore:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)<+H(t).startOf(e)},isSame:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)===+H(t).startOf(e)},min:function(t){return t=H.apply(null,arguments),this>t?this:t},max:function(t){return t=H.apply(null,arguments),t>this?this:t},zone:function(t){var e=this._offset||0;return null==t?this._isUTC?e:this._d.getTimezoneOffset():("string"==typeof t&&(t=p(t)),Math.abs(t)<16&&(t=60*t),this._offset=t,this._isUTC=!0,e!==t&&h(this,H.duration(e-t,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},daysInMonth:function(){return H.utc([this.year(),this.month()+1,0]).date()},dayOfYear:function(t){var e=W((H(this).startOf("day")-H(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},weekYear:function(t){var e=F(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==t?e:this.add("y",t-e)},isoWeekYear:function(t){var e=F(this,1,4).year;return null==t?e:this.add("y",t-e)},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},isoWeek:function(t){var e=F(this,1,4).week;return null==t?e:this.add("d",7*(t-e))},weekday:function(t){var e=(this._d.getDay()+7-this.lang()._week.dow)%7;return null==t?e:this.add("d",t-e)},isoWeekday:function(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)},lang:function(e){return e===t?this._lang:(this._lang=_(e),this)}},P=0;P<ne.length;P++)z(ne[P].toLowerCase().replace(/s$/,""),ne[P]);z("year","FullYear"),H.fn.days=H.fn.day,H.fn.months=H.fn.month,H.fn.weeks=H.fn.week,H.fn.isoWeeks=H.fn.isoWeek,H.fn.toJSON=H.fn.toISOString,H.duration.fn=r.prototype={_bubble:function(){var t,e,n,s,i=this._milliseconds,r=this._days,a=this._months,u=this._data;u.milliseconds=i%1e3,t=o(i/1e3),u.seconds=t%60,e=o(t/60),u.minutes=e%60,n=o(e/60),u.hours=n%24,r+=o(n/24),u.days=r%30,a+=o(r/30),u.months=a%12,s=o(a/12),u.years=s},weeks:function(){return o(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+2592e6*(this._months%12)+31536e6*~~(this._months/12)},humanize:function(t){var e=+this,n=S(e,!t,this.lang());return t&&(n=this.lang().pastFuture(e,n)),this.lang().postformat(n)},add:function(t,e){var n=H.duration(t,e);return this._milliseconds+=n._milliseconds,this._days+=n._days,this._months+=n._months,this._bubble(),this},subtract:function(t,e){var n=H.duration(t,e);return this._milliseconds-=n._milliseconds,this._days-=n._days,this._months-=n._months,this._bubble(),this},get:function(t){return t=f(t),this[t.toLowerCase()+"s"]()},as:function(t){return t=f(t),this["as"+t.charAt(0).toUpperCase()+t.slice(1)+"s"]()},lang:H.fn.lang};for(P in se)se.hasOwnProperty(P)&&(L(P,se[P]),C(P.toLowerCase()));L("Weeks",6048e5),H.duration.fn.asMonths=function(){return(+this-31536e6*this.years())/2592e6+12*this.years()},H.lang("en",{ordinal:function(t){var e=t%10,n=1===~~(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),A&&(module.exports=H),"undefined"==typeof ender&&(this.moment=H),"function"==typeof define&&define.amd&&define("moment",[],function(){return H})}.call(this);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/phantom-driver.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/phantom-driver.js new file mode 100644 index 00000000..22f46870 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/phantom-driver.js @@ -0,0 +1,206 @@ +// Invoke via: ./test.sh +// +// or phantomjs phantom-driver.js [testCase.test] +// +// For more on phantomjs, visit www.phantomjs.org. + +var RunAllAutoTests = function(done_callback) { + +var page = require('webpage').create(); + +// NOTE: Cannot include '#' or '?' in this URL. +var url = 'auto_tests/misc/local.html'; + +// NOTE: changing the line below to this: +// page.open(url, function(status)) { +// makes phantomjs hang. +page.open(url, function(status) { + if (status !== 'success') { + console.warn('Page status: ' + status); + console.log(page); + phantom.exit(); + } + + var testCase, test; + var verbose = false; + var optIdx = 0; + if (phantom.args.length > 0 && phantom.args[0] === "--verbose") { + verbose = true; + optIdx = 1; + } + if (phantom.args.length == optIdx + 1) { + var parts = phantom.args[optIdx].split('.'); + if (2 != parts.length) { + console.warn('Usage: phantomjs phantom-driver.js [--verbose] [testCase.test]'); + phantom.exit(); + } + testCase = parts[0]; + test = parts[1]; + } + + var loggingOn = false; + + page.onConsoleMessage = function (msg) { + if (msg == 'Running ' + test) { + loggingOn = true; + } else if (msg.substr(0, 'Running'.length) == 'Running') { + loggingOn = false; + } + if (verbose || loggingOn) console.log(msg); + }; + + page.onError = function (msg, trace) { + console.log(msg); + trace.forEach(function(item) { + console.log(' ', item.file, ':', item.line); + }) + }; + + var results; + + // Run all tests. + var start = new Date().getTime(); + results = page.evaluate(function() { + var num_passing = 0, num_failing = 0; + var failures = []; + // Phantom doesn't like stacktrace.js using the "arguments" object + // in stacktrace.js, which it interprets in strict mode. + printStackTrace = undefined; + + jstestdriver.attachListener({ + finish : function(tc, name, result, e) { + console.log("Result:", tc.name, name, result, e); + if (result) { + // console.log(testCase + '.' + test + ' passed'); + num_passing++; + } else { + num_failing++; + failures.push(tc.name + '.' + name); + } + } + }); + var testCases = getAllTestCases(); + for (var idx in testCases) { + var entry = testCases[idx]; + + var prototype = entry.testCase; + var tc = new entry.testCase(); + var result = tc.runAllTests(); + } + return { + num_passing : num_passing, + num_failing : num_failing, + failures : failures + }; + }); + var end = new Date().getTime(); + var elapsed = (end - start) / 1000; + + console.log('Ran ' + (results.num_passing + results.num_failing) + ' tests in ' + elapsed + 's.'); + console.log(results.num_passing + ' test(s) passed'); + console.log(results.num_failing + ' test(s) failed:'); + for (var i = 0; i < results.failures.length; i++) { + // TODO(danvk): print an auto_test/misc/local URL that runs this test. + console.log(' ' + results.failures[i] + ' failed.'); + } + + done_callback(results.num_failing, results.num_passing); +}); + +}; + +// Load all "tests/" pages. +var LoadAllManualTests = function(totally_done_callback) { + +var fs = require('fs'); +var tests = fs.list('tests'); +var pages = []; + +function make_barrier_closure(n, fn) { + var calls = 0; + return function() { + calls++; + if (calls == n) { + fn(); + } else { + // console.log('' + calls + ' done, ' + (n - calls) + ' remain'); + } + }; +} + +var tasks = []; +for (var i = 0; i < tests.length; i++) { + if (tests[i].substr(-5) != '.html') continue; + tasks.push(tests[i]); +} +tasks = [ 'independent-series.html' ]; + +var loaded_page = make_barrier_closure(tasks.length, function() { + // Wait 2 secs to allow JS errors to happen after page load. + setTimeout(function() { + var success = 0, failures = 0; + for (var i = 0; i < pages.length; i++) { + if (pages[i].success && !pages[i].hasErrors) { + success++; + } else { + failures++; + } + } + console.log('Successfully loaded ' + success + ' / ' + + (success + failures) + ' test pages.'); + totally_done_callback(failures, success); + }, 2000); +}); + + +for (var i = 0; i < tasks.length; i++) { + var url = 'file://' + fs.absolute('tests/' + tasks[i]); + pages.push(function(path, url) { + var page = require('webpage').create(); + page.success = false; + page.hasErrors = false; + page.onError = function (msg, trace) { + console.log(path + ': ' + msg); + page.hasErrors = true; + trace.forEach(function(item) { + console.log(' ', item.file, ':', item.line); + }); + }; + page.onLoadFinished = function(status) { + if (status == 'success') { + page.success = true; + } + if (!page.done) loaded_page(); + page.done = true; + }; + page.open(url); + return page; + }(tasks[i], url)); +} + +}; + + +// First run all auto_tests. +// If they all pass, load the manual tests. +RunAllAutoTests(function(num_failing, num_passing) { + if (num_failing !== 0) { + console.log('FAIL'); + phantom.exit(); + } else { + console.log('PASS'); + } + phantom.exit(); + + // This is not yet reliable enough to be useful: + /* + LoadAllManualTests(function(failing, passing) { + if (failing !== 0) { + console.log('FAIL'); + } else { + console.log('PASS'); + } + phantom.exit(); + }); + */ +}); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/phantom-perf.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/phantom-perf.js new file mode 100644 index 00000000..b98c3f03 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/phantom-perf.js @@ -0,0 +1,94 @@ +// dygraphs command-line performance benchmark. +// +// Invoke as: +// +// phantomjs phantom-perf.js 1000 100 5 +// +// If the test succeeds, it will print out something like: +// +// 1000/100/5: 1309.4+/-43.4 ms (1284, 1245, 1336, 1346, 1336) +// +// This is mean +/- standard deviation, followed by a list of the individual +// times for each repetition, in milliseconds. + +var system = require('system'), + fs = require('fs'); + +var tmpfile = "tmp-dygraph-test-params.js"; +var url = 'tests/dygraph-many-points-benchmark.html'; + +function fail(msg){ + console.warn(msg); + phantom.exit(1); +} + +function assert(condition, msg){ + if (condition) return; + fail(msg); +} + +var config_file_template = + "document.getElementById('points').value = (points);\n" + + "document.getElementById('series').value = (series);\n" + + "document.getElementById('repetitions').value = (repetitions);\n"; + +var RunBenchmark = function(points, series, repetitions, done_callback) { + var page = require('webpage').create(); + + // page.evalute() is seriously locked down. + // This was the only way I could find to pass the parameters to the page. + fs.write(tmpfile, + config_file_template + .replace("(points)", points) + .replace("(series)", series) + .replace("(repetitions)", repetitions), + "w"); + + page.open(url, function(status) { + if (status !== 'success') { + console.warn('Page status: ' + status); + console.log(page); + phantom.exit(); + } + + assert(page.injectJs(tmpfile), "Unable to inject JS."); + fs.remove(tmpfile); + + var start = new Date().getTime(); + var rep_times = page.evaluate(function() { + var rep_times = updatePlot(); + return rep_times; + }); + var end = new Date().getTime(); + var elapsed = (end - start) / 1000; + done_callback(rep_times); + }); +}; + + +var points, series, repetitions; +if (4 != system.args.length) { + console.warn('Usage: phantomjs phantom-driver.js (points) (series) (repititions)'); + phantom.exit(); +} + +points = parseInt(system.args[1]); +series = parseInt(system.args[2]); +repetitions = parseInt(system.args[3]); +assert(points != null, "Couldn't parse " + system.args[1]); +assert(series != null, "Couldn't parse " + system.args[2]); +assert(repetitions != null, "Couldn't parse " + system.args[3]); + + +RunBenchmark(points, series, repetitions, function(rep_times) { + var mean = 0.0, std = 0.0; + rep_times.forEach(function(x) { mean += x; } ); + mean /= rep_times.length; + rep_times.forEach(function(x) { std += Math.pow(x - mean, 2); }); + std = Math.sqrt(std / (rep_times.length - 1)); + + console.log(points + '/' + series + '/' + repetitions + ': ' + + mean.toFixed(1) + '+/-' + std.toFixed(1) + ' ms (' + + rep_times.join(', ') + ')'); + phantom.exit(); +}); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/README b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/README new file mode 100644 index 00000000..f216545b --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/README @@ -0,0 +1,113 @@ +dygraphs plugins +---------------- + +A single dygraph is actually a collection of dygraphs plugins, each responsible +for some portion of the chart: the plot area, the axes, the legend, the labels, +etc. + +This forces the dygraphs code to be more modular and encourages better APIs. + +The "legend" plugin (plugins/legend.js) can be used as a template for new +plugins. + +Here is a simplified version of it, with comments to explain the plugin +lifecycle and APIs: + +---------------- + +// (standard JavaScript library wrapper; prevents polluting global namespace) +Dygraph.Plugins.Legend = (function() { + +// Plugin constructor. This is invoked once for every chart which uses the +// plugin. You can't actually access the Dygraph object at this point, so the +// initialization you do here shouldn't be chart-specific. (For that, use +// the activate() method). +var legend = function() { + this.div_ = null; +}; + +// Plugins are expected to implement this method for debugging purposes. +legend.toString = function() { + return "Legend"; +}; + +// This is called once the dygraph is ready. The chart data may not be +// available yet, but the options specified in the constructor are. +// +// Proper tasks to do here include: +// - Reading your own options +// - DOM manipulation +// - Registering event listeners +// +// "dygraph" is the Dygraph object for which this instance is being activated. +// "registerer" allows you to register event listeners. +legend.prototype.activate = function(dygraph, registerer) { + // Create the legend div and attach it to the chart DOM. + this.div_ = document.createElement("div"); + dygraph.graphDiv.appendChild(this.div_); + + // Add event listeners. These will be called whenever points are selected + // (i.e. you hover over them) or deselected (i.e. you mouse away from the + // chart). This is your only chance to register event listeners! Once this + // method returns, the gig is up. + registerer.addEventListener('select', legend.select); + registerer.addEventListener('deselect', legend.deselect); +}; + +// The functions called by event listeners all take a single parameter, an +// event object. This contains properties relevant to the particular event, but +// you can always assume that it has: +// +// 1. A "dygraph" parameter which is a reference to the chart on which the +// event took place. +// 2. A "stopPropagation" method, which you can call to prevent the event from +// being seen by any other plugins after you. This effectively cancels the +// event. +// 3. A "preventDefault" method, which prevents dygraphs from performing the +// default action associated with this event. +// +legend.select = function(e) { + // These are two of the properties specific to the "select" event object: + var xValue = e.selectedX; + var points = e.selectedPoints; + + var html = xValue + ':'; + for (var i = 0; i < points.length; i++) { + var point = points[i]; + html += ' ' + point.name + ':' + point.yval; + } + + // In an event listener, "this" refers to your plugin object. + this.div_.innerHTML = html; +}; + +// This clears out the legend when the user mouses away from the chart. +legend.deselect = function(e) { + this.div_.innerHTML = ''; +}; + +return legend; +})(); + +---------------- + +Plugin Events Reference: + +- predraw +- clearChart +- drawChart +- select +- deselect + +TODO(danvk): document all event properties for each event. + + +Special methods: +- (constructor) +- activate +- destroy + + +---------------- + +Notes on plugin registration and event cascade ordering/behavior. diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/annotations.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/annotations.js new file mode 100644 index 00000000..85761040 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/annotations.js @@ -0,0 +1,182 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/*global Dygraph:false */ + +Dygraph.Plugins.Annotations = (function() { + +"use strict"; + +/** +Current bits of jankiness: +- Uses dygraph.layout_ to get the parsed annotations. +- Uses dygraph.plotter_.area + +It would be nice if the plugin didn't require so much special support inside +the core dygraphs classes, but annotations involve quite a bit of parsing and +layout. + +TODO(danvk): cache DOM elements. + +*/ + +var annotations = function() { + this.annotations_ = []; +}; + +annotations.prototype.toString = function() { + return "Annotations Plugin"; +}; + +annotations.prototype.activate = function(g) { + return { + clearChart: this.clearChart, + didDrawChart: this.didDrawChart + }; +}; + +annotations.prototype.detachLabels = function() { + for (var i = 0; i < this.annotations_.length; i++) { + var a = this.annotations_[i]; + if (a.parentNode) a.parentNode.removeChild(a); + this.annotations_[i] = null; + } + this.annotations_ = []; +}; + +annotations.prototype.clearChart = function(e) { + this.detachLabels(); +}; + +annotations.prototype.didDrawChart = function(e) { + var g = e.dygraph; + + // Early out in the (common) case of zero annotations. + var points = g.layout_.annotated_points; + if (!points || points.length === 0) return; + + var containerDiv = e.canvas.parentNode; + var annotationStyle = { + "position": "absolute", + "fontSize": g.getOption('axisLabelFontSize') + "px", + "zIndex": 10, + "overflow": "hidden" + }; + + var bindEvt = function(eventName, classEventName, pt) { + return function(annotation_event) { + var a = pt.annotation; + if (a.hasOwnProperty(eventName)) { + a[eventName](a, pt, g, annotation_event); + } else if (g.getOption(classEventName)) { + g.getOption(classEventName)(a, pt, g, annotation_event ); + } + }; + }; + + // Add the annotations one-by-one. + var area = e.dygraph.plotter_.area; + + // x-coord to sum of previous annotation's heights (used for stacking). + var xToUsedHeight = {}; + + for (var i = 0; i < points.length; i++) { + var p = points[i]; + if (p.canvasx < area.x || p.canvasx > area.x + area.w || + p.canvasy < area.y || p.canvasy > area.y + area.h) { + continue; + } + + var a = p.annotation; + var tick_height = 6; + if (a.hasOwnProperty("tickHeight")) { + tick_height = a.tickHeight; + } + + var div = document.createElement("div"); + for (var name in annotationStyle) { + if (annotationStyle.hasOwnProperty(name)) { + div.style[name] = annotationStyle[name]; + } + } + if (!a.hasOwnProperty('icon')) { + div.className = "dygraphDefaultAnnotation"; + } + if (a.hasOwnProperty('cssClass')) { + div.className += " " + a.cssClass; + } + + var width = a.hasOwnProperty('width') ? a.width : 16; + var height = a.hasOwnProperty('height') ? a.height : 16; + if (a.hasOwnProperty('icon')) { + var img = document.createElement("img"); + img.src = a.icon; + img.width = width; + img.height = height; + div.appendChild(img); + } else if (p.annotation.hasOwnProperty('shortText')) { + div.appendChild(document.createTextNode(p.annotation.shortText)); + } + var left = p.canvasx - width / 2; + div.style.left = left + "px"; + var divTop = 0; + if (a.attachAtBottom) { + var y = (area.y + area.h - height - tick_height); + if (xToUsedHeight[left]) { + y -= xToUsedHeight[left]; + } else { + xToUsedHeight[left] = 0; + } + xToUsedHeight[left] += (tick_height + height); + divTop = y; + } else { + divTop = p.canvasy - height - tick_height; + } + div.style.top = divTop + "px"; + div.style.width = width + "px"; + div.style.height = height + "px"; + div.title = p.annotation.text; + div.style.color = g.colorsMap_[p.name]; + div.style.borderColor = g.colorsMap_[p.name]; + a.div = div; + + g.addAndTrackEvent(div, 'click', + bindEvt('clickHandler', 'annotationClickHandler', p, this)); + g.addAndTrackEvent(div, 'mouseover', + bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this)); + g.addAndTrackEvent(div, 'mouseout', + bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this)); + g.addAndTrackEvent(div, 'dblclick', + bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this)); + + containerDiv.appendChild(div); + this.annotations_.push(div); + + var ctx = e.drawingContext; + ctx.save(); + ctx.strokeStyle = g.colorsMap_[p.name]; + ctx.beginPath(); + if (!a.attachAtBottom) { + ctx.moveTo(p.canvasx, p.canvasy); + ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height); + } else { + var y = divTop + height; + ctx.moveTo(p.canvasx, y); + ctx.lineTo(p.canvasx, y + tick_height); + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); + } +}; + +annotations.prototype.destroy = function() { + this.detachLabels(); +}; + +return annotations; + +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/axes.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/axes.js new file mode 100644 index 00000000..cacda86e --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/axes.js @@ -0,0 +1,315 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/*global Dygraph:false */ + +Dygraph.Plugins.Axes = (function() { + +"use strict"; + +/* +Bits of jankiness: +- Direct layout access +- Direct area access +- Should include calculation of ticks, not just the drawing. + +Options left to make axis-friendly. + ('axisTickSize') + ('drawAxesAtZero') + ('xAxisHeight') + +These too. What is the difference between axisLablelWidth and {x,y}AxisLabelWidth? + ('axisLabelWidth') + ('xAxisLabelWidth') + ('yAxisLabelWidth') +*/ + +/** + * Draws the axes. This includes the labels on the x- and y-axes, as well + * as the tick marks on the axes. + * It does _not_ draw the grid lines which span the entire chart. + */ +var axes = function() { + this.xlabels_ = []; + this.ylabels_ = []; +}; + +axes.prototype.toString = function() { + return "Axes Plugin"; +}; + +axes.prototype.activate = function(g) { + return { + layout: this.layout, + clearChart: this.clearChart, + willDrawChart: this.willDrawChart + }; +}; + +axes.prototype.layout = function(e) { + var g = e.dygraph; + + if (g.getOption('drawYAxis')) { + var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize'); + e.reserveSpaceLeft(w); + } + + if (g.getOption('drawXAxis')) { + var h; + // NOTE: I think this is probably broken now, since g.getOption() now + // hits the dictionary. (That is, g.getOption('xAxisHeight') now always + // has a value.) + if (g.getOption('xAxisHeight')) { + h = g.getOption('xAxisHeight'); + } else { + h = g.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g.getOption('axisTickSize'); + } + e.reserveSpaceBottom(h); + } + + if (g.numAxes() == 2) { + // TODO(danvk): introduce a 'drawAxis' per-axis property. + if (g.getOption('drawYAxis')) { + // TODO(danvk): per-axis setting. + var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize'); + e.reserveSpaceRight(w); + } + } else if (g.numAxes() > 2) { + g.error("Only two y-axes are supported at this time. (Trying " + + "to use " + g.numAxes() + ")"); + } +}; + +axes.prototype.detachLabels = function() { + function removeArray(ary) { + for (var i = 0; i < ary.length; i++) { + var el = ary[i]; + if (el.parentNode) el.parentNode.removeChild(el); + } + } + + removeArray(this.xlabels_); + removeArray(this.ylabels_); + this.xlabels_ = []; + this.ylabels_ = []; +}; + +axes.prototype.clearChart = function(e) { + this.detachLabels(); +}; + +axes.prototype.willDrawChart = function(e) { + var g = e.dygraph; + if (!g.getOption('drawXAxis') && !g.getOption('drawYAxis')) return; + + // Round pixels to half-integer boundaries for crisper drawing. + function halfUp(x) { return Math.round(x) + 0.5; } + function halfDown(y){ return Math.round(y) - 0.5; } + + var context = e.drawingContext; + var containerDiv = e.canvas.parentNode; + var canvasWidth = e.canvas.width; + var canvasHeight = e.canvas.height; + + var label, x, y, tick, i; + + var makeLabelStyle = function(axis) { + return { + position: "absolute", + fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + "px", + zIndex: 10, + color: g.getOptionForAxis('axisLabelColor', axis), + width: g.getOption('axisLabelWidth') + "px", + // height: g.getOptionForAxis('axisLabelFontSize', 'x') + 2 + "px", + lineHeight: "normal", // Something other than "normal" line-height screws up label positioning. + overflow: "hidden" + }; + }; + + var labelStyles = { + x : makeLabelStyle('x'), + y : makeLabelStyle('y'), + y2 : makeLabelStyle('y2') + }; + + var makeDiv = function(txt, axis, prec_axis) { + /* + * This seems to be called with the following three sets of axis/prec_axis: + * x: undefined + * y: y1 + * y: y2 + */ + var div = document.createElement("div"); + var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis]; + for (var name in labelStyle) { + if (labelStyle.hasOwnProperty(name)) { + div.style[name] = labelStyle[name]; + } + } + var inner_div = document.createElement("div"); + inner_div.className = 'dygraph-axis-label' + + ' dygraph-axis-label-' + axis + + (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); + inner_div.innerHTML = txt; + div.appendChild(inner_div); + return div; + }; + + // axis lines + context.save(); + + var layout = g.layout_; + var area = e.dygraph.plotter_.area; + + if (g.getOption('drawYAxis')) { + if (layout.yticks && layout.yticks.length > 0) { + var num_axes = g.numAxes(); + for (i = 0; i < layout.yticks.length; i++) { + tick = layout.yticks[i]; + if (typeof(tick) == "function") return; + x = area.x; + var sgn = 1; + var prec_axis = 'y1'; + if (tick[0] == 1) { // right-side y-axis + x = area.x + area.w; + sgn = -1; + prec_axis = 'y2'; + } + var fontSize = g.getOptionForAxis('axisLabelFontSize', prec_axis); + y = area.y + tick[1] * area.h; + + /* Tick marks are currently clipped, so don't bother drawing them. + context.beginPath(); + context.moveTo(halfUp(x), halfDown(y)); + context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); + context.closePath(); + context.stroke(); + */ + + label = makeDiv(tick[2], 'y', num_axes == 2 ? prec_axis : null); + var top = (y - fontSize / 2); + if (top < 0) top = 0; + + if (top + fontSize + 3 > canvasHeight) { + label.style.bottom = "0px"; + } else { + label.style.top = top + "px"; + } + if (tick[0] === 0) { + label.style.left = (area.x - g.getOption('yAxisLabelWidth') - g.getOption('axisTickSize')) + "px"; + label.style.textAlign = "right"; + } else if (tick[0] == 1) { + label.style.left = (area.x + area.w + + g.getOption('axisTickSize')) + "px"; + label.style.textAlign = "left"; + } + label.style.width = g.getOption('yAxisLabelWidth') + "px"; + containerDiv.appendChild(label); + this.ylabels_.push(label); + } + + // The lowest tick on the y-axis often overlaps with the leftmost + // tick on the x-axis. Shift the bottom tick up a little bit to + // compensate if necessary. + var bottomTick = this.ylabels_[0]; + // Interested in the y2 axis also? + var fontSize = g.getOptionForAxis('axisLabelFontSize', "y"); + var bottom = parseInt(bottomTick.style.top, 10) + fontSize; + if (bottom > canvasHeight - fontSize) { + bottomTick.style.top = (parseInt(bottomTick.style.top, 10) - + fontSize / 2) + "px"; + } + } + + // draw a vertical line on the left to separate the chart from the labels. + var axisX; + if (g.getOption('drawAxesAtZero')) { + var r = g.toPercentXCoord(0); + if (r > 1 || r < 0 || isNaN(r)) r = 0; + axisX = halfUp(area.x + r * area.w); + } else { + axisX = halfUp(area.x); + } + + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y'); + + context.beginPath(); + context.moveTo(axisX, halfDown(area.y)); + context.lineTo(axisX, halfDown(area.y + area.h)); + context.closePath(); + context.stroke(); + + // if there's a secondary y-axis, draw a vertical line for that, too. + if (g.numAxes() == 2) { + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y2'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y2'); + context.beginPath(); + context.moveTo(halfDown(area.x + area.w), halfDown(area.y)); + context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h)); + context.closePath(); + context.stroke(); + } + } + + if (g.getOption('drawXAxis')) { + if (layout.xticks) { + for (i = 0; i < layout.xticks.length; i++) { + tick = layout.xticks[i]; + x = area.x + tick[0] * area.w; + y = area.y + area.h; + + /* Tick marks are currently clipped, so don't bother drawing them. + context.beginPath(); + context.moveTo(halfUp(x), halfDown(y)); + context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); + context.closePath(); + context.stroke(); + */ + + label = makeDiv(tick[1], 'x'); + label.style.textAlign = "center"; + label.style.top = (y + g.getOption('axisTickSize')) + 'px'; + + var left = (x - g.getOption('axisLabelWidth')/2); + if (left + g.getOption('axisLabelWidth') > canvasWidth) { + left = canvasWidth - g.getOption('xAxisLabelWidth'); + label.style.textAlign = "right"; + } + if (left < 0) { + left = 0; + label.style.textAlign = "left"; + } + + label.style.left = left + "px"; + label.style.width = g.getOption('xAxisLabelWidth') + "px"; + containerDiv.appendChild(label); + this.xlabels_.push(label); + } + } + + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'x'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'x'); + context.beginPath(); + var axisY; + if (g.getOption('drawAxesAtZero')) { + var r = g.toPercentYCoord(0, 0); + if (r > 1 || r < 0) r = 1; + axisY = halfDown(area.y + r * area.h); + } else { + axisY = halfDown(area.y + area.h); + } + context.moveTo(halfUp(area.x), axisY); + context.lineTo(halfUp(area.x + area.w), axisY); + context.closePath(); + context.stroke(); + } + + context.restore(); +}; + +return axes; +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/chart-labels.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/chart-labels.js new file mode 100644 index 00000000..da231d80 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/chart-labels.js @@ -0,0 +1,202 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +Dygraph.Plugins.ChartLabels = (function() { + +"use strict"; + +// TODO(danvk): move chart label options out of dygraphs and into the plugin. +// TODO(danvk): only tear down & rebuild the DIVs when it's necessary. + +var chart_labels = function() { + this.title_div_ = null; + this.xlabel_div_ = null; + this.ylabel_div_ = null; + this.y2label_div_ = null; +}; + +chart_labels.prototype.toString = function() { + return "ChartLabels Plugin"; +}; + +chart_labels.prototype.activate = function(g) { + return { + layout: this.layout, + // clearChart: this.clearChart, + didDrawChart: this.didDrawChart + }; +}; + +// QUESTION: should there be a plugin-utils.js? +var createDivInRect = function(r) { + var div = document.createElement('div'); + div.style.position = 'absolute'; + div.style.left = r.x + 'px'; + div.style.top = r.y + 'px'; + div.style.width = r.w + 'px'; + div.style.height = r.h + 'px'; + return div; +}; + +// Detach and null out any existing nodes. +chart_labels.prototype.detachLabels_ = function() { + var els = [ this.title_div_, + this.xlabel_div_, + this.ylabel_div_, + this.y2label_div_ ]; + for (var i = 0; i < els.length; i++) { + var el = els[i]; + if (!el) continue; + if (el.parentNode) el.parentNode.removeChild(el); + } + + this.title_div_ = null; + this.xlabel_div_ = null; + this.ylabel_div_ = null; + this.y2label_div_ = null; +}; + +var createRotatedDiv = function(g, box, axis, classes, html) { + // TODO(danvk): is this outer div actually necessary? + var div = document.createElement("div"); + div.style.position = 'absolute'; + if (axis == 1) { + // NOTE: this is cheating. Should be positioned relative to the box. + div.style.left = '0px'; + } else { + div.style.left = box.x + 'px'; + } + div.style.top = box.y + 'px'; + div.style.width = box.w + 'px'; + div.style.height = box.h + 'px'; + div.style.fontSize = (g.getOption('yLabelWidth') - 2) + 'px'; + + var inner_div = document.createElement("div"); + inner_div.style.position = 'absolute'; + inner_div.style.width = box.h + 'px'; + inner_div.style.height = box.w + 'px'; + inner_div.style.top = (box.h / 2 - box.w / 2) + 'px'; + inner_div.style.left = (box.w / 2 - box.h / 2) + 'px'; + inner_div.style.textAlign = 'center'; + + // CSS rotation is an HTML5 feature which is not standardized. Hence every + // browser has its own name for the CSS style. + var val = 'rotate(' + (axis == 1 ? '-' : '') + '90deg)'; + inner_div.style.transform = val; // HTML5 + inner_div.style.WebkitTransform = val; // Safari/Chrome + inner_div.style.MozTransform = val; // Firefox + inner_div.style.OTransform = val; // Opera + inner_div.style.msTransform = val; // IE9 + + if (typeof(document.documentMode) !== 'undefined' && + document.documentMode < 9) { + // We're dealing w/ an old version of IE, so we have to rotate the text + // using a BasicImage transform. This uses a different origin of rotation + // than HTML5 rotation (top left of div vs. its center). + inner_div.style.filter = + 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + + (axis == 1 ? '3' : '1') + ')'; + inner_div.style.left = '0px'; + inner_div.style.top = '0px'; + } + + var class_div = document.createElement("div"); + class_div.className = classes; + class_div.innerHTML = html; + + inner_div.appendChild(class_div); + div.appendChild(inner_div); + return div; +}; + +chart_labels.prototype.layout = function(e) { + this.detachLabels_(); + + var g = e.dygraph; + var div = e.chart_div; + if (g.getOption('title')) { + // QUESTION: should this return an absolutely-positioned div instead? + var title_rect = e.reserveSpaceTop(g.getOption('titleHeight')); + this.title_div_ = createDivInRect(title_rect); + this.title_div_.style.textAlign = 'center'; + this.title_div_.style.fontSize = (g.getOption('titleHeight') - 8) + 'px'; + this.title_div_.style.fontWeight = 'bold'; + this.title_div_.style.zIndex = 10; + + var class_div = document.createElement("div"); + class_div.className = 'dygraph-label dygraph-title'; + class_div.innerHTML = g.getOption('title'); + this.title_div_.appendChild(class_div); + div.appendChild(this.title_div_); + } + + if (g.getOption('xlabel')) { + var x_rect = e.reserveSpaceBottom(g.getOption('xLabelHeight')); + this.xlabel_div_ = createDivInRect(x_rect); + this.xlabel_div_.style.textAlign = 'center'; + this.xlabel_div_.style.fontSize = (g.getOption('xLabelHeight') - 2) + 'px'; + + var class_div = document.createElement("div"); + class_div.className = 'dygraph-label dygraph-xlabel'; + class_div.innerHTML = g.getOption('xlabel'); + this.xlabel_div_.appendChild(class_div); + div.appendChild(this.xlabel_div_); + } + + if (g.getOption('ylabel')) { + // It would make sense to shift the chart here to make room for the y-axis + // label, but the default yAxisLabelWidth is large enough that this results + // in overly-padded charts. The y-axis label should fit fine. If it + // doesn't, the yAxisLabelWidth option can be increased. + var y_rect = e.reserveSpaceLeft(0); + + this.ylabel_div_ = createRotatedDiv( + g, y_rect, + 1, // primary (left) y-axis + 'dygraph-label dygraph-ylabel', + g.getOption('ylabel')); + div.appendChild(this.ylabel_div_); + } + + if (g.getOption('y2label') && g.numAxes() == 2) { + // same logic applies here as for ylabel. + var y2_rect = e.reserveSpaceRight(0); + this.y2label_div_ = createRotatedDiv( + g, y2_rect, + 2, // secondary (right) y-axis + 'dygraph-label dygraph-y2label', + g.getOption('y2label')); + div.appendChild(this.y2label_div_); + } +}; + +chart_labels.prototype.didDrawChart = function(e) { + var g = e.dygraph; + if (this.title_div_) { + this.title_div_.children[0].innerHTML = g.getOption('title'); + } + if (this.xlabel_div_) { + this.xlabel_div_.children[0].innerHTML = g.getOption('xlabel'); + } + if (this.ylabel_div_) { + this.ylabel_div_.children[0].children[0].innerHTML = g.getOption('ylabel'); + } + if (this.y2label_div_) { + this.y2label_div_.children[0].children[0].innerHTML = g.getOption('y2label'); + } +}; + +chart_labels.prototype.clearChart = function() { +}; + +chart_labels.prototype.destroy = function() { + this.detachLabels_(); +}; + + +return chart_labels; +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/grid.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/grid.js new file mode 100644 index 00000000..425d93f6 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/grid.js @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +Dygraph.Plugins.Grid = (function() { + +/* + +Current bits of jankiness: +- Direct layout access +- Direct area access + +*/ + +"use strict"; + + +/** + * Draws the gridlines, i.e. the gray horizontal & vertical lines running the + * length of the chart. + * + * @constructor + */ +var grid = function() { +}; + +grid.prototype.toString = function() { + return "Gridline Plugin"; +}; + +grid.prototype.activate = function(g) { + return { + willDrawChart: this.willDrawChart + }; +}; + +grid.prototype.willDrawChart = function(e) { + // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to + // half-integers. This prevents them from drawing in two rows/cols. + var g = e.dygraph; + var ctx = e.drawingContext; + var layout = g.layout_; + var area = e.dygraph.plotter_.area; + + function halfUp(x) { return Math.round(x) + 0.5; } + function halfDown(y){ return Math.round(y) - 0.5; } + + var x, y, i, ticks; + if (g.getOption('drawYGrid')) { + var axes = ["y", "y2"]; + var strokeStyles = [], lineWidths = [], drawGrid = [], stroking = [], strokePattern = []; + for (var i = 0; i < axes.length; i++) { + drawGrid[i] = g.getOptionForAxis("drawGrid", axes[i]); + if (drawGrid[i]) { + strokeStyles[i] = g.getOptionForAxis('gridLineColor', axes[i]); + lineWidths[i] = g.getOptionForAxis('gridLineWidth', axes[i]); + strokePattern[i] = g.getOptionForAxis('gridLinePattern', axes[i]); + stroking[i] = strokePattern[i] && (strokePattern[i].length >= 2); + } + } + ticks = layout.yticks; + ctx.save(); + // draw grids for the different y axes + for (i = 0; i < ticks.length; i++) { + var axis = ticks[i][0]; + if(drawGrid[axis]) { + if (stroking[axis]) { + ctx.installPattern(strokePattern[axis]); + } + ctx.strokeStyle = strokeStyles[axis]; + ctx.lineWidth = lineWidths[axis]; + + x = halfUp(area.x); + y = halfDown(area.y + ticks[i][1] * area.h); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + area.w, y); + ctx.closePath(); + ctx.stroke(); + + if (stroking[axis]) { + ctx.uninstallPattern(); + } + } + } + ctx.restore(); + } + + // draw grid for x axis + if (g.getOption('drawXGrid') && g.getOptionForAxis("drawGrid", 'x')) { + ticks = layout.xticks; + ctx.save(); + var strokePattern = g.getOptionForAxis('gridLinePattern', 'x'); + var stroking = strokePattern && (strokePattern.length >= 2); + if (stroking) { + ctx.installPattern(strokePattern); + } + ctx.strokeStyle = g.getOptionForAxis('gridLineColor', 'x'); + ctx.lineWidth = g.getOptionForAxis('gridLineWidth', 'x'); + for (i = 0; i < ticks.length; i++) { + x = halfUp(area.x + ticks[i][0] * area.w); + y = halfDown(area.y + area.h); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x, area.y); + ctx.closePath(); + ctx.stroke(); + } + if (stroking) { + ctx.uninstallPattern(); + } + ctx.restore(); + } +}; + +grid.prototype.destroy = function() { +}; + +return grid; + +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/legend.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/legend.js new file mode 100644 index 00000000..7406f82a --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/legend.js @@ -0,0 +1,332 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +Dygraph.Plugins.Legend = (function() { +/* + +Current bits of jankiness: +- Uses two private APIs: + 1. Dygraph.optionsViewForAxis_ + 2. dygraph.plotter_.area +- Registers for a "predraw" event, which should be renamed. +- I call calculateEmWidthInDiv more often than needed. + +*/ + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + + +/** + * Creates the legend, which appears when the user hovers over the chart. + * The legend can be either a user-specified or generated div. + * + * @constructor + */ +var legend = function() { + this.legend_div_ = null; + this.is_generated_div_ = false; // do we own this div, or was it user-specified? +}; + +legend.prototype.toString = function() { + return "Legend Plugin"; +}; + +// (defined below) +var generateLegendHTML, generateLegendDashHTML; + +/** + * This is called during the dygraph constructor, after options have been set + * but before the data is available. + * + * Proper tasks to do here include: + * - Reading your own options + * - DOM manipulation + * - Registering event listeners + * + * @param {Dygraph} g Graph instance. + * @return {object.<string, function(ev)>} Mapping of event names to callbacks. + */ +legend.prototype.activate = function(g) { + var div; + var divWidth = g.getOption('labelsDivWidth'); + + var userLabelsDiv = g.getOption('labelsDiv'); + if (userLabelsDiv && null !== userLabelsDiv) { + if (typeof(userLabelsDiv) == "string" || userLabelsDiv instanceof String) { + div = document.getElementById(userLabelsDiv); + } else { + div = userLabelsDiv; + } + } else { + // Default legend styles. These can be overridden in CSS by adding + // "!important" after your rule, e.g. "left: 30px !important;" + var messagestyle = { + "position": "absolute", + "fontSize": "14px", + "zIndex": 10, + "width": divWidth + "px", + "top": "0px", + "left": (g.size().width - divWidth - 2) + "px", + "background": "white", + "lineHeight": "normal", + "textAlign": "left", + "overflow": "hidden"}; + + // TODO(danvk): get rid of labelsDivStyles? CSS is better. + Dygraph.update(messagestyle, g.getOption('labelsDivStyles')); + div = document.createElement("div"); + div.className = "dygraph-legend"; + for (var name in messagestyle) { + if (!messagestyle.hasOwnProperty(name)) continue; + + try { + div.style[name] = messagestyle[name]; + } catch (e) { + this.warn("You are using unsupported css properties for your " + + "browser in labelsDivStyles"); + } + } + + // TODO(danvk): come up with a cleaner way to expose this. + g.graphDiv.appendChild(div); + this.is_generated_div_ = true; + } + + this.legend_div_ = div; + this.one_em_width_ = 10; // just a guess, will be updated. + + return { + select: this.select, + deselect: this.deselect, + // TODO(danvk): rethink the name "predraw" before we commit to it in any API. + predraw: this.predraw, + didDrawChart: this.didDrawChart + }; +}; + +// Needed for dashed lines. +var calculateEmWidthInDiv = function(div) { + var sizeSpan = document.createElement('span'); + sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;'); + div.appendChild(sizeSpan); + var oneEmWidth=sizeSpan.offsetWidth; + div.removeChild(sizeSpan); + return oneEmWidth; +}; + +legend.prototype.select = function(e) { + var xValue = e.selectedX; + var points = e.selectedPoints; + + var html = generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_); + this.legend_div_.innerHTML = html; +}; + +legend.prototype.deselect = function(e) { + // Have to do this every time, since styles might have changed. + var oneEmWidth = calculateEmWidthInDiv(this.legend_div_); + this.one_em_width_ = oneEmWidth; + + var html = generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth); + this.legend_div_.innerHTML = html; +}; + +legend.prototype.didDrawChart = function(e) { + this.deselect(e); +}; + +// Right edge should be flush with the right edge of the charting area (which +// may not be the same as the right edge of the div, if we have two y-axes. +// TODO(danvk): is any of this really necessary? Could just set "right" in "activate". +/** + * Position the labels div so that: + * - its right edge is flush with the right edge of the charting area + * - its top edge is flush with the top edge of the charting area + * @private + */ +legend.prototype.predraw = function(e) { + // Don't touch a user-specified labelsDiv. + if (!this.is_generated_div_) return; + + // TODO(danvk): only use real APIs for this. + e.dygraph.graphDiv.appendChild(this.legend_div_); + var area = e.dygraph.plotter_.area; + var labelsDivWidth = e.dygraph.getOption("labelsDivWidth"); + this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px"; + this.legend_div_.style.top = area.y + "px"; + this.legend_div_.style.width = labelsDivWidth + "px"; +}; + +/** + * Called when dygraph.destroy() is called. + * You should null out any references and detach any DOM elements. + */ +legend.prototype.destroy = function() { + this.legend_div_ = null; +}; + +/** + * @private + * Generates HTML for the legend which is displayed when hovering over the + * chart. If no selected points are specified, a default legend is returned + * (this may just be the empty string). + * @param { Number } [x] The x-value of the selected points. + * @param { [Object] } [sel_points] List of selected points for the given + * x-value. Should have properties like 'name', 'yval' and 'canvasy'. + * @param { Number } [oneEmWidth] The pixel width for 1em in the legend. Only + * relevant when displaying a legend with no selection (i.e. {legend: + * 'always'}) and with dashed lines. + */ +generateLegendHTML = function(g, x, sel_points, oneEmWidth) { + // TODO(danvk): deprecate this option in place of {legend: 'never'} + if (g.getOption('showLabelsOnHighlight') !== true) return ''; + + // If no points are selected, we display a default legend. Traditionally, + // this has been blank. But a better default would be a conventional legend, + // which provides essential information for a non-interactive chart. + var html, sepLines, i, dash, strokePattern; + var labels = g.getLabels(); + + if (typeof(x) === 'undefined') { + if (g.getOption('legend') != 'always') { + return ''; + } + + sepLines = g.getOption('labelsSeparateLines'); + html = ''; + for (i = 1; i < labels.length; i++) { + var series = g.getPropertiesForSeries(labels[i]); + if (!series.visible) continue; + + if (html !== '') html += (sepLines ? '<br/>' : ' '); + strokePattern = g.getOption("strokePattern", labels[i]); + dash = generateLegendDashHTML(strokePattern, series.color, oneEmWidth); + html += "<span style='font-weight: bold; color: " + series.color + ";'>" + + dash + " " + labels[i] + "</span>"; + } + return html; + } + + // TODO(danvk): remove this use of a private API + var xOptView = g.optionsViewForAxis_('x'); + var xvf = xOptView('valueFormatter'); + html = xvf(x, xOptView, labels[0], g); + if (html !== '') { + html += ':'; + } + + var yOptViews = []; + var num_axes = g.numAxes(); + for (i = 0; i < num_axes; i++) { + // TODO(danvk): remove this use of a private API + yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : '')); + } + var showZeros = g.getOption("labelsShowZeroValues"); + sepLines = g.getOption("labelsSeparateLines"); + var highlightSeries = g.getHighlightSeries(); + for (i = 0; i < sel_points.length; i++) { + var pt = sel_points[i]; + if (pt.yval === 0 && !showZeros) continue; + if (!Dygraph.isOK(pt.canvasy)) continue; + if (sepLines) html += "<br/>"; + + var series = g.getPropertiesForSeries(pt.name); + var yOptView = yOptViews[series.axis - 1]; + var fmtFunc = yOptView('valueFormatter'); + var yval = fmtFunc(pt.yval, yOptView, pt.name, g); + + var cls = (pt.name == highlightSeries) ? " class='highlight'" : ""; + + // TODO(danvk): use a template string here and make it an attribute. + html += "<span" + cls + ">" + " <b><span style='color: " + series.color + ";'>" + + pt.name + "</span></b>: " + yval + "</span>"; + } + return html; +}; + + +/** + * Generates html for the "dash" displayed on the legend when using "legend: always". + * In particular, this works for dashed lines with any stroke pattern. It will + * try to scale the pattern to fit in 1em width. Or if small enough repeat the + * pattern for 1em width. + * + * @param strokePattern The pattern + * @param color The color of the series. + * @param oneEmWidth The width in pixels of 1em in the legend. + * @private + */ +generateLegendDashHTML = function(strokePattern, color, oneEmWidth) { + // IE 7,8 fail at these divs, so they get boring legend, have not tested 9. + var isIE = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (isIE) return "—"; + + // Easy, common case: a solid line + if (!strokePattern || strokePattern.length <= 1) { + return "<div style=\"display: inline-block; position: relative; " + + "bottom: .5ex; padding-left: 1em; height: 1px; " + + "border-bottom: 2px solid " + color + ";\"></div>"; + } + + var i, j, paddingLeft, marginRight; + var strokePixelLength = 0, segmentLoop = 0; + var normalizedPattern = []; + var loop; + + // Compute the length of the pixels including the first segment twice, + // since we repeat it. + for (i = 0; i <= strokePattern.length; i++) { + strokePixelLength += strokePattern[i%strokePattern.length]; + } + + // See if we can loop the pattern by itself at least twice. + loop = Math.floor(oneEmWidth/(strokePixelLength-strokePattern[0])); + if (loop > 1) { + // This pattern fits at least two times, no scaling just convert to em; + for (i = 0; i < strokePattern.length; i++) { + normalizedPattern[i] = strokePattern[i]/oneEmWidth; + } + // Since we are repeating the pattern, we don't worry about repeating the + // first segment in one draw. + segmentLoop = normalizedPattern.length; + } else { + // If the pattern doesn't fit in the legend we scale it to fit. + loop = 1; + for (i = 0; i < strokePattern.length; i++) { + normalizedPattern[i] = strokePattern[i]/strokePixelLength; + } + // For the scaled patterns we do redraw the first segment. + segmentLoop = normalizedPattern.length+1; + } + + // Now make the pattern. + var dash = ""; + for (j = 0; j < loop; j++) { + for (i = 0; i < segmentLoop; i+=2) { + // The padding is the drawn segment. + paddingLeft = normalizedPattern[i%normalizedPattern.length]; + if (i < strokePattern.length) { + // The margin is the space segment. + marginRight = normalizedPattern[(i+1)%normalizedPattern.length]; + } else { + // The repeated first segment has no right margin. + marginRight = 0; + } + dash += "<div style=\"display: inline-block; position: relative; " + + "bottom: .5ex; margin-right: " + marginRight + "em; padding-left: " + + paddingLeft + "em; height: 1px; border-bottom: 2px solid " + color + + ";\"></div>"; + } + } + return dash; +}; + + +return legend; +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/range-selector.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/range-selector.js new file mode 100644 index 00000000..089e64aa --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/plugins/range-selector.js @@ -0,0 +1,852 @@ +/** + * @license + * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false,TouchEvent:false */ + +/** + * @fileoverview This file contains the RangeSelector plugin used to provide + * a timeline range selector widget for dygraphs. + */ + +Dygraph.Plugins.RangeSelector = (function() { + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + +var rangeSelector = function() { + this.isIE_ = /MSIE/.test(navigator.userAgent) && !window.opera; + this.hasTouchInterface_ = typeof(TouchEvent) != 'undefined'; + this.isMobileDevice_ = /mobile|android/gi.test(navigator.appVersion); + this.interfaceCreated_ = false; +}; + +rangeSelector.prototype.toString = function() { + return "RangeSelector Plugin"; +}; + +rangeSelector.prototype.activate = function(dygraph) { + this.dygraph_ = dygraph; + this.isUsingExcanvas_ = dygraph.isUsingExcanvas_; + if (this.getOption_('showRangeSelector')) { + this.createInterface_(); + } + return { + layout: this.reserveSpace_, + predraw: this.renderStaticLayer_, + didDrawChart: this.renderInteractiveLayer_ + }; +}; + +rangeSelector.prototype.destroy = function() { + this.bgcanvas_ = null; + this.fgcanvas_ = null; + this.leftZoomHandle_ = null; + this.rightZoomHandle_ = null; + this.iePanOverlay_ = null; +}; + +//------------------------------------------------------------------ +// Private methods +//------------------------------------------------------------------ + +rangeSelector.prototype.getOption_ = function(name) { + return this.dygraph_.getOption(name); +}; + +rangeSelector.prototype.setDefaultOption_ = function(name, value) { + return this.dygraph_.attrs_[name] = value; +}; + +/** + * @private + * Creates the range selector elements and adds them to the graph. + */ +rangeSelector.prototype.createInterface_ = function() { + this.createCanvases_(); + if (this.isUsingExcanvas_) { + this.createIEPanOverlay_(); + } + this.createZoomHandles_(); + this.initInteraction_(); + + // Range selector and animatedZooms have a bad interaction. See issue 359. + if (this.getOption_('animatedZooms')) { + this.dygraph_.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.'); + this.dygraph_.updateOptions({animatedZooms: false}, true); + } + + this.interfaceCreated_ = true; + this.addToGraph_(); +}; + +/** + * @private + * Adds the range selector to the graph. + */ +rangeSelector.prototype.addToGraph_ = function() { + var graphDiv = this.graphDiv_ = this.dygraph_.graphDiv; + graphDiv.appendChild(this.bgcanvas_); + graphDiv.appendChild(this.fgcanvas_); + graphDiv.appendChild(this.leftZoomHandle_); + graphDiv.appendChild(this.rightZoomHandle_); +}; + +/** + * @private + * Removes the range selector from the graph. + */ +rangeSelector.prototype.removeFromGraph_ = function() { + var graphDiv = this.graphDiv_; + graphDiv.removeChild(this.bgcanvas_); + graphDiv.removeChild(this.fgcanvas_); + graphDiv.removeChild(this.leftZoomHandle_); + graphDiv.removeChild(this.rightZoomHandle_); + this.graphDiv_ = null; +}; + +/** + * @private + * Called by Layout to allow range selector to reserve its space. + */ +rangeSelector.prototype.reserveSpace_ = function(e) { + if (this.getOption_('showRangeSelector')) { + e.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4); + } +}; + +/** + * @private + * Renders the static portion of the range selector at the predraw stage. + */ +rangeSelector.prototype.renderStaticLayer_ = function() { + if (!this.updateVisibility_()) { + return; + } + this.resize_(); + this.drawStaticLayer_(); +}; + +/** + * @private + * Renders the interactive portion of the range selector after the chart has been drawn. + */ +rangeSelector.prototype.renderInteractiveLayer_ = function() { + if (!this.updateVisibility_() || this.isChangingRange_) { + return; + } + this.placeZoomHandles_(); + this.drawInteractiveLayer_(); +}; + +/** + * @private + * Check to see if the range selector is enabled/disabled and update visibility accordingly. + */ +rangeSelector.prototype.updateVisibility_ = function() { + var enabled = this.getOption_('showRangeSelector'); + if (enabled) { + if (!this.interfaceCreated_) { + this.createInterface_(); + } else if (!this.graphDiv_ || !this.graphDiv_.parentNode) { + this.addToGraph_(); + } + } else if (this.graphDiv_) { + this.removeFromGraph_(); + var dygraph = this.dygraph_; + setTimeout(function() { dygraph.width_ = 0; dygraph.resize(); }, 1); + } + return enabled; +}; + +/** + * @private + * Resizes the range selector. + */ +rangeSelector.prototype.resize_ = function() { + function setElementRect(canvas, rect) { + canvas.style.top = rect.y + 'px'; + canvas.style.left = rect.x + 'px'; + canvas.width = rect.w; + canvas.height = rect.h; + canvas.style.width = canvas.width + 'px'; // for IE + canvas.style.height = canvas.height + 'px'; // for IE + } + + var plotArea = this.dygraph_.layout_.getPlotArea(); + + var xAxisLabelHeight = 0; + if(this.getOption_('drawXAxis')){ + xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize')); + } + this.canvasRect_ = { + x: plotArea.x, + y: plotArea.y + plotArea.h + xAxisLabelHeight + 4, + w: plotArea.w, + h: this.getOption_('rangeSelectorHeight') + }; + + setElementRect(this.bgcanvas_, this.canvasRect_); + setElementRect(this.fgcanvas_, this.canvasRect_); +}; + +/** + * @private + * Creates the background and foreground canvases. + */ +rangeSelector.prototype.createCanvases_ = function() { + this.bgcanvas_ = Dygraph.createCanvas(); + this.bgcanvas_.className = 'dygraph-rangesel-bgcanvas'; + this.bgcanvas_.style.position = 'absolute'; + this.bgcanvas_.style.zIndex = 9; + this.bgcanvas_ctx_ = Dygraph.getContext(this.bgcanvas_); + + this.fgcanvas_ = Dygraph.createCanvas(); + this.fgcanvas_.className = 'dygraph-rangesel-fgcanvas'; + this.fgcanvas_.style.position = 'absolute'; + this.fgcanvas_.style.zIndex = 9; + this.fgcanvas_.style.cursor = 'default'; + this.fgcanvas_ctx_ = Dygraph.getContext(this.fgcanvas_); +}; + +/** + * @private + * Creates overlay divs for IE/Excanvas so that mouse events are handled properly. + */ +rangeSelector.prototype.createIEPanOverlay_ = function() { + this.iePanOverlay_ = document.createElement("div"); + this.iePanOverlay_.style.position = 'absolute'; + this.iePanOverlay_.style.backgroundColor = 'white'; + this.iePanOverlay_.style.filter = 'alpha(opacity=0)'; + this.iePanOverlay_.style.display = 'none'; + this.iePanOverlay_.style.cursor = 'move'; + this.fgcanvas_.appendChild(this.iePanOverlay_); +}; + +/** + * @private + * Creates the zoom handle elements. + */ +rangeSelector.prototype.createZoomHandles_ = function() { + var img = new Image(); + img.className = 'dygraph-rangesel-zoomhandle'; + img.style.position = 'absolute'; + img.style.zIndex = 10; + img.style.visibility = 'hidden'; // Initially hidden so they don't show up in the wrong place. + img.style.cursor = 'col-resize'; + + if (/MSIE 7/.test(navigator.userAgent)) { // IE7 doesn't support embedded src data. + img.width = 7; + img.height = 14; + img.style.backgroundColor = 'white'; + img.style.border = '1px solid #333333'; // Just show box in IE7. + } else { + img.width = 9; + img.height = 16; + img.src = 'data:image/png;base64,' + +'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' + +'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' + +'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' + +'6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' + +'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII='; + } + + if (this.isMobileDevice_) { + img.width *= 2; + img.height *= 2; + } + + this.leftZoomHandle_ = img; + this.rightZoomHandle_ = img.cloneNode(false); +}; + +/** + * @private + * Sets up the interaction for the range selector. + */ +rangeSelector.prototype.initInteraction_ = function() { + var self = this; + var topElem = this.isIE_ ? document : window; + var clientXLast = 0; + var handle = null; + var isZooming = false; + var isPanning = false; + var dynamic = !this.isMobileDevice_ && !this.isUsingExcanvas_; + + // We cover iframes during mouse interactions. See comments in + // dygraph-utils.js for more info on why this is a good idea. + var tarp = new Dygraph.IFrameTarp(); + + // functions, defined below. Defining them this way (rather than with + // "function foo() {...}" makes JSHint happy. + var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone, + onPanStart, onPan, onPanEnd, doPan, onCanvasHover; + + // Touch event functions + var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents; + + toXDataWindow = function(zoomHandleStatus) { + var xDataLimits = self.dygraph_.xAxisExtremes(); + var fact = (xDataLimits[1] - xDataLimits[0])/self.canvasRect_.w; + var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x)*fact; + var xDataMax = xDataLimits[0] + (zoomHandleStatus.rightHandlePos - self.canvasRect_.x)*fact; + return [xDataMin, xDataMax]; + }; + + onZoomStart = function(e) { + Dygraph.cancelEvent(e); + isZooming = true; + clientXLast = e.clientX; + handle = e.target ? e.target : e.srcElement; + if (e.type === 'mousedown' || e.type === 'dragstart') { + // These events are removed manually. + Dygraph.addEvent(topElem, 'mousemove', onZoom); + Dygraph.addEvent(topElem, 'mouseup', onZoomEnd); + } + self.fgcanvas_.style.cursor = 'col-resize'; + tarp.cover(); + return true; + }; + + onZoom = function(e) { + if (!isZooming) { + return false; + } + Dygraph.cancelEvent(e); + + var delX = e.clientX - clientXLast; + if (Math.abs(delX) < 4) { + return true; + } + clientXLast = e.clientX; + + // Move handle. + var zoomHandleStatus = self.getZoomHandleStatus_(); + var newPos; + if (handle == self.leftZoomHandle_) { + newPos = zoomHandleStatus.leftHandlePos + delX; + newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3); + newPos = Math.max(newPos, self.canvasRect_.x); + } else { + newPos = zoomHandleStatus.rightHandlePos + delX; + newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w); + newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3); + } + var halfHandleWidth = handle.width/2; + handle.style.left = (newPos - halfHandleWidth) + 'px'; + self.drawInteractiveLayer_(); + + // Zoom on the fly (if not using excanvas). + if (dynamic) { + doZoom(); + } + return true; + }; + + onZoomEnd = function(e) { + if (!isZooming) { + return false; + } + isZooming = false; + tarp.uncover(); + Dygraph.removeEvent(topElem, 'mousemove', onZoom); + Dygraph.removeEvent(topElem, 'mouseup', onZoomEnd); + self.fgcanvas_.style.cursor = 'default'; + + // If using excanvas, Zoom now. + if (!dynamic) { + doZoom(); + } + return true; + }; + + doZoom = function() { + try { + var zoomHandleStatus = self.getZoomHandleStatus_(); + self.isChangingRange_ = true; + if (!zoomHandleStatus.isZoomed) { + self.dygraph_.resetZoom(); + } else { + var xDataWindow = toXDataWindow(zoomHandleStatus); + self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]); + } + } finally { + self.isChangingRange_ = false; + } + }; + + isMouseInPanZone = function(e) { + if (self.isUsingExcanvas_) { + return e.srcElement == self.iePanOverlay_; + } else { + var rect = self.leftZoomHandle_.getBoundingClientRect(); + var leftHandleClientX = rect.left + rect.width/2; + rect = self.rightZoomHandle_.getBoundingClientRect(); + var rightHandleClientX = rect.left + rect.width/2; + return (e.clientX > leftHandleClientX && e.clientX < rightHandleClientX); + } + }; + + onPanStart = function(e) { + if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) { + Dygraph.cancelEvent(e); + isPanning = true; + clientXLast = e.clientX; + if (e.type === 'mousedown') { + // These events are removed manually. + Dygraph.addEvent(topElem, 'mousemove', onPan); + Dygraph.addEvent(topElem, 'mouseup', onPanEnd); + } + return true; + } + return false; + }; + + onPan = function(e) { + if (!isPanning) { + return false; + } + Dygraph.cancelEvent(e); + + var delX = e.clientX - clientXLast; + if (Math.abs(delX) < 4) { + return true; + } + clientXLast = e.clientX; + + // Move range view + var zoomHandleStatus = self.getZoomHandleStatus_(); + var leftHandlePos = zoomHandleStatus.leftHandlePos; + var rightHandlePos = zoomHandleStatus.rightHandlePos; + var rangeSize = rightHandlePos - leftHandlePos; + if (leftHandlePos + delX <= self.canvasRect_.x) { + leftHandlePos = self.canvasRect_.x; + rightHandlePos = leftHandlePos + rangeSize; + } else if (rightHandlePos + delX >= self.canvasRect_.x + self.canvasRect_.w) { + rightHandlePos = self.canvasRect_.x + self.canvasRect_.w; + leftHandlePos = rightHandlePos - rangeSize; + } else { + leftHandlePos += delX; + rightHandlePos += delX; + } + var halfHandleWidth = self.leftZoomHandle_.width/2; + self.leftZoomHandle_.style.left = (leftHandlePos - halfHandleWidth) + 'px'; + self.rightZoomHandle_.style.left = (rightHandlePos - halfHandleWidth) + 'px'; + self.drawInteractiveLayer_(); + + // Do pan on the fly (if not using excanvas). + if (dynamic) { + doPan(); + } + return true; + }; + + onPanEnd = function(e) { + if (!isPanning) { + return false; + } + isPanning = false; + Dygraph.removeEvent(topElem, 'mousemove', onPan); + Dygraph.removeEvent(topElem, 'mouseup', onPanEnd); + // If using excanvas, do pan now. + if (!dynamic) { + doPan(); + } + return true; + }; + + doPan = function() { + try { + self.isChangingRange_ = true; + self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_()); + self.dygraph_.drawGraph_(false); + } finally { + self.isChangingRange_ = false; + } + }; + + onCanvasHover = function(e) { + if (isZooming || isPanning) { + return; + } + var cursor = isMouseInPanZone(e) ? 'move' : 'default'; + if (cursor != self.fgcanvas_.style.cursor) { + self.fgcanvas_.style.cursor = cursor; + } + }; + + onZoomHandleTouchEvent = function(e) { + if (e.type == 'touchstart' && e.targetTouches.length == 1) { + if (onZoomStart(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { + if (onZoom(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else { + onZoomEnd(e); + } + }; + + onCanvasTouchEvent = function(e) { + if (e.type == 'touchstart' && e.targetTouches.length == 1) { + if (onPanStart(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { + if (onPan(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else { + onPanEnd(e); + } + }; + + addTouchEvents = function(elem, fn) { + var types = ['touchstart', 'touchend', 'touchmove', 'touchcancel']; + for (var i = 0; i < types.length; i++) { + self.dygraph_.addAndTrackEvent(elem, types[i], fn); + } + }; + + this.setDefaultOption_('interactionModel', Dygraph.Interaction.dragIsPanInteractionModel); + this.setDefaultOption_('panEdgeFraction', 0.0001); + + var dragStartEvent = window.opera ? 'mousedown' : 'dragstart'; + this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart); + this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart); + + if (this.isUsingExcanvas_) { + this.dygraph_.addAndTrackEvent(this.iePanOverlay_, 'mousedown', onPanStart); + } else { + this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart); + this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover); + } + + // Touch events + if (this.hasTouchInterface_) { + addTouchEvents(this.leftZoomHandle_, onZoomHandleTouchEvent); + addTouchEvents(this.rightZoomHandle_, onZoomHandleTouchEvent); + addTouchEvents(this.fgcanvas_, onCanvasTouchEvent); + } +}; + +/** + * @private + * Draws the static layer in the background canvas. + */ +rangeSelector.prototype.drawStaticLayer_ = function() { + var ctx = this.bgcanvas_ctx_; + ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); + try { + this.drawMiniPlot_(); + } catch(ex) { + Dygraph.warn(ex); + } + + var margin = 0.5; + this.bgcanvas_ctx_.lineWidth = 1; + ctx.strokeStyle = 'gray'; + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(margin, this.canvasRect_.h-margin); + ctx.lineTo(this.canvasRect_.w-margin, this.canvasRect_.h-margin); + ctx.lineTo(this.canvasRect_.w-margin, margin); + ctx.stroke(); +}; + + +/** + * @private + * Draws the mini plot in the background canvas. + */ +rangeSelector.prototype.drawMiniPlot_ = function() { + var fillStyle = this.getOption_('rangeSelectorPlotFillColor'); + var strokeStyle = this.getOption_('rangeSelectorPlotStrokeColor'); + if (!fillStyle && !strokeStyle) { + return; + } + + var stepPlot = this.getOption_('stepPlot'); + + var combinedSeriesData = this.computeCombinedSeriesAndLimits_(); + var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin; + + // Draw the mini plot. + var ctx = this.bgcanvas_ctx_; + var margin = 0.5; + + var xExtremes = this.dygraph_.xAxisExtremes(); + var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30); + var xFact = (this.canvasRect_.w - margin)/xRange; + var yFact = (this.canvasRect_.h - margin)/yRange; + var canvasWidth = this.canvasRect_.w - margin; + var canvasHeight = this.canvasRect_.h - margin; + + var prevX = null, prevY = null; + + ctx.beginPath(); + ctx.moveTo(margin, canvasHeight); + for (var i = 0; i < combinedSeriesData.data.length; i++) { + var dataPoint = combinedSeriesData.data[i]; + var x = ((dataPoint[0] !== null) ? ((dataPoint[0] - xExtremes[0])*xFact) : NaN); + var y = ((dataPoint[1] !== null) ? (canvasHeight - (dataPoint[1] - combinedSeriesData.yMin)*yFact) : NaN); + if (isFinite(x) && isFinite(y)) { + if(prevX === null) { + ctx.lineTo(x, canvasHeight); + } + else if (stepPlot) { + ctx.lineTo(x, prevY); + } + ctx.lineTo(x, y); + prevX = x; + prevY = y; + } + else { + if(prevX !== null) { + if (stepPlot) { + ctx.lineTo(x, prevY); + ctx.lineTo(x, canvasHeight); + } + else { + ctx.lineTo(prevX, canvasHeight); + } + } + prevX = prevY = null; + } + } + ctx.lineTo(canvasWidth, canvasHeight); + ctx.closePath(); + + if (fillStyle) { + var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight); + lingrad.addColorStop(0, 'white'); + lingrad.addColorStop(1, fillStyle); + this.bgcanvas_ctx_.fillStyle = lingrad; + ctx.fill(); + } + + if (strokeStyle) { + this.bgcanvas_ctx_.strokeStyle = strokeStyle; + this.bgcanvas_ctx_.lineWidth = 1.5; + ctx.stroke(); + } +}; + +/** + * @private + * Computes and returns the combinded series data along with min/max for the mini plot. + * @return {Object} An object containing combinded series array, ymin, ymax. + */ +rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() { + var data = this.dygraph_.rawData_; + var logscale = this.getOption_('logscale'); + + // Create a combined series (average of all series values). + var combinedSeries = []; + var sum; + var count; + var mutipleValues; + var i, j, k; + var xVal, yVal; + + // Find out if data has multiple values per datapoint. + // Go to first data point that actually has values (see http://code.google.com/p/dygraphs/issues/detail?id=246) + for (i = 0; i < data.length; i++) { + if (data[i].length > 1 && data[i][1] !== null) { + mutipleValues = typeof data[i][1] != 'number'; + if (mutipleValues) { + sum = []; + count = []; + for (k = 0; k < data[i][1].length; k++) { + sum.push(0); + count.push(0); + } + } + break; + } + } + + for (i = 0; i < data.length; i++) { + var dataPoint = data[i]; + xVal = dataPoint[0]; + + if (mutipleValues) { + for (k = 0; k < sum.length; k++) { + sum[k] = count[k] = 0; + } + } else { + sum = count = 0; + } + + for (j = 1; j < dataPoint.length; j++) { + if (this.dygraph_.visibility()[j-1]) { + var y; + if (mutipleValues) { + for (k = 0; k < sum.length; k++) { + y = dataPoint[j][k]; + if (y === null || isNaN(y)) continue; + sum[k] += y; + count[k]++; + } + } else { + y = dataPoint[j]; + if (y === null || isNaN(y)) continue; + sum += y; + count++; + } + } + } + + if (mutipleValues) { + for (k = 0; k < sum.length; k++) { + sum[k] /= count[k]; + } + yVal = sum.slice(0); + } else { + yVal = sum/count; + } + + combinedSeries.push([xVal, yVal]); + } + + // Account for roll period, fractions. + combinedSeries = this.dygraph_.rollingAverage(combinedSeries, this.dygraph_.rollPeriod_); + + if (typeof combinedSeries[0][1] != 'number') { + for (i = 0; i < combinedSeries.length; i++) { + yVal = combinedSeries[i][1]; + combinedSeries[i][1] = yVal[0]; + } + } + + // Compute the y range. + var yMin = Number.MAX_VALUE; + var yMax = -Number.MAX_VALUE; + for (i = 0; i < combinedSeries.length; i++) { + yVal = combinedSeries[i][1]; + if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) { + yMin = Math.min(yMin, yVal); + yMax = Math.max(yMax, yVal); + } + } + + // Convert Y data to log scale if needed. + // Also, expand the Y range to compress the mini plot a little. + var extraPercent = 0.25; + if (logscale) { + yMax = Dygraph.log10(yMax); + yMax += yMax*extraPercent; + yMin = Dygraph.log10(yMin); + for (i = 0; i < combinedSeries.length; i++) { + combinedSeries[i][1] = Dygraph.log10(combinedSeries[i][1]); + } + } else { + var yExtra; + var yRange = yMax - yMin; + if (yRange <= Number.MIN_VALUE) { + yExtra = yMax*extraPercent; + } else { + yExtra = yRange*extraPercent; + } + yMax += yExtra; + yMin -= yExtra; + } + + return {data: combinedSeries, yMin: yMin, yMax: yMax}; +}; + +/** + * @private + * Places the zoom handles in the proper position based on the current X data window. + */ +rangeSelector.prototype.placeZoomHandles_ = function() { + var xExtremes = this.dygraph_.xAxisExtremes(); + var xWindowLimits = this.dygraph_.xAxisRange(); + var xRange = xExtremes[1] - xExtremes[0]; + var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0])/xRange); + var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1])/xRange); + var leftCoord = this.canvasRect_.x + this.canvasRect_.w*leftPercent; + var rightCoord = this.canvasRect_.x + this.canvasRect_.w*(1 - rightPercent); + var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height)/2); + var halfHandleWidth = this.leftZoomHandle_.width/2; + this.leftZoomHandle_.style.left = (leftCoord - halfHandleWidth) + 'px'; + this.leftZoomHandle_.style.top = handleTop + 'px'; + this.rightZoomHandle_.style.left = (rightCoord - halfHandleWidth) + 'px'; + this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top; + + this.leftZoomHandle_.style.visibility = 'visible'; + this.rightZoomHandle_.style.visibility = 'visible'; +}; + +/** + * @private + * Draws the interactive layer in the foreground canvas. + */ +rangeSelector.prototype.drawInteractiveLayer_ = function() { + var ctx = this.fgcanvas_ctx_; + ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); + var margin = 1; + var width = this.canvasRect_.w - margin; + var height = this.canvasRect_.h - margin; + var zoomHandleStatus = this.getZoomHandleStatus_(); + + ctx.strokeStyle = 'black'; + if (!zoomHandleStatus.isZoomed) { + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(margin, height); + ctx.lineTo(width, height); + ctx.lineTo(width, margin); + ctx.stroke(); + if (this.iePanOverlay_) { + this.iePanOverlay_.style.display = 'none'; + } + } else { + var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x); + var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x); + + ctx.fillStyle = 'rgba(240, 240, 240, 0.6)'; + ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h); + ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h); + + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(leftHandleCanvasPos, margin); + ctx.lineTo(leftHandleCanvasPos, height); + ctx.lineTo(rightHandleCanvasPos, height); + ctx.lineTo(rightHandleCanvasPos, margin); + ctx.lineTo(width, margin); + ctx.stroke(); + + if (this.isUsingExcanvas_) { + this.iePanOverlay_.style.width = (rightHandleCanvasPos - leftHandleCanvasPos) + 'px'; + this.iePanOverlay_.style.left = leftHandleCanvasPos + 'px'; + this.iePanOverlay_.style.height = height + 'px'; + this.iePanOverlay_.style.display = 'inline'; + } + } +}; + +/** + * @private + * Returns the current zoom handle position information. + * @return {Object} The zoom handle status. + */ +rangeSelector.prototype.getZoomHandleStatus_ = function() { + var halfHandleWidth = this.leftZoomHandle_.width/2; + var leftHandlePos = parseFloat(this.leftZoomHandle_.style.left) + halfHandleWidth; + var rightHandlePos = parseFloat(this.rightZoomHandle_.style.left) + halfHandleWidth; + return { + leftHandlePos: leftHandlePos, + rightHandlePos: rightHandlePos, + isZoomed: (leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x+this.canvasRect_.w) + }; +}; + +return rangeSelector; + +})(); diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/rgbcolor/rgbcolor.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/rgbcolor/rgbcolor.js new file mode 100644 index 00000000..5a882446 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/rgbcolor/rgbcolor.js @@ -0,0 +1,257 @@ +/** + * A class to parse color values + * + * NOTE: modified by danvk. I removed the "getHelpXML" function to reduce the + * file size, added "use strict" and a few "var" declarations where needed. + * + * Modifications by adilh: + * Original "RGBColor" function name collides with: + * http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-RGBColor + * so renamed to "RGBColorParser" + * + * @author Stoyan Stefanov <sstoo@gmail.com> + * @link http://www.phpied.com/rgb-color-parser-in-javascript/ + * @license Use it if you like it + */ +"use strict"; + +function RGBColorParser(color_string) +{ + this.ok = false; + + // strip any leading # + if (color_string.charAt(0) == '#') { // remove # if any + color_string = color_string.substr(1,6); + } + + color_string = color_string.replace(/ /g,''); + color_string = color_string.toLowerCase(); + + // before getting into regexps, try simple matches + // and overwrite the input + var simple_colors = { + aliceblue: 'f0f8ff', + antiquewhite: 'faebd7', + aqua: '00ffff', + aquamarine: '7fffd4', + azure: 'f0ffff', + beige: 'f5f5dc', + bisque: 'ffe4c4', + black: '000000', + blanchedalmond: 'ffebcd', + blue: '0000ff', + blueviolet: '8a2be2', + brown: 'a52a2a', + burlywood: 'deb887', + cadetblue: '5f9ea0', + chartreuse: '7fff00', + chocolate: 'd2691e', + coral: 'ff7f50', + cornflowerblue: '6495ed', + cornsilk: 'fff8dc', + crimson: 'dc143c', + cyan: '00ffff', + darkblue: '00008b', + darkcyan: '008b8b', + darkgoldenrod: 'b8860b', + darkgray: 'a9a9a9', + darkgreen: '006400', + darkkhaki: 'bdb76b', + darkmagenta: '8b008b', + darkolivegreen: '556b2f', + darkorange: 'ff8c00', + darkorchid: '9932cc', + darkred: '8b0000', + darksalmon: 'e9967a', + darkseagreen: '8fbc8f', + darkslateblue: '483d8b', + darkslategray: '2f4f4f', + darkturquoise: '00ced1', + darkviolet: '9400d3', + deeppink: 'ff1493', + deepskyblue: '00bfff', + dimgray: '696969', + dodgerblue: '1e90ff', + feldspar: 'd19275', + firebrick: 'b22222', + floralwhite: 'fffaf0', + forestgreen: '228b22', + fuchsia: 'ff00ff', + gainsboro: 'dcdcdc', + ghostwhite: 'f8f8ff', + gold: 'ffd700', + goldenrod: 'daa520', + gray: '808080', + green: '008000', + greenyellow: 'adff2f', + honeydew: 'f0fff0', + hotpink: 'ff69b4', + indianred : 'cd5c5c', + indigo : '4b0082', + ivory: 'fffff0', + khaki: 'f0e68c', + lavender: 'e6e6fa', + lavenderblush: 'fff0f5', + lawngreen: '7cfc00', + lemonchiffon: 'fffacd', + lightblue: 'add8e6', + lightcoral: 'f08080', + lightcyan: 'e0ffff', + lightgoldenrodyellow: 'fafad2', + lightgrey: 'd3d3d3', + lightgreen: '90ee90', + lightpink: 'ffb6c1', + lightsalmon: 'ffa07a', + lightseagreen: '20b2aa', + lightskyblue: '87cefa', + lightslateblue: '8470ff', + lightslategray: '778899', + lightsteelblue: 'b0c4de', + lightyellow: 'ffffe0', + lime: '00ff00', + limegreen: '32cd32', + linen: 'faf0e6', + magenta: 'ff00ff', + maroon: '800000', + mediumaquamarine: '66cdaa', + mediumblue: '0000cd', + mediumorchid: 'ba55d3', + mediumpurple: '9370d8', + mediumseagreen: '3cb371', + mediumslateblue: '7b68ee', + mediumspringgreen: '00fa9a', + mediumturquoise: '48d1cc', + mediumvioletred: 'c71585', + midnightblue: '191970', + mintcream: 'f5fffa', + mistyrose: 'ffe4e1', + moccasin: 'ffe4b5', + navajowhite: 'ffdead', + navy: '000080', + oldlace: 'fdf5e6', + olive: '808000', + olivedrab: '6b8e23', + orange: 'ffa500', + orangered: 'ff4500', + orchid: 'da70d6', + palegoldenrod: 'eee8aa', + palegreen: '98fb98', + paleturquoise: 'afeeee', + palevioletred: 'd87093', + papayawhip: 'ffefd5', + peachpuff: 'ffdab9', + peru: 'cd853f', + pink: 'ffc0cb', + plum: 'dda0dd', + powderblue: 'b0e0e6', + purple: '800080', + red: 'ff0000', + rosybrown: 'bc8f8f', + royalblue: '4169e1', + saddlebrown: '8b4513', + salmon: 'fa8072', + sandybrown: 'f4a460', + seagreen: '2e8b57', + seashell: 'fff5ee', + sienna: 'a0522d', + silver: 'c0c0c0', + skyblue: '87ceeb', + slateblue: '6a5acd', + slategray: '708090', + snow: 'fffafa', + springgreen: '00ff7f', + steelblue: '4682b4', + tan: 'd2b48c', + teal: '008080', + thistle: 'd8bfd8', + tomato: 'ff6347', + turquoise: '40e0d0', + violet: 'ee82ee', + violetred: 'd02090', + wheat: 'f5deb3', + white: 'ffffff', + whitesmoke: 'f5f5f5', + yellow: 'ffff00', + yellowgreen: '9acd32' + }; + for (var key in simple_colors) { + if (color_string == key) { + color_string = simple_colors[key]; + } + } + // emd of simple type-in colors + + // array of color definition objects + var color_defs = [ + { + re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, + example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], + process: function (bits){ + return [ + parseInt(bits[1]), + parseInt(bits[2]), + parseInt(bits[3]) + ]; + } + }, + { + re: /^(\w{2})(\w{2})(\w{2})$/, + example: ['#00ff00', '336699'], + process: function (bits){ + return [ + parseInt(bits[1], 16), + parseInt(bits[2], 16), + parseInt(bits[3], 16) + ]; + } + }, + { + re: /^(\w{1})(\w{1})(\w{1})$/, + example: ['#fb0', 'f0f'], + process: function (bits){ + return [ + parseInt(bits[1] + bits[1], 16), + parseInt(bits[2] + bits[2], 16), + parseInt(bits[3] + bits[3], 16) + ]; + } + } + ]; + + // search through the definitions to find a match + for (var i = 0; i < color_defs.length; i++) { + var re = color_defs[i].re; + var processor = color_defs[i].process; + var bits = re.exec(color_string); + if (bits) { + var channels = processor(bits); + this.r = channels[0]; + this.g = channels[1]; + this.b = channels[2]; + this.ok = true; + } + + } + + // validate/cleanup values + this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r); + this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g); + this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b); + + // some getters + this.toRGB = function () { + return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; + } + this.toHex = function () { + var r = this.r.toString(16); + var g = this.g.toString(16); + var b = this.b.toString(16); + if (r.length == 1) r = '0' + r; + if (g.length == 1) g = '0' + g; + if (b.length == 1) b = '0' + b; + return '#' + r + g + b; + } + + +} + diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/stacktrace.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/stacktrace.js new file mode 100644 index 00000000..4d0384d1 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/stacktrace.js @@ -0,0 +1,411 @@ +// Domain Public by Eric Wendelin http://eriwen.com/ (2008) +// Luke Smith http://lucassmith.name/ (2008) +// Loic Dachary <loic@dachary.org> (2008) +// Johan Euphrosine <proppy@aminche.com> (2008) +// Oyvind Sean Kinsey http://kinsey.no/blog (2010) +// Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010) + +/** + * Main function giving a function stack trace with a forced or passed in Error + * + * @cfg {Error} e The error to create a stacktrace from (optional) + * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions + * @return {Array} of Strings with functions, lines, files, and arguments where possible + */ +function printStackTrace(options) { + options = options || {guess: true}; + var ex = options.e || null, guess = !!options.guess; + var p = new printStackTrace.implementation(), result = p.run(ex); + return (guess) ? p.guessAnonymousFunctions(result) : result; +} + +printStackTrace.implementation = function() { +}; + +printStackTrace.implementation.prototype = { + /** + * @param {Error} ex The error to create a stacktrace from (optional) + * @param {String} mode Forced mode (optional, mostly for unit tests) + */ + run: function(ex, mode) { + ex = ex || this.createException(); + // examine exception properties w/o debugger + //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);} + mode = mode || this.mode(ex); + if (mode === 'other') { + return this.other(arguments.callee); + } else { + return this[mode](ex); + } + }, + + createException: function() { + try { + this.undef(); + } catch (e) { + return e; + } + }, + + /** + * Mode could differ for different exception, e.g. + * exceptions in Chrome may or may not have arguments or stack. + * + * @return {String} mode of operation for the exception + */ + mode: function(e) { + if (e['arguments'] && e.stack) { + return 'chrome'; + } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) { + // e.message.indexOf("Backtrace:") > -1 -> opera + // !e.stacktrace -> opera + if (!e.stacktrace) { + return 'opera9'; // use e.message + } + // 'opera#sourceloc' in e -> opera9, opera10a + if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) { + return 'opera9'; // use e.message + } + // e.stacktrace && !e.stack -> opera10a + if (!e.stack) { + return 'opera10a'; // use e.stacktrace + } + // e.stacktrace && e.stack -> opera10b + if (e.stacktrace.indexOf("called from line") < 0) { + return 'opera10b'; // use e.stacktrace, format differs from 'opera10a' + } + // e.stacktrace && e.stack -> opera11 + return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b' + } else if (e.stack) { + return 'firefox'; + } + return 'other'; + }, + + /** + * Given a context, function name, and callback function, overwrite it so that it calls + * printStackTrace() first with a callback and then runs the rest of the body. + * + * @param {Object} context of execution (e.g. window) + * @param {String} functionName to instrument + * @param {Function} function to call with a stack trace on invocation + */ + instrumentFunction: function(context, functionName, callback) { + context = context || window; + var original = context[functionName]; + context[functionName] = function instrumented() { + callback.call(this, printStackTrace().slice(4)); + return context[functionName]._instrumented.apply(this, arguments); + }; + context[functionName]._instrumented = original; + }, + + /** + * Given a context and function name of a function that has been + * instrumented, revert the function to it's original (non-instrumented) + * state. + * + * @param {Object} context of execution (e.g. window) + * @param {String} functionName to de-instrument + */ + deinstrumentFunction: function(context, functionName) { + if (context[functionName].constructor === Function && + context[functionName]._instrumented && + context[functionName]._instrumented.constructor === Function) { + context[functionName] = context[functionName]._instrumented; + } + }, + + /** + * Given an Error object, return a formatted Array based on Chrome's stack string. + * + * @param e - Error object to inspect + * @return Array<String> of function calls, files and line numbers + */ + chrome: function(e) { + var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, ''). + replace(/^\s+at\s+/gm, ''). + replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2'). + replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n'); + stack.pop(); + return stack; + }, + + /** + * Given an Error object, return a formatted Array based on Firefox's stack string. + * + * @param e - Error object to inspect + * @return Array<String> of function calls, files and line numbers + */ + firefox: function(e) { + return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n'); + }, + + opera11: function(e) { + // "Error thrown at line 42, column 12 in <anonymous function>() in file://localhost/G:/js/stacktrace.js:\n" + // "Error thrown at line 42, column 12 in <anonymous function: createException>() in file://localhost/G:/js/stacktrace.js:\n" + // "called from line 7, column 4 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n" + // "called from line 15, column 3 in file://localhost/G:/js/test/functional/testcase1.html:\n" + var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/; + var lines = e.stacktrace.split('\n'), result = []; + + for (var i = 0, len = lines.length; i < len; i += 2) { + var match = lineRE.exec(lines[i]); + if (match) { + var location = match[4] + ':' + match[1] + ':' + match[2]; + var fnName = match[3] || "global code"; + fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON); + result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, '')); + } + } + + return result; + }, + + opera10b: function(e) { + // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" + + // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" + + // "@file://localhost/G:/js/test/functional/testcase1.html:15" + var ANON = '{anonymous}', lineRE = /^(.*)@(.+):(\d+)$/; + var lines = e.stacktrace.split('\n'), result = []; + + for (var i = 0, len = lines.length; i < len; i++) { + var match = lineRE.exec(lines[i]); + if (match) { + var fnName = match[1]? (match[1] + '()') : "global code"; + result.push(fnName + '@' + match[2] + ':' + match[3]); + } + } + + return result; + }, + + /** + * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. + * + * @param e - Error object to inspect + * @return Array<String> of function calls, files and line numbers + */ + opera10a: function(e) { + // " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" + // " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; + var lines = e.stacktrace.split('\n'), result = []; + + for (var i = 0, len = lines.length; i < len; i += 2) { + var match = lineRE.exec(lines[i]); + if (match) { + var fnName = match[3] || ANON; + result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); + } + } + + return result; + }, + + // Opera 7.x-9.2x only! + opera9: function(e) { + // " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n" + // " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i; + var lines = e.message.split('\n'), result = []; + + for (var i = 2, len = lines.length; i < len; i += 2) { + var match = lineRE.exec(lines[i]); + if (match) { + result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); + } + } + + return result; + }, + + // Safari, IE, and others + other: function(curr) { + var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10; + while (curr && stack.length < maxStackSize) { + fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; + args = Array.prototype.slice.call(curr['arguments'] || []); + stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; + curr = curr.caller; + } + return stack; + }, + + /** + * Given arguments array as a String, subsituting type names for non-string types. + * + * @param {Arguments} object + * @return {Array} of Strings with stringified arguments + */ + stringifyArguments: function(args) { + var result = []; + var slice = Array.prototype.slice; + for (var i = 0; i < args.length; ++i) { + var arg = args[i]; + if (arg === undefined) { + result[i] = 'undefined'; + } else if (arg === null) { + result[i] = 'null'; + } else if (arg.constructor) { + if (arg.constructor === Array) { + if (arg.length < 3) { + result[i] = '[' + this.stringifyArguments(arg) + ']'; + } else { + result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']'; + } + } else if (arg.constructor === Object) { + result[i] = '#object'; + } else if (arg.constructor === Function) { + result[i] = '#function'; + } else if (arg.constructor === String) { + result[i] = '"' + arg + '"'; + } else if (arg.constructor === Number) { + result[i] = arg; + } + } + } + return result.join(','); + }, + + sourceCache: {}, + + /** + * @return the text from a given URL + */ + ajax: function(url) { + var req = this.createXMLHTTPObject(); + if (req) { + try { + req.open('GET', url, false); + req.send(null); + return req.responseText; + } catch (e) { + } + } + return ''; + }, + + /** + * Try XHR methods in order and store XHR factory. + * + * @return <Function> XHR function or equivalent + */ + createXMLHTTPObject: function() { + var xmlhttp, XMLHttpFactories = [ + function() { + return new XMLHttpRequest(); + }, function() { + return new ActiveXObject('Msxml2.XMLHTTP'); + }, function() { + return new ActiveXObject('Msxml3.XMLHTTP'); + }, function() { + return new ActiveXObject('Microsoft.XMLHTTP'); + } + ]; + for (var i = 0; i < XMLHttpFactories.length; i++) { + try { + xmlhttp = XMLHttpFactories[i](); + // Use memoization to cache the factory + this.createXMLHTTPObject = XMLHttpFactories[i]; + return xmlhttp; + } catch (e) { + } + } + }, + + /** + * Given a URL, check if it is in the same domain (so we can get the source + * via Ajax). + * + * @param url <String> source url + * @return False if we need a cross-domain request + */ + isSameDomain: function(url) { + return url.indexOf(location.hostname) !== -1; + }, + + /** + * Get source code from given URL if in the same domain. + * + * @param url <String> JS source URL + * @return <Array> Array of source code lines + */ + getSource: function(url) { + // TODO reuse source from script tags? + if (!(url in this.sourceCache)) { + this.sourceCache[url] = this.ajax(url).split('\n'); + } + return this.sourceCache[url]; + }, + + guessAnonymousFunctions: function(stack) { + for (var i = 0; i < stack.length; ++i) { + var reStack = /\{anonymous\}\(.*\)@(.*)/, + reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/, + frame = stack[i], ref = reStack.exec(frame); + + if (ref) { + var m = reRef.exec(ref[1]), file = m[1], + lineno = m[2], charno = m[3] || 0; + if (file && this.isSameDomain(file) && lineno) { + var functionName = this.guessAnonymousFunction(file, lineno, charno); + stack[i] = frame.replace('{anonymous}', functionName); + } + } + } + return stack; + }, + + guessAnonymousFunction: function(url, lineNo, charNo) { + var ret; + try { + ret = this.findFunctionName(this.getSource(url), lineNo); + } catch (e) { + ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString(); + } + return ret; + }, + + findFunctionName: function(source, lineNo) { + // FIXME findFunctionName fails for compressed source + // (more than one function on the same line) + // TODO use captured args + // function {name}({args}) m[1]=name m[2]=args + var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/; + // {name} = function ({args}) TODO args capture + // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/ + var reFunctionExpression = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/; + // {name} = eval() + var reFunctionEvaluation = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/; + // Walk backwards in the source lines until we find + // the line which matches one of the patterns above + var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos; + for (var i = 0; i < maxLines; ++i) { + // lineNo is 1-based, source[] is 0-based + line = source[lineNo - i - 1]; + commentPos = line.indexOf('//'); + if (commentPos >= 0) { + line = line.substr(0, commentPos); + } + // TODO check other types of comments? Commented code may lead to false positive + if (line) { + code = line + code; + m = reFunctionExpression.exec(code); + if (m && m[1]) { + return m[1]; + } + m = reFunctionDeclaration.exec(code); + if (m && m[1]) { + //return m[1] + "(" + (m[2] || "") + ")"; + return m[1]; + } + m = reFunctionEvaluation.exec(code); + if (m && m[1]) { + return m[1]; + } + } + } + return '(?)'; + } +}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/Doxyfile b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/Doxyfile new file mode 100644 index 00000000..26ac5a53 --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/Doxyfile @@ -0,0 +1,243 @@ +# Doxyfile 1.5.3 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = strftime +PROJECT_NUMBER = 1.3 +OUTPUT_DIRECTORY = /home/philip/public_html/hacks/strftime/docs/ +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class " \ + "The $name widget " \ + "The $name file " \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = /home/philip/public_html/hacks/strftime/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text " +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = /home/philip/public_html/hacks/strftime +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.js +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = . +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = YES +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = Date \ + Date.ext \ + Date.prototype +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +HTML_DYNAMIC_SECTIONS = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = YES +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = YES diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/strftime-min.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/strftime-min.js new file mode 100644 index 00000000..8207714e --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/strftime-min.js @@ -0,0 +1 @@ +Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(x,pad,r){if(typeof (r)=="undefined"){r=10}for(;parseInt(x,10)<r&&r>1;r/=10){x=pad.toString()+x}return x.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(d){return Date.ext.locales[d.locale].a[d.getDay()]},A:function(d){return Date.ext.locales[d.locale].A[d.getDay()]},b:function(d){return Date.ext.locales[d.locale].b[d.getMonth()]},B:function(d){return Date.ext.locales[d.locale].B[d.getMonth()]},c:"toLocaleString",C:function(d){return Date.ext.util.xPad(parseInt(d.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(d){return Date.ext.util.xPad(parseInt(Date.ext.util.G(d)/100,10),0)},G:function(d){var y=d.getFullYear();var V=parseInt(Date.ext.formats.V(d),10);var W=parseInt(Date.ext.formats.W(d),10);if(W>V){y++}else{if(W===0&&V>=52){y--}}return y},H:["getHours","0"],I:function(d){var I=d.getHours()%12;return Date.ext.util.xPad(I===0?12:I,0)},j:function(d){var ms=d-new Date(""+d.getFullYear()+"/1/1 GMT");ms+=d.getTimezoneOffset()*60000;var doy=parseInt(ms/60000/60/24,10)+1;return Date.ext.util.xPad(doy,0,100)},m:function(d){return Date.ext.util.xPad(d.getMonth()+1,0)},M:["getMinutes","0"],p:function(d){return Date.ext.locales[d.locale].p[d.getHours()>=12?1:0]},P:function(d){return Date.ext.locales[d.locale].P[d.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(d){var dow=d.getDay();return dow===0?7:dow},U:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=6-d.getDay();var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0)},V:function(d){var woy=parseInt(Date.ext.formats.W(d),10);var dow1_1=(new Date(""+d.getFullYear()+"/1/1")).getDay();var idow=woy+(dow1_1>4||dow1_1<=1?0:1);if(idow==53&&(new Date(""+d.getFullYear()+"/12/31")).getDay()<4){idow=1}else{if(idow===0){idow=Date.ext.formats.V(new Date(""+(d.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(idow,0)},w:"getDay",W:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=7-Date.ext.formats.u(d);var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0,10)},y:function(d){return Date.ext.util.xPad(d.getFullYear()%100,0)},Y:"getFullYear",z:function(d){var o=d.getTimezoneOffset();var H=Date.ext.util.xPad(parseInt(Math.abs(o/60),10),0);var M=Date.ext.util.xPad(o%60,0);return(o>0?"-":"+")+H+M},Z:function(d){return d.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(d){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(fmt){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var d=this;while(fmt.match(/%[cDhnrRtTxXzZ]/)){fmt=fmt.replace(/%([cDhnrRtTxXzZ])/g,function(m0,m1){var f=Date.ext.aggregates[m1];return(f=="locale"?Date.ext.locales[d.locale][m1]:f)})}var str=fmt.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(m0,m1){var f=Date.ext.formats[m1];if(typeof (f)=="string"){return d[f]()}else{if(typeof (f)=="function"){return f.call(d,d)}else{if(typeof (f)=="object"&&typeof (f[0])=="string"){return Date.ext.util.xPad(d[f[0]](),f[1])}else{return m1}}}});d=null;return str}; diff --git a/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/strftime.js b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/strftime.js new file mode 100644 index 00000000..d622df9e --- /dev/null +++ b/usecaseui-common/src/main/webapp/static/fusion/raptor/dy3/js/strftime/strftime.js @@ -0,0 +1,731 @@ +/* + strftime for Javascript + Copyright (c) 2008, Philip S Tellis <philip@bluesmoon.info> + All rights reserved. + + This code is distributed under the terms of the BSD licence + + Redistribution and use of this software in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + * The names of the contributors to this file may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file strftime.js + * \author Philip S Tellis \<philip@bluesmoon.info\> + * \version 1.3 + * \date 2008/06 + * \brief Javascript implementation of strftime + * + * Implements strftime for the Date object in javascript based on the PHP implementation described at + * http://www.php.net/strftime This is in turn based on the Open Group specification defined + * at http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html This implementation does not + * include modified conversion specifiers (i.e., Ex and Ox) + * + * The following format specifiers are supported: + * + * \copydoc formats + * + * \%a, \%A, \%b and \%B should be localised for non-English locales. + * + * \par Usage: + * This library may be used as follows: + * \code + * var d = new Date(); + * + * var ymd = d.strftime('%Y/%m/%d'); + * var iso = d.strftime('%Y-%m-%dT%H:%M:%S%z'); + * + * \endcode + * + * \sa \link Date.prototype.strftime Date.strftime \endlink for a description of each of the supported format specifiers + * \sa Date.ext.locales for localisation information + * \sa http://www.php.net/strftime for the PHP implementation which is the basis for this + * \sa http://tech.bluesmoon.info/2008/04/strftime-in-javascript.html for feedback + */ + +//! Date extension object - all supporting objects go in here. +Date.ext = {}; + +//! Utility methods +Date.ext.util = {}; + +/** +\brief Left pad a number with something +\details Takes a number and pads it to the left with the passed in pad character +\param x The number to pad +\param pad The string to pad with +\param r [optional] Upper limit for pad. A value of 10 pads to 2 digits, a value of 100 pads to 3 digits. + Default is 10. + +\return The number left padded with the pad character. This function returns a string and not a number. +*/ +Date.ext.util.xPad=function(x, pad, r) +{ + if(typeof(r) == 'undefined') + { + r=10; + } + for( ; parseInt(x, 10)<r && r>1; r/=10) + x = pad.toString() + x; + return x.toString(); +}; + +/** +\brief Currently selected locale. +\details +The locale for a specific date object may be changed using \code Date.locale = "new-locale"; \endcode +The default will be based on the lang attribute of the HTML tag of your document +*/ +Date.prototype.locale = 'en-GB'; +//! \cond FALSE +if(document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang) +{ + Date.prototype.locale = document.getElementsByTagName('html')[0].lang; +} +//! \endcond + +/** +\brief Localised strings for days of the week and months of the year. +\details +To create your own local strings, add a locale object to the locales object. +The key of your object should be the same as your locale name. For example: + en-US, + fr, + fr-CH, + de-DE +Names are case sensitive and are described at http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +Your locale object must contain the following keys: +\param a Short names of days of week starting with Sunday +\param A Long names days of week starting with Sunday +\param b Short names of months of the year starting with January +\param B Long names of months of the year starting with February +\param c The preferred date and time representation in your locale +\param p AM or PM in your locale +\param P am or pm in your locale +\param x The preferred date representation for the current locale without the time. +\param X The preferred time representation for the current locale without the date. + +\sa Date.ext.locales.en for a sample implementation +\sa \ref localisation for detailed documentation on localising strftime for your own locale +*/ +Date.ext.locales = { }; + +/** + * \brief Localised strings for English (British). + * \details + * This will be used for any of the English dialects unless overridden by a country specific one. + * This is the default locale if none specified + */ +Date.ext.locales.en = { + a: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + A: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + b: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + B: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + c: '%a %d %b %Y %T %Z', + p: ['AM', 'PM'], + P: ['am', 'pm'], + x: '%d/%m/%y', + X: '%T' +}; + +//! \cond FALSE +// Localised strings for US English +Date.ext.locales['en-US'] = Date.ext.locales.en; +Date.ext.locales['en-US'].c = '%a %d %b %Y %r %Z'; +Date.ext.locales['en-US'].x = '%D'; +Date.ext.locales['en-US'].X = '%r'; + +// Localised strings for British English +Date.ext.locales['en-GB'] = Date.ext.locales.en; + +// Localised strings for Australian English +Date.ext.locales['en-AU'] = Date.ext.locales['en-GB']; +//! \endcond + +//! \brief List of supported format specifiers. +/** + * \details + * \arg \%a - abbreviated weekday name according to the current locale + * \arg \%A - full weekday name according to the current locale + * \arg \%b - abbreviated month name according to the current locale + * \arg \%B - full month name according to the current locale + * \arg \%c - preferred date and time representation for the current locale + * \arg \%C - century number (the year divided by 100 and truncated to an integer, range 00 to 99) + * \arg \%d - day of the month as a decimal number (range 01 to 31) + * \arg \%D - same as %m/%d/%y + * \arg \%e - day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31') + * \arg \%g - like %G, but without the century + * \arg \%G - The 4-digit year corresponding to the ISO week number + * \arg \%h - same as %b + * \arg \%H - hour as a decimal number using a 24-hour clock (range 00 to 23) + * \arg \%I - hour as a decimal number using a 12-hour clock (range 01 to 12) + * \arg \%j - day of the year as a decimal number (range 001 to 366) + * \arg \%m - month as a decimal number (range 01 to 12) + * \arg \%M - minute as a decimal number + * \arg \%n - newline character + * \arg \%p - either `AM' or `PM' according to the given time value, or the corresponding strings for the current locale + * \arg \%P - like %p, but lower case + * \arg \%r - time in a.m. and p.m. notation equal to %I:%M:%S %p + * \arg \%R - time in 24 hour notation equal to %H:%M + * \arg \%S - second as a decimal number + * \arg \%t - tab character + * \arg \%T - current time, equal to %H:%M:%S + * \arg \%u - weekday as a decimal number [1,7], with 1 representing Monday + * \arg \%U - week number of the current year as a decimal number, starting with + * the first Sunday as the first day of the first week + * \arg \%V - The ISO 8601:1988 week number of the current year as a decimal number, + * range 01 to 53, where week 1 is the first week that has at least 4 days + * in the current year, and with Monday as the first day of the week. + * \arg \%w - day of the week as a decimal, Sunday being 0 + * \arg \%W - week number of the current year as a decimal number, starting with the + * first Monday as the first day of the first week + * \arg \%x - preferred date representation for the current locale without the time + * \arg \%X - preferred time representation for the current locale without the date + * \arg \%y - year as a decimal number without a century (range 00 to 99) + * \arg \%Y - year as a decimal number including the century + * \arg \%z - numerical time zone representation + * \arg \%Z - time zone name or abbreviation + * \arg \%% - a literal `\%' character + */ +Date.ext.formats = { + a: function(d) { return Date.ext.locales[d.locale].a[d.getDay()]; }, + A: function(d) { return Date.ext.locales[d.locale].A[d.getDay()]; }, + b: function(d) { return Date.ext.locales[d.locale].b[d.getMonth()]; }, + B: function(d) { return Date.ext.locales[d.locale].B[d.getMonth()]; }, + c: 'toLocaleString', + C: function(d) { return Date.ext.util.xPad(parseInt(d.getFullYear()/100, 10), 0); }, + d: ['getDate', '0'], + e: ['getDate', ' '], + g: function(d) { return Date.ext.util.xPad(parseInt(Date.ext.util.G(d)/100, 10), 0); }, + G: function(d) { + var y = d.getFullYear(); + var V = parseInt(Date.ext.formats.V(d), 10); + var W = parseInt(Date.ext.formats.W(d), 10); + + if(W > V) { + y++; + } else if(W===0 && V>=52) { + y--; + } + + return y; + }, + H: ['getHours', '0'], + I: function(d) { var I=d.getHours()%12; return Date.ext.util.xPad(I===0?12:I, 0); }, + j: function(d) { + var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT'); + ms += d.getTimezoneOffset()*60000; + var doy = parseInt(ms/60000/60/24, 10)+1; + return Date.ext.util.xPad(doy, 0, 100); + }, + m: function(d) { return Date.ext.util.xPad(d.getMonth()+1, 0); }, + M: ['getMinutes', '0'], + p: function(d) { return Date.ext.locales[d.locale].p[d.getHours() >= 12 ? 1 : 0 ]; }, + P: function(d) { return Date.ext.locales[d.locale].P[d.getHours() >= 12 ? 1 : 0 ]; }, + S: ['getSeconds', '0'], + u: function(d) { var dow = d.getDay(); return dow===0?7:dow; }, + U: function(d) { + var doy = parseInt(Date.ext.formats.j(d), 10); + var rdow = 6-d.getDay(); + var woy = parseInt((doy+rdow)/7, 10); + return Date.ext.util.xPad(woy, 0); + }, + V: function(d) { + var woy = parseInt(Date.ext.formats.W(d), 10); + var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay(); + // First week is 01 and not 00 as in the case of %U and %W, + // so we add 1 to the final result except if day 1 of the year + // is a Monday (then %W returns 01). + // We also need to subtract 1 if the day 1 of the year is + // Friday-Sunday, so the resulting equation becomes: + var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1); + if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4) + { + idow = 1; + } + else if(idow === 0) + { + idow = Date.ext.formats.V(new Date('' + (d.getFullYear()-1) + '/12/31')); + } + + return Date.ext.util.xPad(idow, 0); + }, + w: 'getDay', + W: function(d) { + var doy = parseInt(Date.ext.formats.j(d), 10); + var rdow = 7-Date.ext.formats.u(d); + var woy = parseInt((doy+rdow)/7, 10); + return Date.ext.util.xPad(woy, 0, 10); + }, + y: function(d) { return Date.ext.util.xPad(d.getFullYear()%100, 0); }, + Y: 'getFullYear', + z: function(d) { + var o = d.getTimezoneOffset(); + var H = Date.ext.util.xPad(parseInt(Math.abs(o/60), 10), 0); + var M = Date.ext.util.xPad(o%60, 0); + return (o>0?'-':'+') + H + M; + }, + Z: function(d) { return d.toString().replace(/^.*\(([^)]+)\)$/, '$1'); }, + '%': function(d) { return '%'; } +}; + +/** +\brief List of aggregate format specifiers. +\details +Aggregate format specifiers map to a combination of basic format specifiers. +These are implemented in terms of Date.ext.formats. + +A format specifier that maps to 'locale' is read from Date.ext.locales[current-locale]. + +\sa Date.ext.formats +*/ +Date.ext.aggregates = { + c: 'locale', + D: '%m/%d/%y', + h: '%b', + n: '\n', + r: '%I:%M:%S %p', + R: '%H:%M', + t: '\t', + T: '%H:%M:%S', + x: 'locale', + X: 'locale' +}; + +//! \cond FALSE +// Cache timezone values because they will never change for a given JS instance +Date.ext.aggregates.z = Date.ext.formats.z(new Date()); +Date.ext.aggregates.Z = Date.ext.formats.Z(new Date()); +//! \endcond + +//! List of unsupported format specifiers. +/** + * \details + * All format specifiers supported by the PHP implementation are supported by + * this javascript implementation. + */ +Date.ext.unsupported = { }; + + +/** + * \brief Formats the date according to the specified format. + * \param fmt The format to format the date in. This may be a combination of the following: + * \copydoc formats + * + * \return A string representation of the date formatted based on the passed in parameter + * \sa http://www.php.net/strftime for documentation on format specifiers +*/ +Date.prototype.strftime=function(fmt) +{ + // Fix locale if declared locale hasn't been defined + // After the first call this condition should never be entered unless someone changes the locale + if(!(this.locale in Date.ext.locales)) + { + if(this.locale.replace(/-[a-zA-Z]+$/, '') in Date.ext.locales) + { + this.locale = this.locale.replace(/-[a-zA-Z]+$/, ''); + } + else + { + this.locale = 'en-GB'; + } + } + + var d = this; + // First replace aggregates + while(fmt.match(/%[cDhnrRtTxXzZ]/)) + { + fmt = fmt.replace(/%([cDhnrRtTxXzZ])/g, function(m0, m1) + { + var f = Date.ext.aggregates[m1]; + return (f == 'locale' ? Date.ext.locales[d.locale][m1] : f); + }); + } + + + // Now replace formats - we need a closure so that the date object gets passed through + var str = fmt.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g, function(m0, m1) + { + var f = Date.ext.formats[m1]; + if(typeof(f) == 'string') { + return d[f](); + } else if(typeof(f) == 'function') { + return f.call(d, d); + } else if(typeof(f) == 'object' && typeof(f[0]) == 'string') { + return Date.ext.util.xPad(d[f[0]](), f[1]); + } else { + return m1; + } + }); + d=null; + return str; +}; + +/** + * \mainpage strftime for Javascript + * + * \section toc Table of Contents + * - \ref intro_sec + * - <a class="el" href="strftime.js">Download full source</a> / <a class="el" href="strftime-min.js">minified</a> + * - \subpage usage + * - \subpage format_specifiers + * - \subpage localisation + * - \link strftime.js API Documentation \endlink + * - \subpage demo + * - \subpage changelog + * - \subpage faq + * - <a class="el" href="http://tech.bluesmoon.info/2008/04/strftime-in-javascript.html">Feedback</a> + * - \subpage copyright_licence + * + * \section intro_sec Introduction + * + * C and PHP developers have had access to a built in strftime function for a long time. + * This function is an easy way to format dates and times for various display needs. + * + * This library brings the flexibility of strftime to the javascript Date object + * + * Use this library if you frequently need to format dates in javascript in a variety of ways. For example, + * if you have PHP code that writes out formatted dates, and want to mimic the functionality using + * progressively enhanced javascript, then this library can do exactly what you want. + * + * + * + * + * \page usage Example usage + * + * \section usage_sec Usage + * This library may be used as follows: + * \code + * var d = new Date(); + * + * var ymd = d.strftime('%Y/%m/%d'); + * var iso = d.strftime('%Y-%m-%dT%H:%M:%S%z'); + * + * \endcode + * + * \subsection examples Examples + * + * To get the current time in hours and minutes: + * \code + * var d = new Date(); + * d.strftime("%H:%M"); + * \endcode + * + * To get the current time with seconds in AM/PM notation: + * \code + * var d = new Date(); + * d.strftime("%r"); + * \endcode + * + * To get the year and day of the year for August 23, 2009: + * \code + * var d = new Date('2009/8/23'); + * d.strftime("%Y-%j"); + * \endcode + * + * \section demo_sec Demo + * + * Try your own examples on the \subpage demo page. You can use any of the supported + * \subpage format_specifiers. + * + * + * + * + * \page localisation Localisation + * You can localise strftime by implementing the short and long forms for days of the + * week and months of the year, and the localised aggregates for the preferred date + * and time representation for your locale. You need to add your locale to the + * Date.ext.locales object. + * + * \section localising_fr Localising for french + * + * For example, this is how we'd add French language strings to the locales object: + * \dontinclude index.html + * \skip Generic french + * \until }; + * The % format specifiers are all defined in \ref formats. You can use any of those. + * + * This locale definition may be included in your own source file, or in the HTML file + * including \c strftime.js, however it must be defined \em after including \c strftime.js + * + * The above definition includes generic french strings and formats that are used in France. + * Other french speaking countries may have other representations for dates and times, so we + * need to override this for them. For example, Canadian french uses a Y-m-d date format, + * while French french uses d.m.Y. We fix this by defining Canadian french to be the same + * as generic french, and then override the format specifiers for \c x for the \c fr-CA locale: + * \until End french + * + * You can now use any of the French locales at any time by setting \link Date.prototype.locale Date.locale \endlink + * to \c "fr", \c "fr-FR", \c "fr-CA", or any other french dialect: + * \code + * var d = new Date("2008/04/22"); + * d.locale = "fr"; + * + * d.strftime("%A, %d %B == %x"); + * \endcode + * will return: + * \code + * mardi, 22 avril == 22.04.2008 + * \endcode + * While changing the locale to "fr-CA": + * \code + * d.locale = "fr-CA"; + * + * d.strftime("%A, %d %B == %x"); + * \endcode + * will return: + * \code + * mardi, 22 avril == 2008-04-22 + * \endcode + * + * You can use any of the format specifiers defined at \ref formats + * + * The locale for all dates defaults to the value of the \c lang attribute of your HTML document if + * it is set, or to \c "en" otherwise. + * \note + * Your locale definitions \b MUST be added to the locale object before calling + * \link Date.prototype.strftime Date.strftime \endlink. + * + * \sa \ref formats for a list of format specifiers that can be used in your definitions + * for c, x and X. + * + * \section locale_names Locale names + * + * Locale names are defined in RFC 1766. Typically, a locale would be a two letter ISO639 + * defined language code and an optional ISO3166 defined country code separated by a - + * + * eg: fr-FR, de-DE, hi-IN + * + * \sa http://www.ietf.org/rfc/rfc1766.txt + * \sa http://www.loc.gov/standards/iso639-2/php/code_list.php + * \sa http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm + * + * \section locale_fallback Locale fallbacks + * + * If a locale object corresponding to the fully specified locale isn't found, an attempt will be made + * to fall back to the two letter language code. If a locale object corresponding to that isn't found + * either, then the locale will fall back to \c "en". No warning will be issued. + * + * For example, if we define a locale for de: + * \until }; + * Then set the locale to \c "de-DE": + * \code + * d.locale = "de-DE"; + * + * d.strftime("%a, %d %b"); + * \endcode + * In this case, the \c "de" locale will be used since \c "de-DE" has not been defined: + * \code + * Di, 22 Apr + * \endcode + * + * Swiss german will return the same since it will also fall back to \c "de": + * \code + * d.locale = "de-CH"; + * + * d.strftime("%a, %d %b"); + * \endcode + * \code + * Di, 22 Apr + * \endcode + * + * We need to override the \c a specifier for Swiss german, since it's different from German german: + * \until End german + * We now get the correct results: + * \code + * d.locale = "de-CH"; + * + * d.strftime("%a, %d %b"); + * \endcode + * \code + * Die, 22 Apr + * \endcode + * + * \section builtin_locales Built in locales + * + * This library comes with pre-defined locales for en, en-GB, en-US and en-AU. + * + * + * + * + * \page format_specifiers Format specifiers + * + * \section specifiers Format specifiers + * strftime has several format specifiers defined by the Open group at + * http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html + * + * PHP added a few of its own, defined at http://www.php.net/strftime + * + * This javascript implementation supports all the PHP specifiers + * + * \subsection supp Supported format specifiers: + * \copydoc formats + * + * \subsection unsupportedformats Unsupported format specifiers: + * \copydoc unsupported + * + * + * + * + * \page demo strftime demo + * <div style="float:right;width:45%;"> + * \copydoc formats + * </div> + * \htmlinclude index.html + * + * + * + * + * \page faq FAQ + * + * \section how_tos Usage + * + * \subsection howtouse Is there a manual on how to use this library? + * + * Yes, see \ref usage + * + * \subsection wheretoget Where can I get a minified version of this library? + * + * The minified version is available <a href="strftime-min.js" title="Minified strftime.js">here</a>. + * + * \subsection which_specifiers Which format specifiers are supported? + * + * See \ref format_specifiers + * + * \section whys Why? + * + * \subsection why_lib Why this library? + * + * I've used the strftime function in C, PHP and the Unix shell, and found it very useful + * to do date formatting. When I needed to do date formatting in javascript, I decided + * that it made the most sense to just reuse what I'm already familiar with. + * + * \subsection why_another Why another strftime implementation for Javascript? + * + * Yes, there are other strftime implementations for Javascript, but I saw problems with + * all of them that meant I couldn't use them directly. Some implementations had bad + * designs. For example, iterating through all possible specifiers and scanning the string + * for them. Others were tied to specific libraries like prototype. + * + * Trying to extend any of the existing implementations would have required only slightly + * less effort than writing this from scratch. In the end it took me just about 3 hours + * to write the code and about 6 hours battling with doxygen to write these docs. + * + * I also had an idea of how I wanted to implement this, so decided to try it. + * + * \subsection why_extend_date Why extend the Date class rather than subclass it? + * + * I tried subclassing Date and failed. I didn't want to waste time on figuring + * out if there was a problem in my code or if it just wasn't possible. Adding to the + * Date.prototype worked well, so I stuck with it. + * + * I did have some worries because of the way for..in loops got messed up after json.js added + * to the Object.prototype, but that isn't an issue here since {} is not a subclass of Date. + * + * My last doubt was about the Date.ext namespace that I created. I still don't like this, + * but I felt that \c ext at least makes clear that this is external or an extension. + * + * It's quite possible that some future version of javascript will add an \c ext or a \c locale + * or a \c strftime property/method to the Date class, but this library should probably + * check for capabilities before doing what it does. + * + * \section curiosity Curiosity + * + * \subsection how_big How big is the code? + * + * \arg 26K bytes with documentation + * \arg 4242 bytes minified using <a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a> + * \arg 1477 bytes minified and gzipped + * + * \subsection how_long How long did it take to write this? + * + * 15 minutes for the idea while I was composing this blog post: + * http://tech.bluesmoon.info/2008/04/javascript-date-functions.html + * + * 3 hours in one evening to write v1.0 of the code and 6 hours the same + * night to write the docs and this manual. As you can tell, I'm fairly + * sleepy. + * + * Versions 1.1 and 1.2 were done in a couple of hours each, and version 1.3 + * in under one hour. + * + * \section contributing Contributing + * + * \subsection how_to_rfe How can I request features or make suggestions? + * + * You can leave a comment on my blog post about this library here: + * http://tech.bluesmoon.info/2008/04/strftime-in-javascript.html + * + * \subsection how_to_contribute Can I/How can I contribute code to this library? + * + * Yes, that would be very nice, thank you. You can do various things. You can make changes + * to the library, and make a diff against the current file and mail me that diff at + * philip@bluesmoon.info, or you could just host the new file on your own servers and add + * your name to the copyright list at the top stating which parts you've added. + * + * If you do mail me a diff, let me know how you'd like to be listed in the copyright section. + * + * \subsection copyright_signover Who owns the copyright on contributed code? + * + * The contributor retains copyright on contributed code. + * + * In some cases I may use contributed code as a template and write the code myself. In this + * case I'll give the contributor credit for the idea, but will not add their name to the + * copyright holders list. + * + * + * + * + * \page copyright_licence Copyright & Licence + * + * \section copyright Copyright + * \dontinclude strftime.js + * \skip Copyright + * \until rights + * + * \section licence Licence + * \skip This code + * \until SUCH DAMAGE. + * + * + * + * \page changelog ChangeLog + * + * \par 1.3 - 2008/06/17: + * - Fixed padding issue with negative timezone offsets in %r + * reported and fixed by Mikko <mikko.heimola@iki.fi> + * - Added support for %P + * - Internationalised %r, %p and %P + * + * \par 1.2 - 2008/04/27: + * - Fixed support for c (previously it just returned toLocaleString()) + * - Add support for c, x and X + * - Add locales for en-GB, en-US and en-AU + * - Make en-GB the default locale (previous was en) + * - Added more localisation docs + * + * \par 1.1 - 2008/04/27: + * - Fix bug in xPad which wasn't padding more than a single digit + * - Fix bug in j which had an off by one error for days after March 10th because of daylight savings + * - Add support for g, G, U, V and W + * + * \par 1.0 - 2008/04/22: + * - Initial release with support for a, A, b, B, c, C, d, D, e, H, I, j, m, M, p, r, R, S, t, T, u, w, y, Y, z, Z, and % + */ |