Delete multiple_axes-old properly. Keep it dead, dead, dead.
[dygraphs.git] / dygraph-tickers.js
index 1e0c61d..35cd75a 100644 (file)
@@ -58,8 +58,9 @@
  *   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)}>} */
@@ -232,14 +233,15 @@ Dygraph.HOURLY = 10;
 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
@@ -253,7 +255,19 @@ Dygraph.DATEFIELD_MS = 6;
 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};
@@ -269,6 +283,7 @@ Dygraph.TICK_PLACEMENT[Dygraph.HOURLY]          = {datefield: Dygraph.DATEFIELD_
 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
@@ -285,7 +300,7 @@ Dygraph.TICK_PLACEMENT[Dygraph.CENTENNIAL]      = {datefield: Dygraph.DATEFIELD_
  * 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);
@@ -295,7 +310,7 @@ Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
     }
   }
   return vals;
-}();
+})();
 
 /**
  * Determine the correct granularity of ticks on a date axis.
@@ -342,80 +357,34 @@ Dygraph.numDateTicks = function(start_time, end_time, granularity) {
 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.
@@ -423,30 +392,50 @@ Dygraph.getDateAxis = function(start_time, end_time, granularity, opts, dg) {
   }
 
   // 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;
 };
@@ -463,3 +452,5 @@ if (Dygraph &&
   Dygraph.DEFAULT_ATTRS['axes']['y']['ticker'] = Dygraph.numericTicks;
   Dygraph.DEFAULT_ATTRS['axes']['y2']['ticker'] = Dygraph.numericTicks;
 }
+
+})();