Zoom in D3 Line Chart

Hello,

I am trying to get Zoom to work here:

But it is not working properly. This is:

I tried several tweaks but the console log shows me error messages. Thanks

#1: Your scale function is calling bottomAxis.scale() and leftAxis.scale(). Instead, call d3.axisBottom and d3.axisLeft.

#2: Your scale function is changing all path elements. The axis lines are paths, so get scaled when they shouldnt be. Instead, assign an ID to your chart-line:

            svg.append("path")
            .datum(dataset1) 
            .attr("id","pointline")

and update the zooming function to only update that one line.

This code for the zoomCallback works (mostly):

                var zoomCallback = function(){
                   d3.selectAll('circle').attr("transform", d3.event.transform);
                   d3.select('#pointline').attr("transform", d3.event.transform);
                    d3.select('#x-axis')
                        .call(d3.axisBottom(d3.event.transform.rescaleX(xScale)));
                    d3.select('#y-axis')
                        .call(d3.axisLeft(d3.event.transform.rescaleX(yScale)));
                }

The only thing it doesnt do is hide things that overlap the axis, but thats because its not redrawing the circles and line, it’s just transforming it.

That was great, thanks.

Here is my updated Codepen:

I included path at first so it zooms the circles and the line of the graph.

The only wrong things is that the graph is moving when zoomed.

Panning does not work anymore, how come?

zoom that moves inside of a certain amount of zoom will try to move towards the mouse.

Works for me?

How do I correct the zoom please? So that it goes back to its original position which is 0, 0.

Normally, the short answer to this is svg.call(zoom.transform, d3.zoomIdentity); but you’ve got some… uniqueness to this graph that throws things off if you do that, so in your case, svg.call(zoom.transform, d3.zoomIdentity.translate(100,100)) will do the trick. Note that this does not make the pan/zoom forget where it was, so if you zoom, reset, and zoom again, it will pick up the pan/zoom as if you’d never reset.

Thanks, so I include that in both zoom functions, the normal one and reset?

No, this doesn’t work:

                let zoom = d3.zoom()
           	    .scaleExtent([0.5, 3])
                .extent([[0, 0], [width, height]])
                .translateExtent([[0, 0], [width, height]])
	            .on('zoom', zoomCallback);

                d3.select('svg').call(zoom.transform, d3.zoomIdentity.translate(100,100));

                function resetZoom() {
                	d3.select('svg')
		            .transition()
		            .call(zoom.scaleTo, 1);
}

What is unique?

Hmm. For some reason the axes arent rescaling themselves properly.

 function resetZoom() {
    d3.select('svg')
      .transition()
      .call(zoom.transform, d3.zoomIdentity.translate(100,100))
     .on("end", () => {
              axBot.call(d3.axisBottom(xScale));
              axLft.call(d3.axisLeft(yScale));
     });
}

Works, but its clunky… hrm.

This resets properly but I don’t know why it changes when I zoom normally.

Looks like I have to build that into the normal zoom function as well. But that doesn’t work cause there is no “end”.

Do you know why this “clunky” code has to be implemented?

I’m going to be honest and say not a flippin clue. Even weirder is when i try and separate the axes code from the rest of the elements, so that the transitions can all happen at the same time:

function resetZoom() {
    d3.select('circle')
      .transition()
      .call(zoom.transform, d3.zoomIdentity.translate(100,100));
    d3.select('#pointline')
      .transition()
      .call(zoom.transform, d3.zoomIdentity.translate(100,100));
     axBot.transition().call(d3.axisBottom(xScale));
     axLft.transition().call(d3.axisLeft(yScale));
}

Suddenly the Y axis decides to become a logarithmic scale looking thing? Yeah I got no idea there.

EDIT: It’s got something to do with the zoom function executing in the middle of the transition. Still playing around with it to see if i can figure out how to undo it…

Right. So here’s how i fixed it.

We need to go back to how you’re zooming in the first place, and do it the d3 way.

var zoomCallback = function(){
    var newX = d3.event.transform.rescaleX(xScale);
    var newY = d3.event.transform.rescaleY(yScale);
 
    axBot.call(d3.axisBottom(newX));
    axLft.call(d3.axisLeft(newY));
 
    d3.selectAll('circle')
      .attr("cx", (d) => { return newX(d[0]); })
      .attr("cy", (d) => { return newY(d[1]); });
 
    d3.selectAll('#pointline')
      .attr("d",
         d3.line()
           .x(function(d) { return newX(d[0]); }) 
           .y(function(d) { return newY(d[1]); }) 
           .curve(d3.curveMonotoneX)
      );
}

If you set that as your actual zoom function, then the standard reset will work:

 function resetZoom() {
    svg
      .transition()
      .call(zoom.transform, d3.zoomIdentity);
}

(Note: This version of the zoom will retain the radius of the circle at its original size. If, for whatever reason,its desired to have the inflated circles, @Dvdscot will need to fiddle with the circles radius, probably multiplying it by the zoom event’s scale factor.)

1 Like

(My version of the above)

1 Like

Thanks a lot, I have seen this return values before:

                // update circle position
                kreis
                    .selectAll("circle")
                    .attr('cx', function(d) {return newX(d[0])})
                    .attr('cy', function(d) {return newY(d[1])});

(d) => …
is the same as
function(d) …
?
Cause you used both expressions.

Why does it have to be like this and in other examples the simpler solution worked?

Yes, (d) => is a shorthand (called “big arrow notation” or “arrow function expressions”) for function (d) {. There’s a bit of nuance there regarding what the function can/cant do in certain circumstances (it can also shorthand the return bit for simple functions), but thats the thrust of it. (d) => d.thing; === function (d) { return d.thing; }

The general answer i’m going to give is because when you fiddle with the scope of the graph in one metric, functions designed to operate without the fiddling get screwy.

I’m sure d3 purists would say “because you should do things the d3 way”. Which is… just their way of saying “because it works.”

1 Like

Received new tasks, like adding more graphs, new axes, tooltips and a button which switches the axis from numbers to percentages.

First thing though is to hide the graph when it goes beyond the axes while I’m zooming.