diff --git a/API.md b/API.md index 7273ae7..84bf209 100644 --- a/API.md +++ b/API.md @@ -582,10 +582,10 @@ xaxis: { mode: "time" timeformat: "%Y/%m/%d" } -``` +``` -This will result in tick labels like "2000/12/24". A subset of the -standard strftime specifiers are supported: +This will result in tick labels like "2000/12/24". A subset of the +standard strftime specifiers are supported (plus the nonstandard %q): ```js %a: weekday name (customizable) @@ -596,6 +596,7 @@ standard strftime specifiers are supported: %I: hours, 12-hour time, zero-padded (01-12) %m: month, zero-padded (01-12) %M: minutes, zero-padded (00-59) +%q: quarter (1-4) %S: seconds, zero-padded (00-59) %y: year (two digits) %Y: year (four digits) diff --git a/NEWS.md b/NEWS.md index 90ad945..04346ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,9 +13,9 @@ the dates are displayed. If null, the dates are displayed as UTC. If "browser", the dates are displayed in the time zone of the user's browser. Date/time formatting has changed and now follows a proper subset of the -standard strftime specifiers. Additionally, if a strftime function is found in -the Date object's prototype, it will be used instead of the built-in -formatter. +standard strftime specifiers, plus one nonstandard specifier for quarters. +Additionally, if a strftime function is found in the Date object's prototype, +it will be used instead of the built-in formatter. Axis labels are now drawn with canvas text with some parsing to support newlines. This solves various issues but also means that they no longer @@ -29,6 +29,9 @@ The base and overlay canvas are now using the CSS classes "flot-base" and ### Changes ### + - Addition of nonstandard %q specifier to date/time formatting. (patch + by risicle, issue 49) + - Date/time formatting follows proper subset of strftime specifiers, and support added for Date.prototype.strftime, if found. (patch by Mark Cote, issues 419 and 558) diff --git a/examples/time.html b/examples/time.html index 8eb1c0b..0c66c37 100644 --- a/examples/time.html +++ b/examples/time.html @@ -23,6 +23,7 @@
Zoom to: +
@@ -69,6 +70,17 @@ $(function () { }); }); + $("#ninetyninequarters").click(function () { + $.plot($("#placeholder"), [d], { + xaxis: { + mode: "time", + minTickSize: [1, "quarter"], + min: (new Date(1999, 0, 1)).getTime(), + max: (new Date(2000, 0, 1)).getTime() + } + }); + }); + $("#ninetynine").click(function () { $.plot($("#placeholder"), [d], { xaxis: { diff --git a/jquery.flot.time.js b/jquery.flot.time.js index a365b75..3af0f81 100644 --- a/jquery.flot.time.js +++ b/jquery.flot.time.js @@ -71,6 +71,9 @@ API.txt for details. case 'l': c = leftPad(hours12, " "); break; case 'm': c = leftPad(d.getMonth() + 1); break; case 'M': c = leftPad(d.getMinutes()); break; + // quarters not in Open Group's strftime specification + case 'q': + c = "" + (Math.floor(d.getMonth() / 3) + 1); break; case 'S': c = leftPad(d.getSeconds()); break; case 'y': c = leftPad(d.getFullYear() % 100); break; case 'Y': c = "" + d.getFullYear(); break; @@ -156,13 +159,14 @@ API.txt for details. "hour": 60 * 60 * 1000, "day": 24 * 60 * 60 * 1000, "month": 30 * 24 * 60 * 60 * 1000, + "quarter": 3 * 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 = [ + var baseSpec = [ [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], @@ -171,10 +175,17 @@ API.txt for details. [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"] + [2, "month"] ]; + // we don't know which variant(s) we'll need yet, but generating both is + // cheap + + var specMonths = baseSpec.concat([[3, "month"], [6, "month"], + [1, "year"]]); + var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], + [1, "year"]]); + function init(plot) { plot.hooks.processDatapoints.push(function (plot, series, datapoints) { $.each(plot.getAxes(), function(axisName, axis) { @@ -188,6 +199,14 @@ API.txt for details. var d = dateGenerator(axis.min, opts); var minSize = 0; + // make quarter use a possibility if quarters are + // mentioned in either of these options + + var spec = (opts.tickSize && opts.tickSize[1] === + "quarter") || + (opts.minTickSize && opts.minTickSize[1] === + "quarter") ? specQuarters : specMonths; + if (opts.minTickSize != null) { if (typeof opts.tickSize == "number") { minSize = opts.tickSize; @@ -255,6 +274,9 @@ API.txt for details. d.setHours(floorInBase(d.getHours(), tickSize)); } else if (unit == "month") { d.setMonth(floorInBase(d.getMonth(), tickSize)); + } else if (unit == "quarter") { + d.setMonth(3 * floorInBase(d.getMonth() / 3, + tickSize)); } else if (unit == "year") { d.setFullYear(floorInBase(d.getFullYear(), tickSize)); } @@ -271,6 +293,10 @@ API.txt for details. d.setHours(0); } else if (step >= timeUnitSize.day * 4) { d.setDate(1); + } else if (step >= timeUnitSize.month * 2) { + d.setMonth(floorInBase(d.getMonth(), 3)); + } else if (step >= timeUnitSize.quarter * 2) { + d.setMonth(floorInBase(d.getMonth(), 6)); } else if (step >= timeUnitSize.year) { d.setMonth(0); } @@ -285,22 +311,25 @@ API.txt for details. v = d.getTime(); ticks.push(v); - if (unit == "month") { + if (unit == "month" || unit == "quarter") { 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 + // a bit complicated - we'll divide the + // month/quarter 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); + d.setMonth(d.getMonth() + + (unit == "quarter" ? 3 : 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); + d.setMonth(d.getMonth() + + tickSize * (unit == "quarter" ? 3 : 1)); } } else if (unit == "year") { d.setFullYear(d.getFullYear() + tickSize); @@ -322,6 +351,14 @@ API.txt for details. return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); } + // possibly use quarters if quarters are mentioned in + // any of these places + + var useQuarters = (axis.options.tickSize && + axis.options.tickSize[1] == "quarter") || + (axis.options.minTickSize && + axis.options.minTickSize[1] == "quarter"); + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; var span = axis.max - axis.min; var suffix = (opts.twelveHourClock) ? " %p" : ""; @@ -338,12 +375,19 @@ API.txt for details. } } else if (t < timeUnitSize.month) { fmt = "%b %d"; - } else if (t < timeUnitSize.year) { + } else if ((useQuarters && t < timeUnitSize.quarter) || + (!useQuarters && t < timeUnitSize.year)) { if (span < timeUnitSize.year) { fmt = "%b"; } else { fmt = "%b %Y"; } + } else if (useQuarters && t < timeUnitSize.year) { + if (span < timeUnitSize.year) { + fmt = "Q%q"; + } else { + fmt = "Q%q %Y"; + } } else { fmt = "%Y"; }