Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

d3.svg.axis().ticks(d3.time.day, n) sets ticks at end and beginning of certain months for certain intervals #2240

Closed
matthewmueller opened this issue Feb 18, 2015 · 10 comments

Comments

@matthewmueller
Copy link

I'm not sure there's much that can be done about this, but for certain intervals you'll get ticks at both the end and the front of the certain months.

For something like d3.svg.axis().ticks(d3.time.day, 3 /* or 1, 5, 10... anything divisible by 30 */), the ticks will look something like this:

Jan 25   Jan 28   **Jan 31   Feb 1**   Feb 3

This is because the conditional evaluates to true: https://github.com/mbostock/d3/blob/a40a611d6b9fc4ff3815ca830d86b6c00d130995/d3.js#L2414 on the first of the month and end of the month.

Following the D3 logic, here's an example for axis.ticks(d3.time.day, 3):

// True for January 31
var dt = 3;
var d = new Date('January 31')
var time = d.getDate() - 1; // 30
var m = time % dt // 30 % 3 == 0
if (!m) times.push(d);

// True for February 1
var d = new Date('February 1')
var time = d.getDate() - 1; // 0
var m = time % dt // 0 % 3 == 0
if (!m) times.push(d);
@matthewmueller matthewmueller changed the title d3.svg.axis().ticks(d3.time.day, n) sets ticks at end and beginning of certain months d3.svg.axis().ticks(d3.time.day, n) sets ticks at end and beginning of certain months for certain intervals Feb 18, 2015
@jasondavies
Copy link
Contributor

Related: #1310.

I think this is working as expected. My guess is that in your particular case you want something like the post-filtering solution that Mike suggests in the discussion linked above, because you probably want regular fixed intervals.

@KamilSzot
Copy link

Isn't there any way to place a tick every 3 days regardless of the month? Something like every 3 total days not every 3rd day of the month?

@KamilSzot
Copy link

I think I can implement it myself like that:

// copied from d3js source, I guess this function is not exposed in the api
function d3_time_range(floor, step, number) {
  return function(t0, t1, dt) {
    var time = floor(t0), times = [];
    if (time < t0) step(time);
    if (dt > 1) {
      while (time < t1) {
        var date = new Date(+time);
        if (!(number(date) % dt)) times.push(date);
        step(time);
      }
    } else {
      while (time < t1) times.push(new Date(+time)), step(time);
    }
    return times;
  };
}

d3.time.daysTotal = d3_time_range(d3.time.day, function(date) {
  date.setDate(date.getDate() + 1);
}, function(date) {
  return ~~(date/86400000);
});

And then:

scale.ticks(d3.time.daysTotal, 5)

means "every 5 days, no matter what day of month tick lands on"

@KamilSzot
Copy link

Maybe we could implement that kinds of intervals or at least expose d3_time_range so people can create them easily ad hoc?

@dwadelson
Copy link

Thanks for your beautiful solution, KamilSzot...i agree that this should be exposed...it would be helpful in the underlying function to have a parameter resetOnMonth to toggle the behavior

@mbostock
Copy link
Member

mbostock commented Jul 5, 2015

Careful: that implementation appears to have a bug in time zones that have daylight saving time changes at midnight, or for dates before epoch (January 1, 1970)… but… close enough for the vast majority of use cases.

I will consider this issue for the new 4.0 API in d3-time and d3-scale. I’ve created two related issues, d3/d3-time#6 and d3/d3-scale#7. It’s pretty easy to create a three-day interval (regardless of month) in the new API thanks to interval.filter and day.count:

var threeday = day.filter(function(d) { return day.count(0, d) % 3 === 0; });

But I haven’t yet exposed a way to use such a custom interval with the new time scale. I’ll probably just allow you to pass an interval—or possibly a range function—to time.ticks, as before. (Though, I don’t think the new time scale will allow you to specify the optional step if you pass in a custom interval.)

@dwadelson
Copy link

i need it because for what i'm plotting, i'd like the registration to be the same day every week.
i'm too ignant to see the bug for daylight savings time, but i will definitely be needing to plot across such changes...is it because of the divide by 86400? can i fix it with a simple if?

@mbostock
Copy link
Member

mbostock commented Jul 5, 2015

The implementation @KamilSzot posted, and which is in essence the same as the 4.0-based implemented I posted, will not show the same day every week unless you happen to use a step that is a multiple of 7. Instead, it always has the same number of days between ticks. This is different from the current 3.x d3.time.days, which filters based on the day-of-month number and thus may skip fewer than step days when crossing the month boundary.

At any rate, I agree that skipping step days regardless of month boundaries is useful, and my current proposal for ticks exactly every three days is:

scale.ticks(day.filter(function(d) { return day.count(0, d) % 3 === 0; }))

(Note: day.count(0, d) counts the number of days since epoch.)

@dwadelson
Copy link

sorry for this ignorant question, but how/where is day defined in the snippet you've provided?

@mbostock
Copy link
Member

mbostock commented Jul 5, 2015

You need to be using the 4.0 API, which hasn’t been officially released yet. I included the snippet for the purposes of discussing the future API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants