X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-utils.js;h=d6dfae5a820419bcc8e57c017bfb67bbef59318d;hb=464b5f504e75c5d2b98eff12b3b8ad520a1729cb;hp=10a393cfb2c6948e5d1cf7dac085ab2a595129d9;hpb=d91ba598b82c927945744c7041dc05500b5545b3;p=dygraphs.git diff --git a/dygraph-utils.js b/dygraph-utils.js index 10a393c..d6dfae5 100644 --- a/dygraph-utils.js +++ b/dygraph-utils.js @@ -33,11 +33,13 @@ 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]; @@ -53,6 +55,7 @@ Dygraph.DOT_DASH_LINE = [7, 2, 2, 2]; * @private */ Dygraph.log = function(severity, message) { + // var st; if (typeof(printStackTrace) != 'undefined') { try { @@ -74,27 +77,40 @@ Dygraph.log = function(severity, message) { // Oh well, it was worth a shot! } } + // 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; } } + // if (Dygraph.LOG_STACK_TRACES) { window.console.log(st.join('\n')); } + // }; /** @@ -104,11 +120,6 @@ Dygraph.log = function(severity, message) { Dygraph.info = function(message) { Dygraph.log(Dygraph.INFO, message); }; -/** - * @param {string} message - * @private - */ -Dygraph.prototype.info = Dygraph.info; /** * @param {string} message @@ -117,24 +128,13 @@ Dygraph.prototype.info = Dygraph.info; 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. @@ -183,7 +183,7 @@ Dygraph.addEvent = function addEvent(elem, type, fn) { * 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 }); }; @@ -197,7 +197,7 @@ Dygraph.prototype.addEvent = function addEvent(elem, type, fn) { * 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 { @@ -211,6 +211,17 @@ Dygraph.removeEvent = function addEvent(elem, type, fn) { } }; +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. @@ -279,64 +290,47 @@ 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 {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}; }; /** @@ -479,6 +473,30 @@ 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 @@ -612,7 +630,6 @@ Dygraph.dateStrToMillis = function(str) { * @param {!Object} self * @param {!Object} o * @return {!Object} - * @private */ Dygraph.update = function(self, o) { if (typeof(o) != 'undefined' && o !== null) { @@ -796,7 +813,7 @@ Dygraph.Iterator.prototype.next = function() { }; /** - * 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. @@ -816,7 +833,9 @@ Dygraph.createIterator = function(array, start, length, opt_predicate) { // Shim layer with setTimeout fallback. // From: http://paulirish.com/2011/requestanimationframe-for-smart-animating/ -Dygraph.requestAnimFrame = (function(){ +// Should be called with the window context: +// Dygraph.requestAnimFrame.call(window, function() {}) +Dygraph.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || @@ -841,7 +860,7 @@ Dygraph.requestAnimFrame = (function(){ * @private */ Dygraph.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis, - cleanupFn) { + cleanupFn) { var frameNumber = 0; var previousFrameNumber; var startTime = new Date().getTime(); @@ -854,7 +873,7 @@ Dygraph.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis, (function loop() { if (frameNumber >= maxFrames) return; - Dygraph.requestAnimFrame(function() { + 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(); @@ -871,7 +890,7 @@ Dygraph.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis, repeatFn(maxFrameArg); // Ensure final call with maxFrameArg. cleanupFn(); } else { - if (frameDelta != 0) { // Don't call repeatFn with duplicate frames. + if (frameDelta !== 0) { // Don't call repeatFn with duplicate frames. repeatFn(frameNumber); } loop(); @@ -884,7 +903,7 @@ Dygraph.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis, * This function will scan the option list and determine if they * require us to recalculate the pixel positions of each point. * @param {!Array.} 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 */ @@ -986,138 +1005,14 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) { 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.} array1 first array - * @param {!Array.} 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/stars.js }; /** @@ -1142,7 +1037,7 @@ Dygraph.Circles = { * }; * window.addEventListener('mouseup', mouseUpHandler); * }; - * + * * @constructor */ Dygraph.IFrameTarp = function() { @@ -1211,3 +1106,86 @@ Dygraph.detectLineDelimiter = function(data) { 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.)} + */ +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} color_str Any valid CSS color string. + * @return {{r:number,g:number,b:number}} Parsed RGB tuple. + * @private + */ +Dygraph.toRGB_ = function(color_str) { + // TODO(danvk): cache color parses to avoid repeated DOM manipulation. + var div = document.createElement('div'); + div.style.backgroundColor = color_str; + div.style.visibility = 'hidden'; + document.body.appendChild(div); + var rgb_str = window.getComputedStyle(div).backgroundColor; + document.body.removeChild(div); + var bits = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(rgb_str); + return { + r: parseInt(bits[1], 10), + g: parseInt(bits[2], 10), + b: parseInt(bits[3], 10) + }; +};