}
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) {
+ 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;
}
}
* 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 });
};
* 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 {
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;
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;
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.
+ });
})();
};
delta = delta || Math.PI * 2 / sides;
ctx.beginPath();
- var first = true;
var initialAngle = rotationRadians;
var angle = initialAngle;
return null;
};
+
+/**
+ * Is one element contained by another?
+ * @param {Element} containee The contained element.
+ * @param {Element} container The container element.
+ * @return {boolean} Whether containee is inside (or equal to) container.
+ * @private
+ */
+Dygraph.isElementContainedBy = function(containee, container) {
+ if (container === null || containee === null) {
+ return false;
+ }
+ while (containee && containee !== container) {
+ containee = containee.parentNode;
+ }
+ return (containee === 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);
+ }
+ }
+};