X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-tickers.js;h=35cd75af7f905ca25abc146d1701fc7284a33705;hb=2e4626574edc0a30cc3059694b5d04193e0f32ae;hp=1f88f9b0c7705c3334c425ee2fb8fccf00246043;hpb=34497c3aa0d477381f4b558629a3cef2727dccb1;p=dygraphs.git diff --git a/dygraph-tickers.js b/dygraph-tickers.js index 1f88f9b..35cd75a 100644 --- a/dygraph-tickers.js +++ b/dygraph-tickers.js @@ -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.} */ -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. @@ -303,7 +318,7 @@ Dygraph.PREFERRED_LOG_TICK_VALUES = function() { * @param {number} a Left edge of the chart (ms) * @param {number} b Right edge of the chart (ms) * @param {number} pixels Size of the chart in the relevant dimension (width). - * @param {function(string):*} opts Function mapping from option name -> value. + * @param {function(string):*} opts Function mapping from option name -> value. * @return {number} The appropriate axis granularity for this chart. See the * enumeration of possible values in dygraph-tickers.js. */ @@ -335,119 +350,92 @@ Dygraph.numDateTicks = function(start_time, end_time, granularity) { * @param {number} start_time * @param {number} end_time * @param {number} granularity (one of the granularities enumerated above) - * @param {function(string):*} opts Function mapping from option name -> value. + * @param {function(string):*} opts Function mapping from option name -> value. * @param {Dygraph=} dg * @return {!Dygraph.TickList} */ 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. date_array[df] = (df === Dygraph.DATEFIELD_D) ? 1 : 0; } - // 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; }; @@ -464,3 +452,5 @@ if (Dygraph && Dygraph.DEFAULT_ATTRS['axes']['y']['ticker'] = Dygraph.numericTicks; Dygraph.DEFAULT_ATTRS['axes']['y2']['ticker'] = Dygraph.numericTicks; } + +})();