* middle of the years.
*/
-/*jshint globalstrict:true, sub:true */
+/*jshint sub:true */
/*global Dygraph:false */
+(function() {
"use strict";
/** @typedef {Array.<{v:number, label:string, label_v:(string|undefined)}>} */
Dygraph.TWO_HOURLY = 11;
Dygraph.SIX_HOURLY = 12;
Dygraph.DAILY = 13;
-Dygraph.WEEKLY = 14;
-Dygraph.MONTHLY = 15;
-Dygraph.QUARTERLY = 16;
-Dygraph.BIANNUAL = 17;
-Dygraph.ANNUAL = 18;
-Dygraph.DECADAL = 19;
-Dygraph.CENTENNIAL = 20;
-Dygraph.NUM_GRANULARITIES = 21;
+Dygraph.TWO_DAILY = 14;
+Dygraph.WEEKLY = 15;
+Dygraph.MONTHLY = 16;
+Dygraph.QUARTERLY = 17;
+Dygraph.BIANNUAL = 18;
+Dygraph.ANNUAL = 19;
+Dygraph.DECADAL = 20;
+Dygraph.CENTENNIAL = 21;
+Dygraph.NUM_GRANULARITIES = 22;
// Date components enumeration (in the order of the arguments in Date)
// TODO: make this an @enum
Dygraph.NUM_DATEFIELDS = 7;
-/** @type {Array.<{datefield:number, step:number, spacing:number}>} */
+/**
+ * The value of datefield will start at an even multiple of "step", i.e.
+ * if datefield=SS and step=5 then the first tick will be on a multiple of 5s.
+ *
+ * For granularities <= HOURLY, ticks are generated every `spacing` ms.
+ *
+ * At coarser granularities, ticks are generated by incrementing `datefield` by
+ * `step`. In this case, the `spacing` value is only used to estimate the
+ * number of ticks. It should roughly correspond to the spacing between
+ * adjacent ticks.
+ *
+ * @type {Array.<{datefield:number, step:number, spacing:number}>}
+ */
Dygraph.TICK_PLACEMENT = [];
Dygraph.TICK_PLACEMENT[Dygraph.SECONDLY] = {datefield: Dygraph.DATEFIELD_SS, step: 1, spacing: 1000 * 1};
Dygraph.TICK_PLACEMENT[Dygraph.TWO_SECONDLY] = {datefield: Dygraph.DATEFIELD_SS, step: 2, spacing: 1000 * 2};
Dygraph.TICK_PLACEMENT[Dygraph.TWO_HOURLY] = {datefield: Dygraph.DATEFIELD_HH, step: 2, spacing: 1000 * 3600 * 2};
Dygraph.TICK_PLACEMENT[Dygraph.SIX_HOURLY] = {datefield: Dygraph.DATEFIELD_HH, step: 6, spacing: 1000 * 3600 * 6};
Dygraph.TICK_PLACEMENT[Dygraph.DAILY] = {datefield: Dygraph.DATEFIELD_D, step: 1, spacing: 1000 * 86400};
+Dygraph.TICK_PLACEMENT[Dygraph.TWO_DAILY] = {datefield: Dygraph.DATEFIELD_D, step: 2, spacing: 1000 * 86400 * 2};
Dygraph.TICK_PLACEMENT[Dygraph.WEEKLY] = {datefield: Dygraph.DATEFIELD_D, step: 7, spacing: 1000 * 604800};
Dygraph.TICK_PLACEMENT[Dygraph.MONTHLY] = {datefield: Dygraph.DATEFIELD_M, step: 1, spacing: 1000 * 7200 * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 / 12
Dygraph.TICK_PLACEMENT[Dygraph.QUARTERLY] = {datefield: Dygraph.DATEFIELD_M, step: 3, spacing: 1000 * 21600 * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 / 4
* NOTE: this assumes that Dygraph.LOG_SCALE = 10.
* @type {Array.<number>}
*/
-Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
+Dygraph.PREFERRED_LOG_TICK_VALUES = (function() {
var vals = [];
for (var power = -39; power <= 39; power++) {
var range = Math.pow(10, power);
}
}
return vals;
-}();
+})();
/**
* Determine the correct granularity of ticks on a date axis.
Dygraph.getDateAxis = function(start_time, end_time, granularity, opts, dg) {
var formatter = /** @type{AxisLabelFormatter} */(
opts("axisLabelFormatter"));
- var utc = opts("labelsDateUTC");
-
- var step = Dygraph.TICK_PLACEMENT[granularity].step;
+ var utc = opts("labelsUTC");
+ var accessors = utc ? Dygraph.DateAccessorsUTC : Dygraph.DateAccessorsLocal;
+
var datefield = Dygraph.TICK_PLACEMENT[granularity].datefield;
-
- // Choose appropiate date methods according to UTC or local time option.
- // weekday: return the day of week from a Date object.
- // decompose_date: decompose a Date object into an array of datefields.
- // compose_date: compose a Date object from an array of date fields.
- var compose_date, decompose_date, weekday;
- if (utc) {
- weekday = function (d) {
- return d.getUTCDay();
- };
- decompose_date = function (d) {
- var a = [];
- a[Dygraph.DATEFIELD_Y] = d.getUTCFullYear();
- a[Dygraph.DATEFIELD_M] = d.getUTCMonth();
- a[Dygraph.DATEFIELD_D] = d.getUTCDate();
- a[Dygraph.DATEFIELD_HH] = d.getUTCHours();
- a[Dygraph.DATEFIELD_MM] = d.getUTCMinutes();
- a[Dygraph.DATEFIELD_SS] = d.getUTCSeconds();
- a[Dygraph.DATEFIELD_MS] = d.getUTCMilliseconds();
- return a;
- };
- compose_date = function (a) {
- var d = new Date(Date.UTC(a[Dygraph.DATEFIELD_Y],
- a[Dygraph.DATEFIELD_M],
- a[Dygraph.DATEFIELD_D],
- a[Dygraph.DATEFIELD_HH],
- a[Dygraph.DATEFIELD_MM],
- a[Dygraph.DATEFIELD_SS],
- a[Dygraph.DATEFIELD_MS]));
- return d;
- };
- } else {
- weekday = function(d) {
- return d.getDay();
- };
- decompose_date = function (d) {
- var a = [];
- a[Dygraph.DATEFIELD_Y] = d.getFullYear();
- a[Dygraph.DATEFIELD_M] = d.getMonth();
- a[Dygraph.DATEFIELD_D] = d.getDate();
- a[Dygraph.DATEFIELD_HH] = d.getHours();
- a[Dygraph.DATEFIELD_MM] = d.getMinutes();
- a[Dygraph.DATEFIELD_SS] = d.getSeconds();
- a[Dygraph.DATEFIELD_MS] = d.getMilliseconds();
- return a;
- };
- compose_date = function (a) {
- var d = new Date(a[Dygraph.DATEFIELD_Y],
- a[Dygraph.DATEFIELD_M],
- a[Dygraph.DATEFIELD_D],
- a[Dygraph.DATEFIELD_HH],
- a[Dygraph.DATEFIELD_MM],
- a[Dygraph.DATEFIELD_SS],
- a[Dygraph.DATEFIELD_MS]);
- return d;
- };
- }
-
+ var step = Dygraph.TICK_PLACEMENT[granularity].step;
+ var spacing = Dygraph.TICK_PLACEMENT[granularity].spacing;
+
// Choose a nice tick position before the initial instant.
// Currently, this code deals properly with the existent daily granularities:
// DAILY (with step of 1) and WEEKLY (with step of 7 but specially handled).
// Other daily granularities (say TWO_DAILY) should also be handled specially
// by setting the start_date_offset to 0.
var start_date = new Date(start_time);
- var date_array = decompose_date(start_date);
+ var date_array = [];
+ date_array[Dygraph.DATEFIELD_Y] = accessors.getFullYear(start_date);
+ date_array[Dygraph.DATEFIELD_M] = accessors.getMonth(start_date);
+ date_array[Dygraph.DATEFIELD_D] = accessors.getDate(start_date);
+ date_array[Dygraph.DATEFIELD_HH] = accessors.getHours(start_date);
+ date_array[Dygraph.DATEFIELD_MM] = accessors.getMinutes(start_date);
+ date_array[Dygraph.DATEFIELD_SS] = accessors.getSeconds(start_date);
+ date_array[Dygraph.DATEFIELD_MS] = accessors.getMilliseconds(start_date);
+
var start_date_offset = date_array[datefield] % step;
if (granularity == Dygraph.WEEKLY) {
// This will put the ticks on Sundays.
- start_date_offset = weekday(start_date);
+ start_date_offset = accessors.getDay(start_date);
}
+
date_array[datefield] -= start_date_offset;
for (var df = datefield + 1; df < Dygraph.NUM_DATEFIELDS; df++) {
// The minimum value is 1 for the day of month, and 0 for all other fields.
}
// Generate the ticks.
- // This relies on the roll over property of the Date functions:
- // when some date field is set to a value outside of its logical range,
- // the excess 'rolls over' the next (more significant) field.
- // When using local time with DST transitions, different dates may represent
- // the same time instant, so do not repeat the tick. At each step,
- // we have to check that the date is effectively increased because native
- // JS date functions do not assert that on DST transitions.
- // Since start_date is no later than start_time (but possibly equal),
- // assuming a previous tick just before start_time also removes an spurious
- // tick outside the given time range.
+ // For granularities not coarser than HOURLY we use the fact that:
+ // the number of milliseconds between ticks is constant
+ // and equal to the defined spacing.
+ // Otherwise we rely on the 'roll over' property of the Date functions:
+ // when some date field is set to a value outside of its logical range,
+ // the excess 'rolls over' the next (more significant) field.
+ // However, when using local time with DST transitions,
+ // there are dates that do not represent any time value at all
+ // (those in the hour skipped at the 'spring forward'),
+ // and the JavaScript engines usually return an equivalent value.
+ // Hence we have to check that the date is properly increased at each step,
+ // returning a date at a nice tick position.
var ticks = [];
- var next_tick_date = compose_date(date_array);
- var next_tick_time = next_tick_date.getTime();
- var prev_tick_time = start_time - 1;
- while (next_tick_time <= end_time) {
- if (next_tick_time > prev_tick_time) {
- ticks.push({ v: next_tick_time,
- label: formatter(next_tick_date, granularity, opts, dg)
+ var tick_date = accessors.makeDate.apply(null, date_array);
+ var tick_time = tick_date.getTime();
+ if (granularity <= Dygraph.HOURLY) {
+ if (tick_time < start_time) {
+ tick_time += spacing;
+ tick_date = new Date(tick_time);
+ }
+ while (tick_time <= end_time) {
+ ticks.push({ v: tick_time,
+ label: formatter(tick_date, granularity, opts, dg)
});
- prev_tick_time = next_tick_time;
+ tick_time += spacing;
+ tick_date = new Date(tick_time);
+ }
+ } else {
+ if (tick_time < start_time) {
+ date_array[datefield] += step;
+ tick_date = accessors.makeDate.apply(null, date_array);
+ tick_time = tick_date.getTime();
+ }
+ while (tick_time <= end_time) {
+ if (granularity >= Dygraph.DAILY ||
+ accessors.getHours(tick_date) % step === 0) {
+ ticks.push({ v: tick_time,
+ label: formatter(tick_date, granularity, opts, dg)
+ });
+ }
+ date_array[datefield] += step;
+ tick_date = accessors.makeDate.apply(null, date_array);
+ tick_time = tick_date.getTime();
}
- date_array[datefield] += step;
- next_tick_date = compose_date(date_array);
- next_tick_time = next_tick_date.getTime();
}
return ticks;
};
Dygraph.DEFAULT_ATTRS['axes']['y']['ticker'] = Dygraph.numericTicks;
Dygraph.DEFAULT_ATTRS['axes']['y2']['ticker'] = Dygraph.numericTicks;
}
+
+})();