I looked at this for a while, and it worked for me, and one thing I cannot understand, but you may be able to, as you seem to be more familiar with d3 than me. In your programmaticZoom() function, I noticed that tx is the offset for the beginning of the start of the graph, and k is the scale. Using this, I changed the block:
var k = width / (x2px - x1px); // get scale var tx = 0 - k * x1px; // get tx var t = d3.zoomIdentity.translate(tx).scale(k);
to
var k = 1; var t = d3.zoomIdentity.scale(k);
Firstly, when k = 1.0 , the graph will fit perfectly in the window. The reason I believe that setting k to its previous value was incorrect is because when you increase the value of k , so that k > 1.0 , it stretches the width of the graph behind the screen. The content on the graph should be adjusted so that it can take up as much space on the graph as possible, but still within the screen. With k < 1 , what happens with width / (x2px - x1px); , the graph is reduced to screen size. Such a setting will only make the contents of the graph suitable for making maximum use of it in the graph, but since the graph is smaller than the screen, it will be adjusted to fit the smaller graph and appear smaller than the screen.
I completely got rid of tx because it offsets the beginning of the chart. When the graph is enlarged, the width will be stretched across the screen. It makes sense to compensate for this, because you need your offset to be equal to where you want to start viewing the chart, and allow parts that you don't need to leave on the screen. In the case when you zoom in, the graph is the size of the screen, so shifting will cause your graph to not start at the beginning of the screen and instead pop out part of the screen. In your case, when k already shortening the graph, the offset causes a truncated graph to appear in the middle of the screen. However, if the graph has not been shortened, the offset will push part of the graph away from the screen.
By changing this, a zoom out button appears, but the problem still remains. In the zoomed() function, you set var t = d3.event.transform; . d3.event.transform contains the values k, x, and y , which should be 1, 0, and 0 respectively, with a complete decrease. I cannot change these values โโin the programmaticZoom() function, though, since d3.event.transform exists only after the event has been fired, namely the mouse wheel. If you can get these values k = 1, x = 0, y = 0 only when you click the zoom button, the problem should be fully fixed.
Hope this helps some, I am not very familiar with d3, but this should solve most of your problem and hopefully give you an idea of โโwhat is going wrong.