Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.js keep zoomable y-axis from going below zero

I have a graph with zoom features. My main observation was that the x-axis updated its scale based on my current zoom level. I wanted the y-axis to do this too, so enabled zoom.y(y) , the undesired side affect being that now the user can zoom out in all directions, even into negative values "below" the graph.

http://jsfiddle.net/ericps/xJ3Ke/5/

var zoom = d3.behavior.zoom().scaleExtent([0.2, 5]) .on("zoom", draw); doesn't seem to really take the y-axis into account. And the user can still drag the chart anywhere in any direction to infinity.

One idea I thought of was independent of having zoom.y(y) enabled, and simply requires redrawing the y-axis based on what it is in the currently visible range. Like some kind of redraw based on the position of the X axis only. I don't want up and down scrolling at all now, only left and right

aside from commenting out //zoom.y(y) how would this be done? Insight appreciated.

like image 877
CQM Avatar asked Dec 16 '22 13:12

CQM


1 Answers

All you need to do is update the y scale domain in your draw method.

The zoom function will modify the associated scales and set their domain to simulate a zoom. So you can get your x visible data bounds by doing x.invert(0) and x.invert(width), for example. If you converted your data to use Date's instead of strings then this is what I would suggest you use to filter, it woudl probably be more efficient.

As it is though, you can still use the x scale to filter to your visible data, find the y-axis extents of those values, and set your y scales domain to match accordingly. And in fact you can do all this in just a few lines (in your zoom update callback):

var yExtent = d3.extent(data.filter(function(d) { 
    var dt = x(d.date);
    return dt > 0 && dt < width;
}), function(d) { return d.value; });
y.domain(yExtent).nice(); 

You can try it out here

To better explain what is going on:

The zoom behaviour listens to mouse events and modifies the range of the associated scales.

The scales are used by the axes which draw them as lines with ticks, and the scales are also used by the data associated with your paths and areas as you've set them up in callbacks.

So when the zoom changes it fires a callback and the basic method is what you had:

svg.select("g.x.axis").call(xAxis);
svg.select("g.y.axis").call(yAxis);
svg.select("path.area").attr("d", area);
svg.select("path.line").attr("d", line);

we redraw the x- and y- axes with the newly updated domains and we redraw (recompute) the area and the line - also with the newly domained x- and y- scales.

So to get the behaviour you wanted we take away the default zoom behaviour on the y scale and instead we will modify the y scales domain ourselves whenever we get a zoom or pan: conveniently we already have a callback for those actions because of the zoom behaviour.

The first step to compute our y scale's domain is to figure out which data values are visible. The x axis has been configured to output to a range of 0 to width and the zoom behaviour has updated the x scale's domain so that only a subset of the original domain outputs to this range. So we use the javascript array's filter method to pull out only those data objects whose mapping puts them in our visible range:

data.filter(function(d) { 
    var dt = x(d.date);
    return dt > 0 && dt < width;
}

Then we use the handy d3 extent method to return the min and max values in an array. But because our array is all objects we need an accessor function so that the extents method has some numbers to actually compare (this is a common pattern in D3)

d3.extents(filteredData, function(d) { return d.value; });

So now we know the min and max values for all the data points that are drawn given our current x scale. The last bit is then just to set the y scale's domain and continue as normal!

y.domain(yExtent).nice(); 

The nice method I found in the api because it's the kind of thing you want a scale to do and d3 often does things for you that you want to do.

A great tutorial for figuring out some of this stuff is: http://alignedleft.com/tutorials/ It is worth stepping through even the parts you think you know already.

like image 137
Superboggly Avatar answered Jan 20 '23 05:01

Superboggly