X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-utils.js;h=223360b319135d7745f60748d8fd7bb7d3b6643b;hb=2323945cebe74e7ca1f3897fcb5e7a5aed5fc80f;hp=2a86b245ddc7aa9b21384072149f850b6d8a48f3;hpb=f11283de83195d2aee4bbbdc9651305bcdba478e;p=dygraphs.git diff --git a/dygraph-utils.js b/dygraph-utils.js index 2a86b24..223360b 100644 --- a/dygraph-utils.js +++ b/dygraph-utils.js @@ -76,18 +76,28 @@ Dygraph.log = function(severity, message) { } 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: - window.console.debug('dygraphs: ' + message); + log(console, console.debug, 'dygraphs: ' + message); break; case Dygraph.INFO: - window.console.info('dygraphs: ' + message); + log(console, console.info, 'dygraphs: ' + message); break; case Dygraph.WARNING: - window.console.warn('dygraphs: ' + message); + log(console, console.warn, 'dygraphs: ' + message); break; case Dygraph.ERROR: - window.console.error('dygraphs: ' + message); + log(console, console.error, 'dygraphs: ' + message); break; } } @@ -125,7 +135,6 @@ Dygraph.prototype.warn = Dygraph.warn; /** * @param {string} message - * @private */ Dygraph.error = function(message) { Dygraph.log(Dygraph.ERROR, message); @@ -183,7 +192,7 @@ Dygraph.addEvent = function addEvent(elem, type, fn) { * on the event. The function takes one parameter: the event object. * @private */ -Dygraph.prototype.addEvent = function addEvent(elem, type, fn) { +Dygraph.prototype.addEvent = function(elem, type, fn) { Dygraph.addEvent(elem, type, fn); this.registeredEvents_.push({ elem : elem, type : type, fn : fn }); }; @@ -197,7 +206,7 @@ Dygraph.prototype.addEvent = function addEvent(elem, type, fn) { * on the event. The function takes one parameter: the event object. * @private */ -Dygraph.removeEvent = function addEvent(elem, type, fn) { +Dygraph.removeEvent = function(elem, type, fn) { if (elem.removeEventListener) { elem.removeEventListener(type, fn, false); } else { @@ -291,6 +300,12 @@ Dygraph.findPosX = function(obj) { 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; @@ -322,6 +337,12 @@ Dygraph.findPosY = function(obj) { 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; @@ -612,7 +633,6 @@ Dygraph.dateStrToMillis = function(str) { * @param {!Object} self * @param {!Object} o * @return {!Object} - * @private */ Dygraph.update = function(self, o) { if (typeof(o) != 'undefined' && o !== null) { @@ -752,7 +772,7 @@ Dygraph.isAndroid = function() { * @param {!Array} array * @param {number} start * @param {number} length - * @param {function(!Array,Object):boolean=} predicate + * @param {function(!Array,?):boolean=} predicate * @constructor */ Dygraph.Iterator = function(array, start, length, predicate) { @@ -796,7 +816,7 @@ Dygraph.Iterator.prototype.next = function() { }; /** - * Returns a new iterator over array, between indexes start and + * 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. @@ -805,7 +825,7 @@ Dygraph.Iterator.prototype.next = function() { * 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(Object):boolean=} opt_predicate a function that takes + * @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 @@ -814,39 +834,71 @@ 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 N times at a given interval, then call a cleanup function - * once. repeat_fn is called once immediately, then (times - 1) times - * asynchronously. If times=1, then cleanup_fn() is also called synchronously. - * @param {function(number)} repeat_fn Called repeatedly -- takes the number of - * calls (from 0 to times-1) as an argument. - * @param {number} times The number of times to call repeat_fn - * @param {number} every_ms Milliseconds between calls - * @param {function()} cleanup_fn A function to call after all repeat_fn calls. + * 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(repeat_fn, times, every_ms, cleanup_fn) { - var count = 0; - var start_time = new Date().getTime(); - repeat_fn(count); - if (times == 1) { - cleanup_fn(); +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 (count >= times) return; - var target_time = start_time + (1 + count) * every_ms; - setTimeout(function() { - count++; - repeat_fn(count); - if (count >= times - 1) { - cleanup_fn(); + 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(); } - }, target_time - new Date().getTime()); - // TODO(danvk): adjust every_ms to produce evenly-timed function calls. + }); })(); }; @@ -854,7 +906,7 @@ Dygraph.repeatAndCleanup = function(repeat_fn, times, every_ms, cleanup_fn) { * This function will scan the option list and determine if they * require us to recalculate the pixel positions of each point. * @param {!Array.} labels a list of options to check. - * @param {!Object} attrs + * @param {!Object} attrs * @return {boolean} true if the graph needs new points else false. * @private */ @@ -958,7 +1010,7 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) { /** * Compares two arrays to see if they are equal. If either parameter is not an - * array it will return false. Does a shallow compare + * array it will return false. Does a shallow compare * Dygraph.compareArrays([[1,2], [3, 4]], [[1,2], [3,4]]) === false. * @param {!Array.} array1 first array * @param {!Array.} array2 second array @@ -986,25 +1038,24 @@ Dygraph.compareArrays = function(array1, array2) { * @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 + * @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 ? rotationRadians : 0; - delta = delta ? delta : Math.PI * 2 / sides; + rotationRadians = rotationRadians || 0; + delta = delta || Math.PI * 2 / sides; ctx.beginPath(); - var first = true; 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]; + return [x, y]; }; var initialCoordinates = computeCoordinates(); @@ -1024,8 +1075,8 @@ Dygraph.regularShape_ = function( /** * TODO(danvk): be more specific on the return type. * @param {number} sides - * @param {number} rotationRadians - * @param {number} delta + * @param {number=} rotationRadians + * @param {number=} delta * @return {Function} * @private */ @@ -1037,21 +1088,6 @@ Dygraph.shapeFunction_ = function(sides, rotationRadians, delta) { }; }; -/** - * @param {number} sides the number of sides in the shape. - * @param {number} rotationRadians the shift of the initial angle, in radians. - * @param {!CanvasRenderingContext2D} ctx the canvas context - * @param {number} cx center x coordate - * @param {number} cy center y coordinate - * @param {string} color stroke color - * @param {number} radius the radius of the image. - * @param {number} delta the angle shift for each line. If missing, creates a - * regular polygon. - */ -Dygraph.DrawPolygon_ = function(sides, rotationRadians, ctx, cx, cy, color, radius, delta) { - new Dygraph.RegularShape_(sides, rotationRadians, delta).draw(ctx, cx, cy, radius); -}; - Dygraph.Circles = { DEFAULT : function(g, name, ctx, canvasx, canvasy, color, radius) { ctx.beginPath(); @@ -1127,7 +1163,7 @@ Dygraph.Circles = { * }; * window.addEventListener('mouseup', mouseUpHandler); * }; - * + * * @constructor */ Dygraph.IFrameTarp = function() { @@ -1196,3 +1232,63 @@ Dygraph.detectLineDelimiter = function(data) { 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.)} + */ +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); + } + } +};