diff --git a/jquery.flot.time.js b/jquery.flot.time.js index cfe5245..7b87f2c 100644 --- a/jquery.flot.time.js +++ b/jquery.flot.time.js @@ -4,306 +4,338 @@ Pretty handling of time axes. Set axis.mode to "time" to enable. See the section "Time series data" in API.txt for details. */ + (function ($) { - var options = {}; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - - // Returns a string with the date d formatted according to fmt. - // A subset of the Open Group's strftime format is supported. - function formatDate(d, fmt, monthNames, dayNames) { - if (typeof d.strftime == "function") { - return d.strftime(fmt); - } - var leftPad = function(n, pad) { - n = "" + n; - pad = "" + (pad == null ? "0" : pad); - return n.length == 1 ? pad + n : n; - }; - - var r = []; - var escape = false; - var hours = d.getHours(); - var isAM = hours < 12; - if (monthNames == null) - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - if (dayNames == null) - dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - - var hours12; - if (hours > 12) { - hours12 = hours - 12; - } else if (hours == 0) { - hours12 = 12; - } else { - hours12 = hours; - } - - for (var i = 0; i < fmt.length; ++i) { - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'a': c = "" + dayNames[d.getDay()]; break; - case 'b': c = "" + monthNames[d.getMonth()]; break; - case 'd': c = leftPad(d.getDate()); break; - case 'e': c = leftPad(d.getDate(), " "); break; - case 'H': c = leftPad(hours); break; - case 'I': c = leftPad(hours12); break; - case 'l': c = leftPad(hours12, " "); break; - case 'm': c = leftPad(d.getMonth() + 1); break; - case 'M': c = leftPad(d.getMinutes()); break; - case 'S': c = leftPad(d.getSeconds()); break; - case 'y': c = leftPad(d.getFullYear() % 100); break; - case 'Y': c = "" + d.getFullYear(); break; - case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; - case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; - case 'w': c = "" + d.getDay(); break; - } - r.push(c); - escape = false; - } - else { - if (c == "%") - escape = true; - else - r.push(c); - } - } - return r.join(""); - } - - // To have a consistent view of time-based data independent of which time - // zone the client happens to be in we need a date-like object independent - // of time zones. This is done through a wrapper that only calls the UTC - // versions of the accessor methods. - function makeUtcWrapper(d) { - function addProxyMethod(sourceObj, sourceMethod, targetObj, - targetMethod) { - sourceObj[sourceMethod] = function() { - return targetObj[targetMethod].apply(targetObj, arguments); - }; - }; - var utc = { - date: d - }; - // support strftime, if found - if (d.strftime != undefined) - addProxyMethod(utc, "strftime", d, "strftime"); - addProxyMethod(utc, "getTime", d, "getTime"); - addProxyMethod(utc, "setTime", d, "setTime"); - var props = [ "Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds" ]; - for (var p = 0; p < props.length; p++) { - addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); - addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); - } - return utc; - }; - - // select time zone strategy. This returns a date-like object tied to the - // desired timezone - function dateGenerator(ts, opts) { - if (opts.timezone == "browser") { - return new Date(ts); - } else if (!opts.timezone || opts.timezone == "utc") { - return makeUtcWrapper(new Date(ts)); - } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { - var d = new timezoneJS.Date(); - // timezone-js is fickle, so be sure to set the time zone before - // setting the time. - d.setTimezone(opts.timezone); - d.setTime(ts); - return d; - } else { - return makeUtcWrapper(new Date(ts)); - } - } - - // map of app. size of time units in milliseconds - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - var spec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"], [3, "month"], [6, "month"], - [1, "year"] - ]; - - function init(plot) { - plot.hooks.processDatapoints.push(function (plot, series, datapoints) { - $.each(plot.getAxes(), function(axisName, axis) { - var opts = axis.options; - if (opts.mode == "time") { - axis.tickGenerator = function(axis) { - var ticks = [], - d = dateGenerator(axis.min, opts), - minSize = 0; - - if (opts.minTickSize != null) { - if (typeof opts.tickSize == "number") - minSize = opts.tickSize; - else - minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; - } - - for (var i = 0; i < spec.length - 1; ++i) - if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) - break; - var size = spec[i][0]; - var unit = spec[i][1]; - - // special-case the possibility of several years - if (unit == "year") { - // if given a minTickSize in years, just use it, - // ensuring that it's an integer - if (opts.minTickSize != null && opts.minTickSize[1] == "year") { - size = Math.floor(opts.minTickSize[0]); - } else { - var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); - var norm = (axis.delta / timeUnitSize.year) / magn; - if (norm < 1.5) - size = 1; - else if (norm < 3) - size = 2; - else if (norm < 7.5) - size = 5; - else - size = 10; - - size *= magn; - } - - // minimum size for years is 1 - if (size < 1) - size = 1; - } - - axis.tickSize = opts.tickSize || [size, unit]; - var tickSize = axis.tickSize[0]; - unit = axis.tickSize[1]; - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") - d.setSeconds(floorInBase(d.getSeconds(), tickSize)); - if (unit == "minute") - d.setMinutes(floorInBase(d.getMinutes(), tickSize)); - if (unit == "hour") - d.setHours(floorInBase(d.getHours(), tickSize)); - if (unit == "month") - d.setMonth(floorInBase(d.getMonth(), tickSize)); - if (unit == "year") - d.setFullYear(floorInBase(d.getFullYear(), tickSize)); - - // reset smaller components - d.setMilliseconds(0); - if (step >= timeUnitSize.minute) - d.setSeconds(0); - if (step >= timeUnitSize.hour) - d.setMinutes(0); - if (step >= timeUnitSize.day) - d.setHours(0); - if (step >= timeUnitSize.day * 4) - d.setDate(1); - if (step >= timeUnitSize.year) - d.setMonth(0); - - - var carry = 0, v = Number.NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push(v); - if (unit == "month") { - if (tickSize < 1) { - // a bit complicated - we'll divide the month - // up but we need to take care of fractions - // so we don't end up in the middle of a day - d.setDate(1); - var start = d.getTime(); - d.setMonth(d.getMonth() + 1); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getHours(); - d.setHours(0); - } - else - d.setMonth(d.getMonth() + tickSize); - } - else if (unit == "year") { - d.setFullYear(d.getFullYear() + tickSize); - } - else - d.setTime(v + step); - } while (v < axis.max && v != prev); - - return ticks; - }; - - axis.tickFormatter = function (v, axis) { - var d = dateGenerator(v, axis.options); - - // first check global format - if (opts.timeformat != null) - return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - var suffix = (opts.twelveHourClock) ? " %p" : ""; - var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; - - var fmt; - if (t < timeUnitSize.minute) - fmt = hourCode + ":%M:%S" + suffix; - else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) - fmt = hourCode + ":%M" + suffix; - else - fmt = "%b %d " + hourCode + ":%M" + suffix; - } - else if (t < timeUnitSize.month) - fmt = "%b %d"; - else if (t < timeUnitSize.year) { - if (span < timeUnitSize.year) - fmt = "%b"; - else - fmt = "%b %Y"; - } - else - fmt = "%Y"; - - var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); - return rt; - }; - } - }); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'time', - version: '1.0' - }); + + var options = {}; + + // round to nearby lower multiple of base + + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + + // Returns a string with the date d formatted according to fmt. + // A subset of the Open Group's strftime format is supported. + + function formatDate(d, fmt, monthNames, dayNames) { + + if (typeof d.strftime == "function") { + return d.strftime(fmt); + } + + var leftPad = function(n, pad) { + n = "" + n; + pad = "" + (pad == null ? "0" : pad); + return n.length == 1 ? pad + n : n; + }; + + var r = []; + var escape = false; + var hours = d.getHours(); + var isAM = hours < 12; + + if (monthNames == null) + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + if (dayNames == null) + dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + + var hours12; + + if (hours > 12) { + hours12 = hours - 12; + } else if (hours == 0) { + hours12 = 12; + } else { + hours12 = hours; + } + + for (var i = 0; i < fmt.length; ++i) { + + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'a': c = "" + dayNames[d.getDay()]; break; + case 'b': c = "" + monthNames[d.getMonth()]; break; + case 'd': c = leftPad(d.getDate()); break; + case 'e': c = leftPad(d.getDate(), " "); break; + case 'H': c = leftPad(hours); break; + case 'I': c = leftPad(hours12); break; + case 'l': c = leftPad(hours12, " "); break; + case 'm': c = leftPad(d.getMonth() + 1); break; + case 'M': c = leftPad(d.getMinutes()); break; + case 'S': c = leftPad(d.getSeconds()); break; + case 'y': c = leftPad(d.getFullYear() % 100); break; + case 'Y': c = "" + d.getFullYear(); break; + case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; + case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; + case 'w': c = "" + d.getDay(); break; + } + r.push(c); + escape = false; + } else { + if (c == "%") + escape = true; + else + r.push(c); + } + } + + return r.join(""); + } + + // To have a consistent view of time-based data independent of which time + // zone the client happens to be in we need a date-like object independent + // of time zones. This is done through a wrapper that only calls the UTC + // versions of the accessor methods. + + function makeUtcWrapper(d) { + + function addProxyMethod(sourceObj, sourceMethod, targetObj, + targetMethod) { + sourceObj[sourceMethod] = function() { + return targetObj[targetMethod].apply(targetObj, arguments); + }; + }; + + var utc = { + date: d + }; + + // support strftime, if found + + if (d.strftime != undefined) + addProxyMethod(utc, "strftime", d, "strftime"); + + addProxyMethod(utc, "getTime", d, "getTime"); + addProxyMethod(utc, "setTime", d, "setTime"); + + var props = [ "Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds" ]; + + for (var p = 0; p < props.length; p++) { + addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); + addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); + } + + return utc; + }; + + // select time zone strategy. This returns a date-like object tied to the + // desired timezone + + function dateGenerator(ts, opts) { + if (opts.timezone == "browser") { + return new Date(ts); + } else if (!opts.timezone || opts.timezone == "utc") { + return makeUtcWrapper(new Date(ts)); + } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { + var d = new timezoneJS.Date(); + // timezone-js is fickle, so be sure to set the time zone before + // setting the time. + d.setTimezone(opts.timezone); + d.setTime(ts); + return d; + } else { + return makeUtcWrapper(new Date(ts)); + } + } + + // map of app. size of time units in milliseconds + + var timeUnitSize = { + "second": 1000, + "minute": 60 * 1000, + "hour": 60 * 60 * 1000, + "day": 24 * 60 * 60 * 1000, + "month": 30 * 24 * 60 * 60 * 1000, + "year": 365.2425 * 24 * 60 * 60 * 1000 + }; + + // the allowed tick sizes, after 1 year we use + // an integer algorithm + + var spec = [ + [1, "second"], [2, "second"], [5, "second"], [10, "second"], + [30, "second"], + [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], + [30, "minute"], + [1, "hour"], [2, "hour"], [4, "hour"], + [8, "hour"], [12, "hour"], + [1, "day"], [2, "day"], [3, "day"], + [0.25, "month"], [0.5, "month"], [1, "month"], + [2, "month"], [3, "month"], [6, "month"], + [1, "year"] + ]; + + function init(plot) { + plot.hooks.processDatapoints.push(function (plot, series, datapoints) { + $.each(plot.getAxes(), function(axisName, axis) { + + var opts = axis.options; + + if (opts.mode == "time") { + axis.tickGenerator = function(axis) { + var ticks = [], + d = dateGenerator(axis.min, opts), + minSize = 0; + + if (opts.minTickSize != null) { + if (typeof opts.tickSize == "number") + minSize = opts.tickSize; + else + minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; + } + + for (var i = 0; i < spec.length - 1; ++i) + if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] + + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 + && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) + break; + + var size = spec[i][0]; + var unit = spec[i][1]; + + // special-case the possibility of several years + + if (unit == "year") { + + // if given a minTickSize in years, just use it, + // ensuring that it's an integer + + if (opts.minTickSize != null && opts.minTickSize[1] == "year") { + size = Math.floor(opts.minTickSize[0]); + } else { + var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); + var norm = (axis.delta / timeUnitSize.year) / magn; + if (norm < 1.5) + size = 1; + else if (norm < 3) + size = 2; + else if (norm < 7.5) + size = 5; + else + size = 10; + + size *= magn; + } + + // minimum size for years is 1 + if (size < 1) + size = 1; + } + + axis.tickSize = opts.tickSize || [size, unit]; + var tickSize = axis.tickSize[0]; + unit = axis.tickSize[1]; + + var step = tickSize * timeUnitSize[unit]; + + if (unit == "second") + d.setSeconds(floorInBase(d.getSeconds(), tickSize)); + if (unit == "minute") + d.setMinutes(floorInBase(d.getMinutes(), tickSize)); + if (unit == "hour") + d.setHours(floorInBase(d.getHours(), tickSize)); + if (unit == "month") + d.setMonth(floorInBase(d.getMonth(), tickSize)); + if (unit == "year") + d.setFullYear(floorInBase(d.getFullYear(), tickSize)); + + // reset smaller components + + d.setMilliseconds(0); + + if (step >= timeUnitSize.minute) + d.setSeconds(0); + if (step >= timeUnitSize.hour) + d.setMinutes(0); + if (step >= timeUnitSize.day) + d.setHours(0); + if (step >= timeUnitSize.day * 4) + d.setDate(1); + if (step >= timeUnitSize.year) + d.setMonth(0); + + var carry = 0, v = Number.NaN, prev; + + do { + prev = v; + v = d.getTime(); + ticks.push(v); + if (unit == "month") { + if (tickSize < 1) { + // a bit complicated - we'll divide the month + // up but we need to take care of fractions + // so we don't end up in the middle of a day + d.setDate(1); + var start = d.getTime(); + d.setMonth(d.getMonth() + 1); + var end = d.getTime(); + d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); + carry = d.getHours(); + d.setHours(0); + } + else + d.setMonth(d.getMonth() + tickSize); + } + else if (unit == "year") { + d.setFullYear(d.getFullYear() + tickSize); + } + else + d.setTime(v + step); + } while (v < axis.max && v != prev); + + return ticks; + }; + + axis.tickFormatter = function (v, axis) { + var d = dateGenerator(v, axis.options); + + // first check global format + if (opts.timeformat != null) + return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); + + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; + var span = axis.max - axis.min; + var suffix = (opts.twelveHourClock) ? " %p" : ""; + var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; + + var fmt; + + if (t < timeUnitSize.minute) + fmt = hourCode + ":%M:%S" + suffix; + else if (t < timeUnitSize.day) { + if (span < 2 * timeUnitSize.day) + fmt = hourCode + ":%M" + suffix; + else + fmt = "%b %d " + hourCode + ":%M" + suffix; + } + else if (t < timeUnitSize.month) + fmt = "%b %d"; + else if (t < timeUnitSize.year) { + if (span < timeUnitSize.year) + fmt = "%b"; + else + fmt = "%b %Y"; + } + else + fmt = "%Y"; + + var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); + + return rt; + }; + } + }); + }); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'time', + version: '1.0' + }); + })(jQuery);