X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;ds=inline;f=dygraph-utils.js;h=63fc1e0a433a36d0a6845647d9282c9e76c14aee;hb=99386b99835dfcc75053b4a9a4304b3d8a8546ad;hp=82d9189fad1f375bb13a6bea567fbf6eebd58138;hpb=0d4989dbb907d4c5b5624d3a3a3cd21d7edeb8a9;p=dygraphs.git
diff --git a/dygraph-utils.js b/dygraph-utils.js
index 82d9189..63fc1e0 100644
--- a/dygraph-utils.js
+++ b/dygraph-utils.js
@@ -11,15 +11,21 @@
* 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 */
+/**
+ * @private
+ * @param {number} x
+ * @return {number}
+ */
Dygraph.log10 = function(x) {
return Math.log(x) / Dygraph.LN_TEN;
-}
+};
// Various logging levels.
Dygraph.DEBUG = 1;
@@ -27,82 +33,110 @@ 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];
/**
- * @private
* Log an error on the JS console at the given severity.
- * @param { Integer } severity One of Dygraph.{DEBUG,INFO,WARNING,ERROR}
- * @param { String } The message to log.
+ * @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') {
- // Remove uninteresting bits: logging functions and paths.
- var st = printStackTrace({guess:false});
- while (st[0].indexOf("stacktrace") != -1) {
- st.splice(0, 1);
- }
+ 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].', '');
+ 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!
}
- var top_msg = st.splice(0, 1)[0];
- message += ' (' + top_msg.replace(/^.*@ ?/, '') + ')';
}
+ //
+
+ 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);
+ }
+ };
- if (typeof(console) != 'undefined') {
switch (severity) {
case Dygraph.DEBUG:
- console.debug('dygraphs: ' + message);
+ log(console, console.debug, 'dygraphs: ' + message);
break;
case Dygraph.INFO:
- console.info('dygraphs: ' + message);
+ log(console, console.info, 'dygraphs: ' + message);
break;
case Dygraph.WARNING:
- console.warn('dygraphs: ' + message);
+ log(console, console.warn, 'dygraphs: ' + message);
break;
case Dygraph.ERROR:
- console.error('dygraphs: ' + message);
+ log(console, console.error, 'dygraphs: ' + message);
break;
}
}
+ //
if (Dygraph.LOG_STACK_TRACES) {
- console.log(st.join('\n'));
+ window.console.log(st.join('\n'));
}
+ //
};
-/** @private */
+/**
+ * @param {string} message
+ * @private
+ */
Dygraph.info = function(message) {
Dygraph.log(Dygraph.INFO, message);
};
-/** @private */
-Dygraph.prototype.info = Dygraph.info;
-/** @private */
+/**
+ * @param {string} message
+ * @private
+ */
Dygraph.warn = function(message) {
Dygraph.log(Dygraph.WARNING, message);
};
-/** @private */
-Dygraph.prototype.warn = Dygraph.warn;
-/** @private */
+/**
+ * @param {string} message
+ */
Dygraph.error = function(message) {
Dygraph.log(Dygraph.ERROR, message);
};
-/** @private */
-Dygraph.prototype.error = Dygraph.error;
/**
- * @private
* Return the 2d context for a dygraph canvas.
*
* This method is only exposed for the sake of replacing the function in
@@ -113,19 +147,22 @@ Dygraph.prototype.error = Dygraph.error;
* var realContext = oldFunc(canvas);
* return new Proxy(realContext);
* };
+ * @param {!HTMLCanvasElement} canvas
+ * @return {!CanvasRenderingContext2D}
+ * @private
*/
Dygraph.getContext = function(canvas) {
- return canvas.getContext("2d");
+ return /** @type{!CanvasRenderingContext2D}*/(canvas.getContext("2d"));
};
/**
- * @private
* Add an event handler. This smooths a difference between IE and the rest of
* the world.
- * @param { DOM element } elem The element to add the event to.
- * @param { String } type The type of the event, e.g. 'click' or 'mousemove'.
- * @param { Function } fn The function to call on the event. The function takes
- * one parameter: the event object.
+ * @param {!Node} 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) {
@@ -137,30 +174,61 @@ Dygraph.addEvent = function addEvent(elem, type, fn) {
};
/**
+ * Add an event handler. This event handler is kept until the graph is
+ * destroyed with a call to graph.destroy().
+ *
+ * @param {!Node} 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 {!Node} elem The element to remove the event from.
+ * @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
- * Remove an event handler. This smooths a difference between IE and the rest of
- * the world.
- * @param { DOM element } elem The element to add the event to.
- * @param { String } type The type of the event, e.g. 'click' or 'mousemove'.
- * @param { Function } fn The function to call on the event. The function takes
- * one parameter: the event object.
*/
-Dygraph.removeEvent = function addEvent(elem, type, fn) {
+Dygraph.removeEvent = function(elem, type, fn) {
if (elem.removeEventListener) {
elem.removeEventListener(type, fn, false);
} else {
- elem.detachEvent('on'+type, elem[type+fn]);
+ 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_ = [];
+};
+
/**
- * @private
* 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.
+ * @param {!Event} e The event whose normal behavior should be canceled.
+ * @private
*/
Dygraph.cancelEvent = function(e) {
e = e ? e : window.event;
@@ -180,10 +248,10 @@ Dygraph.cancelEvent = function(e) {
* 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.
+ * @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) {
@@ -222,70 +290,62 @@ Dygraph.hsvToRGB = function (hue, saturation, value) {
// ... and modifications to support scrolling divs.
/**
- * Find the x-coordinate of the supplied object relative to the left side
- * of the page.
+ * Find the coordinates of an object relative to the top left of the page.
+ *
+ * TODO(danvk): change obj type from Node -> !Node
+ * @param {Node} obj
+ * @return {{x:number,y:number}}
* @private
*/
-Dygraph.findPosX = function(obj) {
- var curleft = 0;
- if(obj.offsetParent) {
+Dygraph.findPos = function(obj) {
+ var curleft = 0, curtop = 0;
+ if (obj.offsetParent) {
var copyObj = obj;
- while(1) {
- curleft += copyObj.offsetLeft;
- if(!copyObj.offsetParent) {
- break;
+ while (1) {
+ // NOTE: the if statement here is for IE8.
+ var borderLeft = "0", borderTop = "0";
+ if (window.getComputedStyle) {
+ var computedStyle = window.getComputedStyle(copyObj, null);
+ borderLeft = computedStyle.borderLeft || "0";
+ borderTop = computedStyle.borderTop || "0";
}
- 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.
- * @private
- */
-Dygraph.findPosY = function(obj) {
- var curtop = 0;
- if(obj.offsetParent) {
- var copyObj = obj;
- while(1) {
+ curleft += parseInt(borderLeft, 10) ;
+ curtop += parseInt(borderTop, 10) ;
+ curleft += copyObj.offsetLeft;
curtop += copyObj.offsetTop;
- if(!copyObj.offsetParent) {
+ if (!copyObj.offsetParent) {
break;
}
copyObj = copyObj.offsetParent;
}
- } else if(obj.y) {
- curtop += obj.y;
+ } else {
+ // TODO(danvk): why would obj ever have these properties?
+ if (obj.x) curleft += obj.x;
+ if (obj.y) curtop += obj.y;
}
+
// This handles the case where the object is inside a scrolled div.
- while(obj && obj != document.body) {
+ while (obj && obj != document.body) {
+ curleft -= obj.scrollLeft;
curtop -= obj.scrollTop;
obj = obj.parentNode;
}
- return curtop;
+ return {x: curleft, y: curtop};
};
/**
- * @private
* 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;
+ var de = document.documentElement;
var b = document.body;
return e.clientX +
(de.scrollLeft || b.scrollLeft) -
@@ -294,16 +354,18 @@ Dygraph.pageX = function(e) {
};
/**
- * @private
* 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;
+ var de = document.documentElement;
var b = document.body;
return e.clientY +
(de.scrollTop || b.scrollTop) -
@@ -312,13 +374,53 @@ Dygraph.pageY = function(e) {
};
/**
+ * Converts page the x-coordinate of the event to pixel x-coordinates on the
+ * canvas (i.e. DOM Coords).
+ * @param {!Event} e Drag event.
+ * @param {!DygraphInteractionContext} context Interaction context object.
+ * @return {number} The amount by which the drag has moved to the right.
+ */
+Dygraph.dragGetX_ = function(e, context) {
+ return Dygraph.pageX(e) - context.px;
+};
+
+/**
+ * Converts page the y-coordinate of the event to pixel y-coordinates on the
+ * canvas (i.e. DOM Coords).
+ * @param {!Event} e Drag event.
+ * @param {!DygraphInteractionContext} context Interaction context object.
+ * @return {number} The amount by which the drag has moved down.
+ */
+Dygraph.dragGetY_ = function(e, context) {
+ return Dygraph.pageY(e) - context.py;
+};
+
+/**
+ * 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
- * @param { Number } x The number to consider.
- * @return { Boolean } Whether the number is zero or NaN.
*/
-// TODO(danvk): rename this function to something like 'isNonZeroNan'.
Dygraph.isOK = function(x) {
- return x && !isNaN(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=} opt_allowNaNY Treat point with y=NaN as valid
+ * @return {boolean} Whether the point has numeric x and y.
+ * @private
+ */
+Dygraph.isValidPoint = function(p, opt_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) || (!opt_allowNaNY && isNaN(p.y))) return false;
+ return true;
};
/**
@@ -334,9 +436,9 @@ Dygraph.isOK = function(x) {
* 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
+ * @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) {
@@ -359,13 +461,15 @@ Dygraph.floatFormat = function(x, opt_precision) {
//
// 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) ?
+ return (Math.abs(x) < 1.0e-3 && x !== 0.0) ?
x.toExponential(p - 1) : x.toPrecision(p);
};
/**
- * @private
* 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;
@@ -373,8 +477,9 @@ Dygraph.zeropad = function(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"
+ *
+ * @param {number} date The JavaScript date (ms since epoch)
+ * @return {string} A time of the form "HH:MM:SS"
* @private
*/
Dygraph.hmsString_ = function(date) {
@@ -390,10 +495,34 @@ Dygraph.hmsString_ = function(date) {
};
/**
+ * 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;
+};
+
+/**
* 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
+ * @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) {
@@ -402,69 +531,85 @@ Dygraph.round_ = function(num, places) {
};
/**
- * @private
* Implementation of binary search over an array.
* Currently does not work when val is outside the range of arry's values.
- * @param { Integer } val the value to search for
- * @param { Integer[] } arry is the value over which to search
- * @param { Integer } 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 { Integer } [low] The first index in arry to consider (optional)
- * @param { Integer } [high] The last index in arry to consider (optional)
+ * @param {number} val the value to search for
+ * @param {Array.} 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 || high == null) {
+ if (low === null || low === undefined ||
+ high === null || high === undefined) {
low = 0;
high = arry.length - 1;
}
if (low > high) {
return -1;
}
- if (abs == null) {
+ if (abs === null || abs === undefined) {
abs = 0;
}
var validIndex = function(idx) {
return idx >= 0 && idx < arry.length;
- }
- var mid = parseInt((low + high) / 2);
+ };
+ var mid = parseInt((low + high) / 2, 10);
var element = arry[mid];
+ var idx;
if (element == val) {
return mid;
- }
- if (element > val) {
+ } else if (element > val) {
if (abs > 0) {
// Accept if element > val, but also if prior element < val.
- var idx = mid - 1;
+ idx = mid - 1;
if (validIndex(idx) && arry[idx] < val) {
return mid;
}
}
return Dygraph.binarySearch(val, arry, abs, low, mid - 1);
- }
- if (element < val) {
+ } else if (element < val) {
if (abs < 0) {
// Accept if element < val, but also if prior element > val.
- var idx = mid + 1;
+ 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
};
/**
- * @private
* 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} A date in YYYYMMDD format.
- * @return {Number} Milliseconds since epoch.
+ *
+ * @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) {
@@ -473,8 +618,8 @@ Dygraph.dateParser = function(dateStr) {
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);
+ 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
@@ -489,12 +634,12 @@ Dygraph.dateParser = function(dateStr) {
};
/**
- * @private
* 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 { Integer } millis since epoch
+ * @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();
@@ -504,9 +649,11 @@ Dygraph.dateStrToMillis = function(str) {
/**
* Copies all the properties from o to self.
*
- * @private
+ * @param {!Object} self
+ * @param {!Object} o
+ * @return {!Object}
*/
-Dygraph.update = function (self, o) {
+Dygraph.update = function(self, o) {
if (typeof(o) != 'undefined' && o !== null) {
for (var k in o) {
if (o.hasOwnProperty(k)) {
@@ -520,6 +667,9 @@ Dygraph.update = function (self, o) {
/**
* Copies all the properties from o to self.
*
+ * @param {!Object} self
+ * @param {!Object} o
+ * @return {!Object}
* @private
*/
Dygraph.updateDeep = function (self, o) {
@@ -534,7 +684,7 @@ Dygraph.updateDeep = function (self, o) {
if (typeof(o) != 'undefined' && o !== null) {
for (var k in o) {
if (o.hasOwnProperty(k)) {
- if (o[k] == null) {
+ if (o[k] === null) {
self[k] = null;
} else if (Dygraph.isArrayLike(o[k])) {
self[k] = o[k].slice();
@@ -542,7 +692,7 @@ Dygraph.updateDeep = function (self, o) {
// DOM objects are shallowly-copied.
self[k] = o[k];
} else if (typeof(o[k]) == 'object') {
- if (typeof(self[k]) != 'object') {
+ if (typeof(self[k]) != 'object' || self[k] === null) {
self[k] = {};
}
Dygraph.updateDeep(self[k], o[k]);
@@ -556,9 +706,11 @@ Dygraph.updateDeep = function (self, o) {
};
/**
+ * @param {*} o
+ * @return {boolean}
* @private
*/
-Dygraph.isArrayLike = function (o) {
+Dygraph.isArrayLike = function(o) {
var typ = typeof(o);
if (
(typ != 'object' && !(typ == 'function' &&
@@ -573,6 +725,8 @@ Dygraph.isArrayLike = function (o) {
};
/**
+ * @param {Object} o
+ * @return {boolean}
* @private
*/
Dygraph.isDateLike = function (o) {
@@ -585,6 +739,8 @@ Dygraph.isDateLike = function (o) {
/**
* Note: this only seems to work for arrays.
+ * @param {!Array} o
+ * @return {!Array}
* @private
*/
Dygraph.clone = function(o) {
@@ -601,64 +757,201 @@ Dygraph.clone = function(o) {
};
/**
- * @private
* 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(canvas);
+ canvas = G_vmlCanvasManager.initElement(
+ /**@type{!HTMLCanvasElement}*/(canvas));
}
return canvas;
};
/**
+ * Returns the context's pixel ratio, which is the ratio between the device
+ * pixel ratio and the backing store ratio. Typically this is 1 for conventional
+ * displays, and > 1 for HiDPI displays (such as the Retina MBP).
+ * See http://www.html5rocks.com/en/tutorials/canvas/hidpi/ for more details.
+ *
+ * @param {!CanvasRenderingContext2D} context The canvas's 2d context.
+ * @return {number} The ratio of the device pixel ratio and the backing store
+ * ratio for the specified context.
+ */
+Dygraph.getContextPixelRatio = function(context) {
+ try {
+ var devicePixelRatio = window.devicePixelRatio || 1,
+ backingStoreRatio = context.webkitBackingStorePixelRatio ||
+ context.mozBackingStorePixelRatio ||
+ context.msBackingStorePixelRatio ||
+ context.oBackingStorePixelRatio ||
+ context.backingStorePixelRatio || 1;
+ return devicePixelRatio / backingStoreRatio;
+ } catch (e) {
+ return 1;
+ }
+};
+
+/**
+ * Checks whether the user is on an Android browser.
+ * Android does not fully support the