7

I'm trying to code an interval round function using the d3.js time intervals API.

The thing I want to do is fairly simple: write a function that rounds a time to the nearest 6 hours and returns it as a Date object.

For example:

  • At 10:30, d3.hour.my6HourRound(new Date) should return 12:00 today
  • At 12:30, d3.hour.my6HourRound(new Date) should return 12:00 today
  • At 23:50, d3.hour.my6HourRound(new Date) should return 00:00 tomorrow

It must not be so difficult, but d3.js api lacks of usage demos in API.

Cihad Turhan
  • 2,749
  • 6
  • 28
  • 45

3 Answers3

4

Here's my solution which makes use of built-in d3 functions:

function another6HourRound(date) {
    var subHalf = d3.time.hour.offset(date, -3);
    var addHalf = d3.time.hour.offset(date, 3);
    return d3.time.hours(subHalf, addHalf, 6)[0];
}

Returns the nearest 6 hour interval (on 00, 06, 12, or 18)

Logan
  • 165
  • 1
  • 7
  • Nice solution @Logan. Can you explain how this works? – Cihad Turhan Apr 15 '14 at 06:55
  • 2
    You basically create a 6-hour range centered on your date. This ensures that the 6-hour interval that you're rounding to is within that range. Then `d3.time.hours` returns an array of all the dates that fall on that 'normalized' 6-hour interval. There should be only one date, so you return the first element, and you end up with the closest interval to your date. – Logan Apr 15 '14 at 14:52
2

You are looking for this example: http://bl.ocks.org/mbostock/4149176

Working example for your case: http://bl.ocks.org/musically-ut/7699650

Code from example

function timeFormat(formats) {
  return function(date) {
    var i = formats.length - 1, f = formats[i];
    while (!f[1](date)) f = formats[--i];
    return d3.functor(f[0])(date);
  };
}

var customTimeFormat = timeFormat([
  [d3.time.format("%Y"), function() { return true; }],
  [d3.time.format("%B"), function(d) { return d.getMonth(); }],
  [d3.time.format("%b %d"), function(d) { return d.getDate() != 1; }],
  [d3.time.format("%a %d"), function(d) { return d.getDay() && d.getDate() != 1; }],
  [d3.time.format("%I %p"), function(d) { return d.getHours(); }],
  [d3.time.format("%I:%M"), function(d) { return d.getMinutes(); }],
  [d3.time.format(":%S"), function(d) { return d.getSeconds(); }],
  [d3.time.format(".%L"), function(d) { return d.getMilliseconds(); }]
]);

var xAxis = d3.svg.axis()
    .scale(x) // x is a scale.
    .tickFormat(customTimeFormat);

In your case, you want something of this kind:

var customTimeFormat = timeFormat([
  ["00:00", function () { return true; }],
  ["06:00", function (d) { return 3 <= d.getHours() && d.getHours() < 9; }],
  ["12:00", function (d) { return 9 <= d.getHours() && d.getHours() < 15; }],
  ["18:00", function (d) { return 15 <= d.getHours() && d.getHours() < 21; }]
]);
musically_ut
  • 34,028
  • 8
  • 94
  • 106
  • Alright, your edit shed some light. Currently, I'm debugging the code you linked above, I put a breakpoint at line 59. `return f[0](date)` and I logged `f[0]` on console. And my brain f*cked because it gives me something like `%Y` Screenshot [here](http://postimg.org/image/k17ni3rg1/) – Cihad Turhan Nov 29 '13 at 00:03
  • @CihadTurhan Ah, I should have left a comment. I have updated the code; Mike should have used `d3.functor(f[0])` instead of just `f[0]`. – musically_ut Nov 29 '13 at 00:13
  • Most probably I need to learn more. Thanks for your effort, it works. However, I don't want to draw an axis. I need to use this as a function which returns me a date object. Look [this link](http://square.github.io/crossfilter/) for example. The bottom chart is daily, because they used `d3.time.day` but when I try to use the code above, it gives error. How can I make this graph 6 hourly, 5 daily etc for example? thanks for sparing time for this. – Cihad Turhan Nov 29 '13 at 00:27
  • @CihadTurhan Sorry for misunderstanding your question. I am afraid I am signing off for the day, I'll have a look at the crossfilter example tomorrow. – musically_ut Nov 29 '13 at 00:30
2

Well, I've implemented my own solution. By diving into the source code of D3, I found how they write their time functions. For hour it's like:

  d3_time.hour = d3_time_interval(function(date) {
    var timezone = date.getTimezoneOffset() / 60;
    return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 36e5);
  }, function(date) {
    return date.getHours();
  });
  d3_time.hours = d3_time.hour.range;
  d3_time.hours.utc = d3_time.hour.utc.range;

and I simply write

my6HourRound = function(date) {
    var hours = 6;
    var timezone = date.getTimezoneOffset() / 60;
    return new Date((Math.floor(date / 36e5 / hours - timezone) + timezone) * 36e5 * hours);
}

which works. I'm pretty sure there's a better method which makes this generic using D3 functions. Otherwise, you need to define custom functions for day, month, week, year etc. Therefore I'm not accepting my own question.

I'm looking forward to the one which make it in D3 way.

Cihad Turhan
  • 2,749
  • 6
  • 28
  • 45