Dygraph.WARNING = 3;
Dygraph.ERROR = 3;
+// <REMOVE_FOR_COMBINED>
// 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;
+// </REMOVE_FOR_COMBINED>
/** A dotted line stroke pattern. */
Dygraph.DOTTED_LINE = [2, 2];
* @private
*/
Dygraph.log = function(severity, message) {
+ // <REMOVE_FOR_COMBINED>
var st;
if (typeof(printStackTrace) != 'undefined') {
try {
// Oh well, it was worth a shot!
}
}
+ // </REMOVE_FOR_COMBINED>
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;
}
}
+ // <REMOVE_FOR_COMBINED>
if (Dygraph.LOG_STACK_TRACES) {
window.console.log(st.join('\n'));
}
+ // </REMOVE_FOR_COMBINED>
};
/**
Dygraph.info = function(message) {
Dygraph.log(Dygraph.INFO, message);
};
-/**
- * @param {string} message
- * @private
- */
-Dygraph.prototype.info = Dygraph.info;
/**
* @param {string} message
Dygraph.warn = function(message) {
Dygraph.log(Dygraph.WARNING, message);
};
-/**
- * @param {string} message
- * @private
- */
-Dygraph.prototype.warn = Dygraph.warn;
/**
* @param {string} message
- * @private
*/
Dygraph.error = function(message) {
Dygraph.log(Dygraph.ERROR, message);
};
-/**
- * @param {string} message
- * @private
- */
-Dygraph.prototype.error = Dygraph.error;
/**
* Return the 2d context for a dygraph canvas.
/**
* Add an event handler. This smooths a difference between IE and the rest of
* the world.
- * @param { !Element } 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
+ * @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
*/
* Add an event handler. This event handler is kept until the graph is
* destroyed with a call to graph.destroy().
*
- * @param { !Element } 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
+ * @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.addEvent = function addEvent(elem, type, fn) {
+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 {!Element} elem The element to add the event to.
+ * @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
*/
-Dygraph.removeEvent = function addEvent(elem, type, fn) {
+Dygraph.removeEvent = function(elem, type, fn) {
if (elem.removeEventListener) {
elem.removeEventListener(type, fn, false);
} else {
}
};
+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_ = [];
+};
+
/**
* 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) {
// ... 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 {number}
+ * @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.
- * TODO(danvk): change obj type from Node -> !Node
- * TODO(danvk): consolidate with findPosX and return an {x, y} object.
- * @param {Node} obj
- * @return {number}
- * @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};
};
/**
};
/**
+ * 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 { {x:?number,y:?number,yval:?number} } p The point to consider, valid
+ * @param {{x:?number,y:?number,yval:?number}} p The point to consider, valid
* points are {x, y} objects
- * @param { boolean } allowNaNY Treat point with y=NaN as valid
- * @return { boolean } Whether the point has numeric x and y.
+ * @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, allowNaNY) {
+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) || (!allowNaNY && isNaN(p.y))) return false;
+ if (isNaN(p.x) || (!opt_allowNaNY && isNaN(p.y))) return false;
return true;
};
};
/**
+ * 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
* @param {!Object} self
* @param {!Object} o
* @return {!Object}
- * @private
*/
Dygraph.update = function(self, o) {
if (typeof(o) != 'undefined' && o !== null) {
};
/**
- * @param {Object} o
+ * @param {*} o
* @return {boolean}
* @private
*/
};
/**
+ * 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 <canvas> tag, e.g. w/r/t/ clipping.
* @return {boolean}
};
/**
- * 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.
* This function will scan the option list and determine if they
* require us to recalculate the pixel positions of each point.
* @param {!Array.<string>} 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
*/
return requiresNewPoints;
};
-/**
- * Compares two arrays to see if they are equal. If either parameter is not an
- * array it will return false. Does a shallow compare
- * Dygraph.compareArrays([[1,2], [3, 4]], [[1,2], [3,4]]) === false.
- * @param {!Array.<T>} array1 first array
- * @param {!Array.<T>} array2 second array
- * @return {boolean} True if both parameters are arrays, and contents are equal.
- * @template T
- */
-Dygraph.compareArrays = function(array1, array2) {
- if (!Dygraph.isArrayLike(array1) || !Dygraph.isArrayLike(array2)) {
- return false;
- }
- if (array1.length !== array2.length) {
- return false;
- }
- for (var i = 0; i < array1.length; i++) {
- if (array1[i] !== array2[i]) {
- return false;
- }
- }
- return true;
-};
-
-/**
- * @param {!CanvasRenderingContext2D} ctx the canvas context
- * @param {number} sides the number of sides in the shape.
- * @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
- * regular polygon.
- * @private
- */
-Dygraph.regularShape_ = function(
- ctx, sides, radius, cx, cy, rotationRadians, delta) {
- 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];
- };
-
- var initialCoordinates = computeCoordinates();
- var x = initialCoordinates[0];
- var y = initialCoordinates[1];
- ctx.moveTo(x, y);
-
- for (var idx = 0; idx < sides; idx++) {
- angle = (idx == sides - 1) ? initialAngle : (angle + delta);
- var coords = computeCoordinates();
- ctx.lineTo(coords[0], coords[1]);
- }
- ctx.fill();
- ctx.stroke();
-};
-
-/**
- * TODO(danvk): be more specific on the return type.
- * @param {number} sides
- * @param {number=} rotationRadians
- * @param {number=} delta
- * @return {Function}
- * @private
- */
-Dygraph.shapeFunction_ = function(sides, rotationRadians, delta) {
- return function(g, name, ctx, cx, cy, color, radius) {
- ctx.strokeStyle = color;
- ctx.fillStyle = "white";
- Dygraph.regularShape_(ctx, sides, radius, cx, cy, rotationRadians, delta);
- };
-};
-
Dygraph.Circles = {
DEFAULT : function(g, name, ctx, canvasx, canvasy, color, radius) {
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false);
ctx.fill();
- },
- TRIANGLE : Dygraph.shapeFunction_(3),
- SQUARE : Dygraph.shapeFunction_(4, Math.PI / 4),
- DIAMOND : Dygraph.shapeFunction_(4),
- PENTAGON : Dygraph.shapeFunction_(5),
- HEXAGON : Dygraph.shapeFunction_(6),
- CIRCLE : function(g, name, ctx, cx, cy, color, radius) {
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.fillStyle = "white";
- ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false);
- ctx.fill();
- ctx.stroke();
- },
- STAR : Dygraph.shapeFunction_(5, 0, 4 * Math.PI / 5),
- PLUS : function(g, name, ctx, cx, cy, color, radius) {
- ctx.strokeStyle = color;
-
- ctx.beginPath();
- ctx.moveTo(cx + radius, cy);
- ctx.lineTo(cx - radius, cy);
- ctx.closePath();
- ctx.stroke();
-
- ctx.beginPath();
- ctx.moveTo(cx, cy + radius);
- ctx.lineTo(cx, cy - radius);
- ctx.closePath();
- ctx.stroke();
- },
- EX : function(g, name, ctx, cx, cy, color, radius) {
- ctx.strokeStyle = color;
-
- ctx.beginPath();
- ctx.moveTo(cx + radius, cy + radius);
- ctx.lineTo(cx - radius, cy - radius);
- ctx.closePath();
- ctx.stroke();
-
- ctx.beginPath();
- ctx.moveTo(cx + radius, cy - radius);
- ctx.lineTo(cx - radius, cy + radius);
- ctx.closePath();
- ctx.stroke();
}
+ // For more shapes, include extras/shapes.js
};
/**
* };
* window.addEventListener('mouseup', mouseUpHandler);
* };
- *
+ *
* @constructor
*/
Dygraph.IFrameTarp = function() {
var iframes = document.getElementsByTagName("iframe");
for (var i = 0; i < iframes.length; i++) {
var iframe = iframes[i];
- var x = Dygraph.findPosX(iframe),
- y = Dygraph.findPosY(iframe),
+ var pos = Dygraph.findPos(iframe),
+ x = pos.x,
+ y = pos.y,
width = iframe.offsetWidth,
height = iframe.offsetHeight;
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.<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);
+ }
+ }
+};
+
+/**
+ * Converts any valid CSS color (hex, rgb(), named color) to an RGB tuple.
+ *
+ * @param {!string} colorStr Any valid CSS color string.
+ * @return {{r:number,g:number,b:number}} Parsed RGB tuple.
+ * @private
+ */
+Dygraph.toRGB_ = function(colorStr) {
+ // TODO(danvk): cache color parses to avoid repeated DOM manipulation.
+ var div = document.createElement('div');
+ div.style.backgroundColor = colorStr;
+ div.style.visibility = 'hidden';
+ document.body.appendChild(div);
+ var rgbStr = window.getComputedStyle(div, null).backgroundColor;
+ document.body.removeChild(div);
+ var bits = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(rgbStr);
+ return {
+ r: parseInt(bits[1], 10),
+ g: parseInt(bits[2], 10),
+ b: parseInt(bits[3], 10)
+ };
+};
+
+/**
+ * Checks whether the browser supports the <canvas> tag.
+ * @param {HTMLCanvasElement=} opt_canvasElement Pass a canvas element as an
+ * optimization if you have one.
+ * @return {boolean} Whether the browser supports canvas.
+ */
+Dygraph.isCanvasSupported = function(opt_canvasElement) {
+ var canvas;
+ try {
+ canvas = opt_canvasElement || document.createElement("canvas");
+ canvas.getContext("2d");
+ }
+ catch (e) {
+ var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
+ var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
+ if ((!ie) || (ie[1] < 6) || (opera))
+ return false;
+ return true;
+ }
+ return true;
+};
+
+/**
+ * Parses the value as a floating point number. This is like the parseFloat()
+ * built-in, but with a few differences:
+ * - the empty string is parsed as null, rather than NaN.
+ * - if the string cannot be parsed at all, an error is logged.
+ * If the string can't be parsed, this method returns null.
+ * @param {string} x The string to be parsed
+ * @param {number=} opt_line_no The line number from which the string comes.
+ * @param {string=} opt_line The text of the line from which the string comes.
+ */
+Dygraph.parseFloat_ = function(x, opt_line_no, opt_line) {
+ var val = parseFloat(x);
+ if (!isNaN(val)) return val;
+
+ // Try to figure out what happeend.
+ // If the value is the empty string, parse it as null.
+ if (/^ *$/.test(x)) return null;
+
+ // If it was actually "NaN", return it as NaN.
+ if (/^ *nan *$/i.test(x)) return NaN;
+
+ // Looks like a parsing error.
+ var msg = "Unable to parse '" + x + "' as a number";
+ if (opt_line !== undefined && opt_line_no !== undefined) {
+ msg += " on line " + (1+(opt_line_no||0)) + " ('" + opt_line + "') of CSV.";
+ }
+ Dygraph.error(msg);
+
+ return null;
+};