Trace
Trace

Reputation: 18899

How is the number of ticks on an axis defined?

I'm new to d3 and have the following code for creating the x-axis on my graph:

export const drawXAxis = (svg, timestamps, chartWidth, chartHeight) => {
    console.log(chartWidth); // 885
    console.log(timestamps.length); // 310
    const xScale = d3.scaleLinear()
        .domain([-1, timestamps.length])
        .range([0, chartWidth]);

    const xBand = d3.scaleBand()
        .domain(
            d3.range(-1, timestamps.length))
                .range([0, chartWidth])
                .padding(0.3);

    const xAxis = d3.axisBottom()
        .scale(xScale)
        .tickFormat(function(d) {
            const ts = moment.utc(timestamps[d]);
            return ts.format('HH') + 'h';
        });

    const gX = svg.append("g")
        .attr("class", "axis x-axis")
        .attr("transform", "translate(0," + chartHeight + ")")
        .call(xAxis);

    return [xScale, xBand, xAxis, gX];
};

enter image description here

As I understand it, d3 decides on the number of ticks that appears on the X-axis.
In order to gain more control over the values appearing on the X-axis for zooming purposes, I would like to understand how d3 determines that - in this case - I have 16 ticks.
What If I want to space the ticks more evenly, for example, I want to see a tick on every 12 or 6 hours? My data contains 0 -> 23 hour values per day consistently, but d3 displays random hours on my graph.

Upvotes: 1

Views: 625

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102219

I'm gonna answer just the question in the title ("how is the number of ticks on an axis defined?"), not the one you made at the end ("What If I want to space the ticks more evenly, for example, I want to see a tick on every 12 or 6 hours?"), which is not related and quite simple to fix (and, besides that, it's certainly a duplicate).

Your question demands a detective work. Our journey starts, of course, at d3.axisBottom(). If you look at the source code, you'll see that the number of ticks in the enter selection...

tick = selection.selectAll(".tick").data(values, scale).order()

...depends on values, which is:

var values = tickValues == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) : tickValues

What this line tells us is that, if tickValues is null (no tickValues used), the code should use scale.ticks for scales that have a ticks method (continuous), our just the scale's domain for ordinal scales.

That leads us to the continuous scales. There, using a linear scale (which is the one you're using), we can see at the source code that scale.ticks returns this:

scale.ticks = function(count) {
    var d = domain();
    return ticks(d[0], d[d.length - 1], count == null ? 10 : count);
};

However, since ticks is imported from d3.array, we have to go there for seeing how the ticks are calculated. Also, since we didn't pass anything as count, count defaults to 10.

So, finally, we arrive at this:

start = Math.ceil(start / step);
stop = Math.floor(stop / step);
ticks = new Array(n = Math.ceil(stop - start + 1));
while (++i < n) ticks[i] = (start + i) * step;

Or this:

start = Math.floor(start * step);
stop = Math.ceil(stop * step);
ticks = new Array(n = Math.ceil(start - stop + 1));
while (++i < n) ticks[i] = (start - i) / step;

Depending on the value of steps. If you look at the tickIncrement function below, you can see that steps can only be 1, 2, 5 or 10 (and their negatives).

And that's all you need to know the length of the array in the variable ticks above. Depending on the start and stop values (i.e., depending on the domain), sometimes we have more than 10 ticks (16 in your case), sometimes we have less than 10, even if the default count is 10. Have a look here:

const s = d3.scaleLinear();
  
console.log(s.domain([1,12]).ticks().length);
console.log(s.domain([100,240]).ticks().length);
console.log(s.domain([10,10]).ticks().length);
console.log(s.domain([2,10]).ticks().length);
console.log(s.domain([1,4]).ticks().length);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

The last example, as you can see, gives us 16 ticks.

Upvotes: 2

Related Questions