Reputation: 974
I'm implementing a 2d chart using canvas. I want to reuse d3's logic for generating the chart's axes. d3 does quite a lot of good work in generating axes and I'd like to take advantage of it.
(Note: For backward-compatibility reasons I'm stuck using d3v3 for the time being.)
Consider the following d3 code:
let scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
let axis = d3.svg.axis()
.scale(scale)
.ticks(num_ticks)
.tickSize(10)
.orient("bottom");
I can render this into a chart div with:
svg.selectAll('.x-axis').call(axis);
I want to be able to programmatically get the tick data out of axis
, including the formatted labels, by writing a function that behaves as follows:
ticks = get_axis_ticks(axis)
ticks
should hold each tick position (as a Date
in this particular case) and the corresponding d3-generated label.
[[Date(...), "Wed 19"],
[Date(...), "Fri 21"],
[Date(...), "Apr 23"],
[Date(...), "Tue 25"],
...]
I could then use this data to paint an axis on my canvas.
I've dug into d3v3 source (in particular here: https://github.com/d3/d3/blob/v3.5.17/src/svg/axis.js) but I find it very difficult to tease apart the logic from the SVG manipulation.
Help would be much appreciated.
Upvotes: 3
Views: 586
Reputation: 974
I was able to get this working. I found the time formatting strategy in the d3 docs: https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format_multi I believe this is the strategy that d3 itself uses by default when users do not provide a custom format.
I learned that simply calling scale.ticks(N)
will return N
ish ticks suitable for rendering on an axis. These tick values are chosen on natural boundaries. E.g., if you're working with a time axis, these ticks will be aligned on minute, hour, day boundaries.
Here's a solution:
let time_format = d3.time.format.multi([
[".%L", d => d.getMilliseconds()],
[":%S", d => d.getSeconds()],
["%I:%M", d => d.getMinutes()],
["%I %p", d => d.getHours()],
["%a %d", d => d.getDay() && d.getDate() !== 1],
["%b %d", d => d.getDate() !== 1],
["%B", d => d.getMonth()],
["%Y", () => true]
]);
let get_ticks = (width, lo, hi, num_ticks) => {
let scale = d3.time.scale().range([0, width]).domain([lo, hi]);
return scale.ticks(num_ticks).map(t => [t, time_format(t)]);
};
Upvotes: 1
Reputation: 2093
One idea I have is to use the scale function you have created to generate the ticks you desire and push them into an array.
As a very simple example, if you would like 10 ticks, each incrementing by a unit of 1, you could do something like this: https://jsfiddle.net/Q5Jag/3148/
//define dummy values
var lo = 1;
var hi = 10;
var width = 512
var scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
//define your function
var get_x_axis = function() {
let axisArr = [];
for(var i = 0; i < 10; i++ ) {
//calculate your value
axisArr.push(scale(i))
}
return axisArr
}
//call it
let axisTicks = get_x_axis()
//log it
console.log(axisTicks)
I'm not sure if this is what you're looking for, but if you need further help just ask.
Upvotes: 1