Reputation: 3797
I have a line chart in d3.js.
The X-axis has dates, as defined by:
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b/%d/%Y")).ticks(d3.timeYear))
.selectAll("text")
.style("text-anchor", "end")
.attr("dy", ".25em")
.attr("transform", "rotate(-45)");
I would like to show additional information next to each date. Specifically, a calculation based on the value of the line for that date. For the sake of simplicity, let's say I just want to display the value of the line at that point, multiplied by 3. So, if on 04/19/2020 the value of the line is 150, then the X-axis should read:
04/19/2020
450
If I try to use the axis transform to insert the text into the axis label directly, I get an error, presumably because it's no longer a valid date/time format then. So I think I have to add the text as a separate element, but I don't understand how I can iterate over every point on the graph and calculate the proper text to display. Calculating the correct Y position should be easy for each individual case (y is the full height of the graph plus some fixed offset) so the problem is to iterate and set the correct X and value for each point.
Upvotes: 4
Views: 1645
Reputation: 11414
You can define create a custom tickFormat
method. For example:
function customFormat(e) {
// do whatever calculation you like with e here
return d3.timeFormat("%b/%d/%Y")(e) + "-" + e.getDay();
}
Then you can pass it to tickFormat
like this:
d3.axisBottom(x).tickFormat(customFormat).ticks(d3.timeYear)
This is what it will look like:
If the custom text needs to be below the existing axis, you can add another x-axis. You can also use a custom tickFormat
for it and make its lines invisible. For example:
function customFormat(e) {
return e.getDay();
}
let anotheraxis = g.append("g")
.attr("transform", "translate(0," + (height + 60) + ")")
.call(d3.axisBottom(x).tickFormat(customFormat).ticks(d3.timeYear));
// Hide the new axis' lines
anotheraxis
.selectAll("line")
.style('opacity', 0);
anotheraxis
.selectAll("path")
.style('opacity', 0);
This will look like:
You mention "a calculation based on the value of the line for that date." Of course, if your data was generated by some kind of function, you could just pass the tick value in your custom format to that function. However, if you have a set of data points, like in your JSFiddle example, you would have to use interpolation to calculate the value of your curve at that tick value. D3 doesn't seem to provide an easy way to do this, but these questions might be starting points for you.
It's also possible to use the tickValues
method to set which specific ticks the axis should have. If you set it to your data's x-values, then you won't need to do any interpolation because you already have the y-values.
For example, you can create an array containing only your data's dates:
let dates = data.map((x) => x.date);
And then construct an object that contains the values of your data for the dates:
let date_values = data.reduce((o, x) => { o[x.date] = x.value; return o;}, {});
Then you can create a custom format that uses the date_values
object to get the value for a date and multiplies it by 3:
function customFormat(e) {
return d3.timeFormat("%b/%d/%Y")(e) + "-" + (3 * date_values[e]);
}
And then finally, use the custom format and dates
array for the axis:
.call(d3.axisBottom(x).tickFormat(customFormat).tickValues(dates))
I've created a demo here. This is what it looks like:
Another way you can add values to the bottom of the axis, is to select all of the .tick
elements and add a new text
element for each of them. For example:
let axis_bottom = g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b/%d/%Y")).ticks(d3.timeYear).ticks(5));
axis_bottom
.selectAll("text")
.attr("dy", ".5em");
axis_bottom
.selectAll('.tick')
.append('text')
.attr("dy", "2.5em")
.attr("fill", "currentColor")
.text((e) => {
// Do whatever calculation on e here.
return 'abcd';
});
This is what it will look like:
Upvotes: 2