I am making a multi-line chart and I have implemented a brush to be able to zoom into a specific domain on the x-axis. However, when I zoom in I want the y-axis to scale along so that its domain goes from [0, maxY], where maxY is the maximum y-value for the current selection on the x-axis. To generate the lines I am using d3.line() (which has the connection between the x and y values). This is how I currently calculate the maxY value:
//Find max and min values in data to set the domain of the y-axis
var maxArray = updatedData.map(function(variable){
//First map the values in the array to a new array
var valuesArray = variable.values.map(function(d){
return d.value;
})
//Find max value in array
return Math.max(...valuesArray);
});
var maxY = Math.max(...maxArray);
And here is where I set the scales and create the d3.line():
var xScale = d3.scaleTime()
.range([0, chartWidth]);
var yScale = d3.scaleLinear()
.domain([0, maxY])
.range([chartHeight, 0]);
var brush = d3.brushX()
.on("end", brushend);
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) {return xScale(d.date)})
.y(function(d) {return yScale(d.value)})
//Save this to be able to zoom back out
var originalDomain = [new Date(data[0].Timestamp), new Date(data[data.length-1].Timestamp)];
xScale.domain(originalDomain);
Here is the code where I set the new xScale.domain() and zoom in on that interval (which is called when the brushing is ended):
function brushend(){
//sourceEvent - the underlying input event, such as mousemove or touchmove.
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var brushInterval = d3.event.selection; //The interval of the current brushed selection
//If the function is called with no selection: ignore
if(!brushInterval) return;
//Enable reset button
resetButton.attr("disabled", null)
.on("click", resetAxis);
var newDomain = brushInterval.map(xScale.invert, xScale);
//TODO: Find max and min values in data to set the domain of the y-axis
xScale.domain(newDomain);
chart.selectAll(".line")
.transition()
.duration(1000)
.attr("d", function(d){ return line(d.values)});
chart.select(".x-axis")
.transition()
.duration(1000)
.call(xAxis);
//Remove the visual brush
d3.select(".brush").call(brush.move, null);
}
What I would like to do is to find the maximum y-value in the currently selected domain. I know that I can filter the data values to remove the ones that are not in the currently selected domain and then calculate the maximum value from them (like I did for the original domain). But it seems like there should be an easier solution to this. I didn't find any function in the documentation for d3.line() that could calculate max values.
Is there any easy way to calculate max value from d3.line()?
Thanks
There is not really an easier solution to this as you somehow have to filter the values to only take into account the ones which are in your selected x domain. However, using two nested calls to d3.max() you can at least give it a pleasant look and spare some iterations by avoiding an additional call to .filter(). Since d3.max() will ignore null values you can use it to filter your values by returning null if the current datum is outside of the x domain's boundaries. To get the maximum value you can use something like the following:
const maxY = xDomain => d3.max(updatedData, variable =>
d3.max(
variable.values,
v => v.Timestamp >= xDomain[0] && v.Timestamp <= xDomain[1] ? v.value : null
)
);
Have a look at the following snippet for a working demo:
var updatedData = [{
values: [{Timestamp:0, value:1},{Timestamp:1, value:5},{Timestamp:2, value:10},{Timestamp:3, value:3},{Timestamp:4, value:30}]
}, {
values: [{Timestamp:0, value:19},{Timestamp:1, value:12},{Timestamp:2, value:13},{Timestamp:3, value:8},{Timestamp:4, value:50}]
}];
const maxY = xDomain => d3.max(updatedData, variable =>
d3.max(
variable.values,
v => (!xDomain || v.Timestamp >= xDomain[0] && v.Timestamp <= xDomain[1]) ? v.value : null
)
);
console.log(maxY()); // Default, check all values: max 50
console.log(maxY([1,3])); // Max 13
console.log(maxY([0,3])); // Max 19
<script src="https://d3js.org/d3.v4.js"></script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With