+ */
+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();
+ }
+ });
+ })();
+};
+
+// 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,
+ '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,
+ 'panEdgeFraction': true,
+ 'pixelsPerYLabel': true,
+ 'pointClickCallback': true,
+ 'pointSize': true,
+ 'rangeSelectorPlotFillColor': true,
+ 'rangeSelectorPlotStrokeColor': true,
+ 'showLabelsOnHighlight': true,
+ 'showRoller': true,
+ 'strokeWidth': true,
+ 'underlayCallback': true,
+ 'unhighlightCallback': true,
+ 'zoomCallback': true
+};
+
+/**