Reputation: 18899
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];
};
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
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