Reputation: 1496
I have a X axis that has this kind of data:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
Sequential numbers. I want to have a bottom axis with X ticks but want to always include the first and the last item—which, in this case is: 1
and 15
respectively.
The problem with d3.ticks
is that it sometimes doesn't return the last tick. And I can't use nice: true
because all ticks should be numbers that exist in the data set.
Thoughts?
EDIT: The goal is to find an even space between ticks considering that the first and the last tick should be always present.
Upvotes: 1
Views: 339
Reputation: 102174
In your specific situation you can use d3.range
to generate the ticks, that you'll pass to your axis' tickValues
method. For instance:
const scale = d3.scaleLinear([0, 15], [0, 1]);
const numberOfTicks = 7;
const tickStep = (scale.domain()[1] - scale.domain()[0]) / (numberOfTicks - 1);
const myTicks = d3.range(scale.domain()[0], scale.domain()[1] + tickStep, tickStep);
console.log(myTicks)
<script src="https://d3js.org/d3.v5.min.js"></script>
However, due to the floating-point precision issues, you may have an unexpected length for your array depending on the combination of domain/number of ticks. For instance, using [8,15]
as the domain and 7 ticks:
const scale = d3.scaleLinear([8, 15], [0, 1]);
const numberOfTicks = 7;
const tickStep = (scale.domain()[1] - scale.domain()[0]) / (numberOfTicks - 1);
const myTicks = d3.range(scale.domain()[0], scale.domain()[1] + tickStep, tickStep);
console.log(myTicks)
<script src="https://d3js.org/d3.v5.min.js"></script>
As you can see, we have 8 elements in the array, not 7.
For dealing with those cases, you can just pass the number of ticks to d3.range
and deal with the math using map
:
const scale = d3.scaleLinear().domain([8, 15]);
const numberOfTicks = 7;
const tickStep = (scale.domain()[1] - scale.domain()[0]) / (numberOfTicks - 1);
const myTicks = d3.range(numberOfTicks)
.map(d => scale.domain()[0] + d * tickStep);
console.log(myTicks);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
The previous examples generate evenly spaced ticks. However, I suppose you want integers as values. In that case, pay attention to the fact that you cannot have both integers and evenly spaced ticks for all possible combinations of domain and number of ticks.
To get integers, just round the values:
const scale = d3.scaleLinear([0, 15], [0, 1]);
const numberOfTicks = 7;
const tickStep = (scale.domain()[1] - scale.domain()[0]) / (numberOfTicks - 1);
const myTicks = d3.range(scale.domain()[0], scale.domain()[1] + tickStep, tickStep)
.map(d => Math.round(d))
console.log(myTicks)
<script src="https://d3js.org/d3.v5.min.js"></script>
And here the same approach as the third snippet, doing all the math in the map
and using [8,15]
as an example:
const scale = d3.scaleLinear().domain([8, 15]);
const numberOfTicks = 7;
const tickStep = (scale.domain()[1] - scale.domain()[0]) / (numberOfTicks - 1);
const myTicks = d3.range(numberOfTicks)
.map(d => Math.round(scale.domain()[0] + d * tickStep));
console.log(myTicks);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Upvotes: 6