
assets.lib.dygraphs.dygraph-utils.js Maven / Gradle / Ivy
/**
* @license
* Copyright 2011 Dan Vanderkam ([email protected])
* MIT-licensed (http://opensource.org/licenses/MIT)
*/
/**
* @fileoverview This file contains utility functions used by dygraphs. These
* are typically static (i.e. not related to any particular dygraph). Examples
* include date/time formatting functions, basic algorithms (e.g. binary
* search) and generic DOM-manipulation functions.
*/
(function() {
/*global Dygraph:false, G_vmlCanvasManager:false, Node:false */
"use strict";
Dygraph.LOG_SCALE = 10;
Dygraph.LN_TEN = Math.log(Dygraph.LOG_SCALE);
/**
* @private
* @param {number} x
* @return {number}
*/
Dygraph.log10 = function(x) {
return Math.log(x) / Dygraph.LN_TEN;
};
/** 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];
/**
* Return the 2d context for a dygraph canvas.
*
* This method is only exposed for the sake of replacing the function in
* automated tests, e.g.
*
* var oldFunc = Dygraph.getContext();
* Dygraph.getContext = function(canvas) {
* var realContext = oldFunc(canvas);
* return new Proxy(realContext);
* };
* @param {!HTMLCanvasElement} canvas
* @return {!CanvasRenderingContext2D}
* @private
*/
Dygraph.getContext = function(canvas) {
return /** @type{!CanvasRenderingContext2D}*/(canvas.getContext("2d"));
};
/**
* Add an event handler. This smooths a difference between IE and the rest of
* the world.
* @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) {
elem.addEventListener(type, fn, false);
} else {
elem[type+fn] = function(){fn(window.event);};
elem.attachEvent('on'+type, 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
*/
Dygraph.removeEvent = function(elem, type, fn) {
if (elem.removeEventListener) {
elem.removeEventListener(type, fn, false);
} else {
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_ = [];
};
/**
* 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.
* @private
*/
Dygraph.cancelEvent = function(e) {
e = e ? e : window.event;
if (e.stopPropagation) {
e.stopPropagation();
}
if (e.preventDefault) {
e.preventDefault();
}
e.cancelBubble = true;
e.cancel = true;
e.returnValue = false;
return false;
};
/**
* 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.
* @private
*/
Dygraph.hsvToRGB = function (hue, saturation, value) {
var red;
var green;
var blue;
if (saturation === 0) {
red = value;
green = value;
blue = value;
} else {
var i = Math.floor(hue * 6);
var f = (hue * 6) - i;
var p = value * (1 - saturation);
var q = value * (1 - (saturation * f));
var t = value * (1 - (saturation * (1 - f)));
switch (i) {
case 1: red = q; green = value; blue = p; break;
case 2: red = p; green = value; blue = t; break;
case 3: red = p; green = q; blue = value; break;
case 4: red = t; green = p; blue = value; break;
case 5: red = value; green = p; blue = q; break;
case 6: // fall through
case 0: red = value; green = t; blue = p; break;
}
}
red = Math.floor(255 * red + 0.5);
green = Math.floor(255 * green + 0.5);
blue = Math.floor(255 * blue + 0.5);
return 'rgb(' + red + ',' + green + ',' + blue + ')';
};
// The following functions are from quirksmode.org with a modification for Safari from
// http://blog.firetree.net/2005/07/04/javascript-find-position/
// http://www.quirksmode.org/js/findpos.html
// ... and modifications to support scrolling divs.
/**
* 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.findPos = function(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
var copyObj = obj;
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";
}
curleft += parseInt(borderLeft, 10) ;
curtop += parseInt(borderTop, 10) ;
curleft += copyObj.offsetLeft;
curtop += copyObj.offsetTop;
if (!copyObj.offsetParent) {
break;
}
copyObj = copyObj.offsetParent;
}
} 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) {
curleft -= obj.scrollLeft;
curtop -= obj.scrollTop;
obj = obj.parentNode;
}
return {x: curleft, y: curtop};
};
/**
* 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.documentElement;
var b = document.body;
return e.clientX +
(de.scrollLeft || b.scrollLeft) -
(de.clientLeft || 0);
}
};
/**
* 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.documentElement;
var b = document.body;
return e.clientY +
(de.scrollTop || b.scrollTop) -
(de.clientTop || 0);
}
};
/**
* 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
*/
Dygraph.isOK = function(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;
};
/**
* Number formatting function which mimicks the behavior of %g in printf, i.e.
* either exponential or fixed format (without trailing 0s) is used depending on
* the length of the generated string. The advantage of this format is that
* there is a predictable upper bound on the resulting string length,
* significant figures are not dropped, and normal numbers are not displayed in
* exponential notation.
*
* NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g.
* It creates strings which are too long for absolute values between 10^-4 and
* 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
* string length should be precision + 6 (e.g 1.123e+300).
*/
Dygraph.floatFormat = function(x, opt_precision) {
// Avoid invalid precision values; [1, 21] is the valid range.
var p = Math.min(Math.max(1, opt_precision || 2), 21);
// This is deceptively simple. The actual algorithm comes from:
//
// Max allowed length = p + 4
// where 4 comes from 'e+n' and '.'.
//
// Length of fixed format = 2 + y + p
// where 2 comes from '0.' and y = # of leading zeroes.
//
// Equating the two and solving for y yields y = 2, or 0.00xxxx which is
// 1.0e-3.
//
// Since the behavior of toPrecision() is identical for larger numbers, we
// don't have to worry about the other bound.
//
// 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) ?
x.toExponential(p - 1) : x.toPrecision(p);
};
/**
* 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;
};
/**
* Date accessors to get the parts of a calendar date (year, month,
* day, hour, minute, second and millisecond) according to local time,
* and factory method to call the Date constructor with an array of arguments.
*/
Dygraph.DateAccessorsLocal = {
getFullYear: function(d) {return d.getFullYear();},
getMonth: function(d) {return d.getMonth();},
getDate: function(d) {return d.getDate();},
getHours: function(d) {return d.getHours();},
getMinutes: function(d) {return d.getMinutes();},
getSeconds: function(d) {return d.getSeconds();},
getMilliseconds: function(d) {return d.getMilliseconds();},
getDay: function(d) {return d.getDay();},
makeDate: function(y, m, d, hh, mm, ss, ms) {
return new Date(y, m, d, hh, mm, ss, ms);
}
};
/**
* Date accessors to get the parts of a calendar date (year, month,
* day of month, hour, minute, second and millisecond) according to UTC time,
* and factory method to call the Date constructor with an array of arguments.
*/
Dygraph.DateAccessorsUTC = {
getFullYear: function(d) {return d.getUTCFullYear();},
getMonth: function(d) {return d.getUTCMonth();},
getDate: function(d) {return d.getUTCDate();},
getHours: function(d) {return d.getUTCHours();},
getMinutes: function(d) {return d.getUTCMinutes();},
getSeconds: function(d) {return d.getUTCSeconds();},
getMilliseconds: function(d) {return d.getUTCMilliseconds();},
getDay: function(d) {return d.getUTCDay();},
makeDate: function(y, m, d, hh, mm, ss, ms) {
return new Date(Date.UTC(y, m, d, hh, mm, ss, ms));
}
};
/**
* Return a string version of the hours, minutes and seconds portion of a date.
* @param {number} hh The hours (from 0-23)
* @param {number} mm The minutes (from 0-59)
* @param {number} ss The seconds (from 0-59)
* @return {string} A time of the form "HH:MM" or "HH:MM:SS"
* @private
*/
Dygraph.hmsString_ = function(hh, mm, ss) {
var zeropad = Dygraph.zeropad;
var ret = zeropad(hh) + ":" + zeropad(mm);
if (ss) {
ret += ":" + zeropad(ss);
}
return ret;
};
/**
* Convert a JS date (millis since epoch) to a formatted string.
* @param {number} time The JavaScript time value (ms since epoch)
* @param {boolean} utc Wether output UTC or local time
* @return {string} A date of one of these forms:
* "YYYY/MM/DD", "YYYY/MM/DD HH:MM" or "YYYY/MM/DD HH:MM:SS"
* @private
*/
Dygraph.dateString_ = function(time, utc) {
var zeropad = Dygraph.zeropad;
var accessors = utc ? Dygraph.DateAccessorsUTC : Dygraph.DateAccessorsLocal;
var date = new Date(time);
var y = accessors.getFullYear(date);
var m = accessors.getMonth(date);
var d = accessors.getDate(date);
var hh = accessors.getHours(date);
var mm = accessors.getMinutes(date);
var ss = accessors.getSeconds(date);
// Get a year string:
var year = "" + y;
// Get a 0 padded month string
var month = zeropad(m + 1); //months are 0-offset, sigh
// Get a 0 padded day string
var day = zeropad(d);
var frac = hh * 3600 + mm * 60 + ss;
var ret = year + "/" + month + "/" + day;
if (frac) {
ret += " " + Dygraph.hmsString_(hh, mm, ss);
}
return 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
* @private
*/
Dygraph.round_ = function(num, places) {
var shift = Math.pow(10, places);
return Math.round(num * shift)/shift;
};
/**
* Implementation of binary search over an array.
* Currently does not work when val is outside the range of arry's values.
* @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 || low === undefined ||
high === null || high === undefined) {
low = 0;
high = arry.length - 1;
}
if (low > high) {
return -1;
}
if (abs === null || abs === undefined) {
abs = 0;
}
var validIndex = function(idx) {
return idx >= 0 && idx < arry.length;
};
var mid = parseInt((low + high) / 2, 10);
var element = arry[mid];
var idx;
if (element == val) {
return mid;
} else if (element > val) {
if (abs > 0) {
// Accept if element > val, but also if prior element < val.
idx = mid - 1;
if (validIndex(idx) && arry[idx] < val) {
return mid;
}
}
return Dygraph.binarySearch(val, arry, abs, low, mid - 1);
} else if (element < val) {
if (abs < 0) {
// Accept if element < val, but also if prior element > val.
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
};
/**
* 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} 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) {
dateStrSlashed = dateStrSlashed.replace("-", "/");
}
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);
d = Dygraph.dateStrToMillis(dateStrSlashed);
} else {
// Any format that Date.parse will accept, e.g. "2009/07/12" or
// "2009/07/12 12:34:56"
d = Dygraph.dateStrToMillis(dateStr);
}
if (!d || isNaN(d)) {
console.error("Couldn't parse " + dateStr + " as a date");
}
return d;
};
/**
* 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 {number} millis since epoch
* @private
*/
Dygraph.dateStrToMillis = function(str) {
return new Date(str).getTime();
};
// These functions are all based on MochiKit.
/**
* Copies all the properties from o to self.
*
* @param {!Object} self
* @param {!Object} o
* @return {!Object}
*/
Dygraph.update = function(self, o) {
if (typeof(o) != 'undefined' && o !== null) {
for (var k in o) {
if (o.hasOwnProperty(k)) {
self[k] = o[k];
}
}
}
return self;
};
/**
* Copies all the properties from o to self.
*
* @param {!Object} self
* @param {!Object} o
* @return {!Object}
* @private
*/
Dygraph.updateDeep = function (self, o) {
// Taken from http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
function isNode(o) {
return (
typeof Node === "object" ? o instanceof Node :
typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
);
}
if (typeof(o) != 'undefined' && o !== null) {
for (var k in o) {
if (o.hasOwnProperty(k)) {
if (o[k] === null) {
self[k] = null;
} else if (Dygraph.isArrayLike(o[k])) {
self[k] = o[k].slice();
} else if (isNode(o[k])) {
// DOM objects are shallowly-copied.
self[k] = o[k];
} else if (typeof(o[k]) == 'object') {
if (typeof(self[k]) != 'object' || self[k] === null) {
self[k] = {};
}
Dygraph.updateDeep(self[k], o[k]);
} else {
self[k] = o[k];
}
}
}
}
return self;
};
/**
* @param {*} o
* @return {boolean}
* @private
*/
Dygraph.isArrayLike = function(o) {
var typ = typeof(o);
if (
(typ != 'object' && !(typ == 'function' &&
typeof(o.item) == 'function')) ||
o === null ||
typeof(o.length) != 'number' ||
o.nodeType === 3
) {
return false;
}
return true;
};
/**
* @param {Object} o
* @return {boolean}
* @private
*/
Dygraph.isDateLike = function (o) {
if (typeof(o) != "object" || o === null ||
typeof(o.getTime) != 'function') {
return false;
}
return true;
};
/**
* Note: this only seems to work for arrays.
* @param {!Array} o
* @return {!Array}
* @private
*/
Dygraph.clone = function(o) {
// TODO(danvk): figure out how MochiKit's version works
var r = [];
for (var i = 0; i < o.length; i++) {
if (Dygraph.isArrayLike(o[i])) {
r.push(Dygraph.clone(o[i]));
} else {
r.push(o[i]);
}
}
return r;
};
/**
* 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(
/**@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;
var backingStoreRatio = context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
if (devicePixelRatio !== undefined) {
return devicePixelRatio / backingStoreRatio;
} else {
// At least devicePixelRatio must be defined for this ratio to make sense.
// We default backingStoreRatio to 1: this does not exist on some browsers
// (i.e. desktop Chrome).
return 1;
}
} catch (e) {
return 1;
}
};
/**
* Checks whether the user is on an Android browser.
* Android does not fully support the
© 2015 - 2025 Weber Informatics LLC | Privacy Policy