The Simile Timeline allows you to make beautiful, interactive timelines for web pages using only JavaScript. It is fairly flexible and allows you to create timelines with intervals of Seconds, Hours and Days right up to Centuries and Millenniums.
However, it does not support Quarters, which is what I needed. So I have submitted a patch to the project, and have also been using the following code so I can use Quarters now without needing to patch my local install.
The code is not the most elegant, as it duplicates a lot of the code from the library, but until my patch gets incorporated it is the only way to have Quarters.
To use it, download the js file and include it in your web page after the timeline files. You can then use a Quarter in the same way as you would any interval, as shown below:
Timeline.createBandInfo({
...
intervalUnit: Timeline.DateTime.QUARTER,
...
});
Find the code on GitHub, or [DDET expand it below:]
/* * This allows you to use a Quarter as an interval in the Simile Timeline. * See http://andrew-jones.com/2009/12/01/using-a-quarter-interval-with-the-simile-timeline/ for info. * It has been tested with version 2.3.0 (March 6, 2009). Unlikely to work with other versions. * For more information on the timeline, see http://www.simile-widgets.org/wiki/Timeline */
SimileAjax.DateTime.QUARTER = 11;
(function() { var d = SimileAjax.DateTime; var a = d.gregorianUnitLengths; a[d.QUARTER] = a[d.MONTH] * 3;})();
/** * Rounds date objects down to the nearest interval or multiple of an interval. * This method modifies the given date object, converting it to the given * timezone if specified. * * @param {Date} date the date object to round * @param {Number} intervalUnit a constant, integer index specifying an * interval, e.g. SimileAjax.DateTime.HOUR * @param {Number} timeZone a timezone shift, given in hours * @param {Number} multiple a multiple of the interval to round by * @param {Number} firstDayOfWeek an integer specifying the first day of the * week, 0 corresponds to Sunday, 1 to Monday, etc. */SimileAjax.DateTime.roundDownToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) { var timeShift = timeZone * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]; var date2 = new Date(date.getTime() + timeShift); var clearInDay = function(d) { d.setUTCMilliseconds(0); d.setUTCSeconds(0); d.setUTCMinutes(0); d.setUTCHours(0); }; var clearInYear = function(d) { clearInDay(d); d.setUTCDate(1); d.setUTCMonth(0); }; switch(intervalUnit) { case SimileAjax.DateTime.MILLISECOND: var x = date2.getUTCMilliseconds(); date2.setUTCMilliseconds(x - (x % multiple)); break; case SimileAjax.DateTime.SECOND: date2.setUTCMilliseconds(0); var x = date2.getUTCSeconds(); date2.setUTCSeconds(x - (x % multiple)); break; case SimileAjax.DateTime.MINUTE: date2.setUTCMilliseconds(0); date2.setUTCSeconds(0); var x = date2.getUTCMinutes(); date2.setTime(date2.getTime() - (x % multiple) * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]); break; case SimileAjax.DateTime.HOUR: date2.setUTCMilliseconds(0); date2.setUTCSeconds(0); date2.setUTCMinutes(0); var x = date2.getUTCHours(); date2.setUTCHours(x - (x % multiple)); break; case SimileAjax.DateTime.DAY: clearInDay(date2); break; case SimileAjax.DateTime.WEEK: clearInDay(date2); var d = (date2.getUTCDay() + 7 - firstDayOfWeek) % 7; date2.setTime(date2.getTime() - d * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY]); break; case SimileAjax.DateTime.MONTH: clearInDay(date2); date2.setUTCDate(1); var x = date2.getUTCMonth(); date2.setUTCMonth(x - (x % multiple)); break; case SimileAjax.DateTime.QUARTER: // added this clearInDay(date2); date2.setUTCDate(1); // x is the numeric month // need to work out the start of this quarter var x = date2.getUTCMonth(); if( x <= 2 ){ x = 0; } else if( x >= 3 && x <= 5 ){ x = 3; } else if( x >= 6 && x <= 8 ){ x = 6; } else { x = 9; } date2.setUTCMonth(x - (x % multiple)); break; case SimileAjax.DateTime.YEAR: clearInYear(date2); var x = date2.getUTCFullYear(); date2.setUTCFullYear(x - (x % multiple)); break; case SimileAjax.DateTime.DECADE: clearInYear(date2); date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 10) * 10); break; case SimileAjax.DateTime.CENTURY: clearInYear(date2); date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 100) * 100); break; case SimileAjax.DateTime.MILLENNIUM: clearInYear(date2); date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 1000) * 1000); break; } date.setTime(date2.getTime() - timeShift);};/** * Increments a date object by a specified interval, taking into * consideration the timezone. * * @param {Date} date the date object to increment * @param {Number} intervalUnit a constant, integer index specifying an * interval, e.g. SimileAjax.DateTime.HOUR * @param {Number} timeZone the timezone offset in hours */SimileAjax.DateTime.incrementByInterval = function(date, intervalUnit, timeZone) { timeZone = (typeof timeZone == 'undefined') ? 0 : timeZone;
var timeShift = timeZone * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]; var date2 = new Date(date.getTime() + timeShift);
switch(intervalUnit) { case SimileAjax.DateTime.MILLISECOND: date2.setTime(date2.getTime() + 1) break; case SimileAjax.DateTime.SECOND: date2.setTime(date2.getTime() + 1000); break; case SimileAjax.DateTime.MINUTE: date2.setTime(date2.getTime() + SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]); break; case SimileAjax.DateTime.HOUR: date2.setTime(date2.getTime() + SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]); break; case SimileAjax.DateTime.DAY: date2.setUTCDate(date2.getUTCDate() + 1); break; case SimileAjax.DateTime.WEEK: date2.setUTCDate(date2.getUTCDate() + 7); break; case SimileAjax.DateTime.MONTH: date2.setUTCMonth(date2.getUTCMonth() + 1); break; case SimileAjax.DateTime.QUARTER: // added this date2.setUTCMonth(date2.getUTCMonth() + 3); break; case SimileAjax.DateTime.YEAR: date2.setUTCFullYear(date2.getUTCFullYear() + 1); break; case SimileAjax.DateTime.DECADE: date2.setUTCFullYear(date2.getUTCFullYear() + 10); break; case SimileAjax.DateTime.CENTURY: date2.setUTCFullYear(date2.getUTCFullYear() + 100); break; case SimileAjax.DateTime.MILLENNIUM: date2.setUTCFullYear(date2.getUTCFullYear() + 1000); break; }
date.setTime(date2.getTime() - timeShift);};Timeline.GregorianDateLabeller.prototype.defaultLabelInterval = function(date, intervalUnit) { var text; var emphasized = false; date = SimileAjax.DateTime.removeTimeZoneOffset(date, this._timeZone); switch(intervalUnit) { case SimileAjax.DateTime.MILLISECOND: text = date.getUTCMilliseconds(); break; case SimileAjax.DateTime.SECOND: text = date.getUTCSeconds(); break; case SimileAjax.DateTime.MINUTE: var m = date.getUTCMinutes(); if (m == 0) { text = date.getUTCHours() + ":00"; emphasized = true; } else { text = m; } break; case SimileAjax.DateTime.HOUR: text = date.getUTCHours() + "hr"; break; case SimileAjax.DateTime.DAY: text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate(); break; case SimileAjax.DateTime.WEEK: text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate(); break; case SimileAjax.DateTime.MONTH: var m = date.getUTCMonth(); if (m != 0) { text = Timeline.GregorianDateLabeller.getMonthName(m, this._locale); break; } // else, fall through case SimileAjax.DateTime.QUARTER: // Added this var m = date.getUTCMonth(); // which quarter are we in? switch( m ){ case 0: text = 'Q1 ' + date.getUTCFullYear(); break; case 3: text = 'Q2 ' + date.getUTCFullYear(); break; case 6: text = 'Q3 ' + date.getUTCFullYear(); break; case 9: text = 'Q4 ' + date.getUTCFullYear(); break; } break; case SimileAjax.DateTime.YEAR: case SimileAjax.DateTime.DECADE: case SimileAjax.DateTime.CENTURY: case SimileAjax.DateTime.MILLENNIUM: var y = date.getUTCFullYear(); if (y > 0) { text = date.getUTCFullYear(); } else { text = (1 - y) + "BC"; } emphasized = (intervalUnit == SimileAjax.DateTime.MONTH) || (intervalUnit == SimileAjax.DateTime.DECADE && y % 100 == 0) || (intervalUnit == SimileAjax.DateTime.CENTURY && y % 1000 == 0); break; default: text = date.toUTCString(); } return { text: text, emphasized: emphasized };}[/DDET]