+
+/**
+ * @private
+ * Checks whether the user is on an Android browser.
+ * Android does not fully support the <canvas> tag, e.g. w/r/t/ clipping.
+ */
+Dygraph.isAndroid = function() {
+ return (/Android/).test(navigator.userAgent);
+};
+
+/**
+ * @private
+ * 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 repeat_fn {Function} Called repeatedly -- takes the number of calls
+ * (from 0 to times-1) as an argument.
+ * @param times {number} The number of times to call repeat_fn
+ * @param every_ms {number} Milliseconds between calls
+ * @param cleanup_fn {Function} A function to call after all repeat_fn 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();
+ return;
+ }
+
+ (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();
+ } else {
+ loop();
+ }
+ }, target_time - new Date().getTime());
+ // TODO(danvk): adjust every_ms to produce evenly-timed function calls.
+ })();
+};
+
+/**
+ * @private
+ * This function will scan the option list and determine if they
+ * require us to recalculate the pixel positions of each point.
+ * @param { List } a list of options to check.
+ * @return { Boolean } true if the graph needs new points else false.
+ */
+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,
+ 'drawPoints': 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;
+};