I've been at this for a few days now and I can't get this chart to obey panning bounds. Initially, the data could be pulled off the page both negatively and positively, but I've been able to stop the negative by following this blog post. I'll paste what I believe is the relevant code here, but the file is way to long to include.
The chart in question is an elevation chart made up of concatenated area objects that are colored according to their gradient.
There's a commented out line that is the one giving trouble. I've put question marks in place of what is supposed to be a max bound. For some reason, I can't find the max bound of the data area.
Here's a Plunkr
// Set up the size of the chart relative to the div
var x = d3.scale.linear().range([0, (width-80)]);
var y = d3.scale.linear().range([height, 0]);
var y1 = d3.scale.linear().range([height, 0]);
// Define the look of the axis
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5);
var yAxisRight = d3.svg.axis().scale(y1).orient("left").ticks(5);
// Areas are segments of the chart filled with color
var area = d3.svg.area()
.x(function(d) { return x(d.distance); })
.y0(height)
.y1(function(d) { return y(d.elevation); });
// Functions for handling zoom events
var gradientZoomListener = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", gradientZoomHandler);
function gradientZoomHandler() {
var t = gradientZoomListener.translate(),
tx = t[0],
ty = t[1];
tx = Math.min(tx, 0);
// tx = Math.max(tx, ??);
gradientZoomListener.translate([tx,ty])
gradientChart.select(".x.axis").call(xAxis);
gradientChart.select(".y.axis").call(yAxis);
gradientChart.selectAll('.area').attr('d', area);
}
The most intuitive way to do it in this case is to check what the scale maps the bounds of the input domain to, rather than checking the pixel values. The idea is that the lower bound of the domain should map to 0 (the lower bound of the output range) or less, and the upper bound to the upper bound of the output range or more.
If the lower bound maps to less than 0, the value is to the left of the graph, i.e. the lowest shown value is more than the bound. If it is larger than 0, there must be a gap between the y axis and the first value. Similarly for the upper bound, if it maps to less than the upper bound of the output range, there must be a gap between it and the end of the graph.
In code, this looks as follows.
The only non-trivial thing is the adjusting of the translation value if there's a gap between the largest value and the end of the graph. The size of this gap is the difference between where the largest input value is projected to and the largest output value. The gap is then subtracted from the current translation value to close it.
Complete example here. Note that I've moved some code around to get access to the values which are only known when the data has been read.
It works the same way for the y axis.