How to do a Zoom on a graph using D3

Hello,

I am trying to implement Zoom on this example:

I found several examples of zoom, with axes and without. Generally you need to select an area, call zoom there and this zoom is a d3.zoom which then calls another self programmed function where the graph is redrawn.

So I added this:

        let zoom = d3.zoom()
            .scaleExtent([0.5, 10])
            .extent([[0, 0], [width, height]])
            .on('zoom', NeuerChart);

        svg.append("rect")
            .attr("width", width)
            .attr("height", height)
            .style("fill", "none")
            .style("pointer-events", "all")
            .attr("transform", "translate(" + 100 + "," + 100 + ")")
            .call(zoom);

        function NeuerChart () {
            // recover the new scale
            var newX = d3.event.transform.rescaleX(xScale);
            var newY = d3.event.transform.rescaleY(yScale);

            // update axes with these new boundaries
            xScale.call(d3.axisBottom(newX));
            yScale.call(d3.axisLeft(newY));

            d3.event.transform.translate(offsetX, offsetY).scale(1);

                    // update circle position
            kreis
                .selectAll("circle")
                .attr('cx', function(d) {return newX(d.Sepal_Length)})
                .attr('cy', function(d) {return newY(d.Petal_Length)});
  }

Also found this example but nothing works so far.

Posting a link to what you have on codepen will be easier to debug.

I’ve got working examples using zoom, but mine are all using D3 in react, so I’ll have to experiment on your code to find the problem. Or, you can find many different zoom examples by looking at Observable or bl.ocks.org (lots of google results point there).

Happy to hear from you again.

I used a new book and got zoom to half work. The axes don’t rescale though. I think it has to do with them being a group element. I have two other examples where it works, one uses a #point in a , the other one works different. I cannot just use one working code for the other.

var zoomCallback = function(){
d3.select(‘#points’).attr(“transform”, d3.event.transform);
d3.select(‘#x-axis’)
.call(bottomAxis.scale(d3.event.transform.rescaleX(xScale)));
d3.select(‘#y-axis’)
.call(leftAxis.scale(d3.event.transform.rescaleX(yScale)));
}

var zoom = d3.zoom()
.on(‘zoom’, zoomCallback);

d3.select(‘svg’).call(zoom);

vs

  var zoom = d3.zoom()
      .scaleExtent([.5, 20])  // This control how much you can unzoom (x0.5) and zoom (x20)
      .extent([[0, 0], [width, height]])
      .on("zoom", updateChart);

  // This add an invisible rect on top of the chart area. This rect can recover pointer events: necessary to understand when the user zoom
  SVG.append("rect")
      .attr("width", width)
      .attr("height", height)
      .style("fill", "none")
      .style("pointer-events", "all")
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
      .call(zoom);
  // now the user can zoom and it will trigger the function called updateChart

  // A function that updates the chart when the user zoom and thus new boundaries are available
  function updateChart() {

    // recover the new scale
    var newX = d3.event.transform.rescaleX(x);
    var newY = d3.event.transform.rescaleY(y);

    // update axes with these new boundaries
    xAxis.call(d3.axisBottom(newX))
    yAxis.call(d3.axisLeft(newY))

    // update circle position
    scatter
      .selectAll("circle")
      .attr('cx', function(d) {return newX(d.Sepal_Length)})
      .attr('cy', function(d) {return newY(d.Petal_Length)});
  }

Both examples only use circles, my example uses circles and a joining line which are grouped I think.

These code snippets don’t seem complete and I’m still not sure exactly what you want to zoom, whether you mean zooming part of a visualization or the entire visualization. You really should post this on codepen (or similar) and post a link.

These examples seem fairly thorough; they just need some adaptation to your specific problem.

Like this?

When I zoom it also moves and doesn’t stay in the middle.

Should work like this:

I think you’re going to need to look at the documentation and the examples at observable to really get acquainted with zooming.

Zoom events are based on the mouse pointer position and zoom around it. So in both your examples, you only zoom the middle if the pointer is in the middle. The zoom functions generate a transform object, with members, x, y, and k, which correspond to the horizontal and vertical translation and the scale factor of the zoom. That information is then utilized in the zoomed({ transform }) function to perform the necessary transformations to the graph’s elements.

I’ve got a simple, incomplete example that shows a simple scatter plot. The zoom is only partially implemented, but you can see how the points change with zoom events and pointer location. You can uncomment the log statements to see more information in the console.

The axes do not change on zoom right now, but you can fix that by using the information in the transform object to transform the x and y scales. You would also need to fix the problem of plotting points outside the axes.

Thanks, I tried various methods and only received error messages in the console log regarding the axes.

d3.v5.min.js:2 Uncaught TypeError: t.copy is not a function
at Wb.rescaleX (d3.v5.min.js:2:187467)
at SVGSVGElement.zoomCallback (Line Chart.html:146:50)
at H.apply (d3.v5.min.js:2:7323)
at kt (d3.v5.min.js:2:11740)
at w.emit (d3.v5.min.js:2:247135)
at w.zoom (d3.v5.min.js:2:247007)
at SVGSVGElement.M (d3.v5.min.js:2:243133)
at SVGSVGElement. (d3.v5.min.js:2:10935)

I copied your code into Visual Studio Code, extended the HTML to include D3 but it doesn’t work there either.

zoomed file:///C:/Users/xxx/Documents/JavaScript/Codepen Example/JS.js:93
apply https://d3js.org/d3.v5.min.js:2
kt https://d3js.org/d3.v5.min.js:2
emit https://d3js.org/d3.v5.min.js:2
zoom https://d3js.org/d3.v5.min.js:2
transform https://d3js.org/d3.v5.min.js:2
each https://d3js.org/d3.v5.min.js:2
transform https://d3js.org/d3.v5.min.js:2
call https://d3js.org/d3.v5.min.js:2
file:///C:/Users/xxx/Documents/JavaScript/Codepen Example/JS.js:133

It only shows me the plot without the axes and zooming is not possible. What went wrong here? css.css is your css file and js.js your JS code, all in the same folder.

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Titel</title>
    <link rel="stylesheet" href="css.css">
  </head>
  <body>
    <div id="visualization">
    </div>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="JS.js"></script>    
  </body>
</html>
//            var axBot = d3.axisBottom(xScale).ticks(7).tickValues([0, 60, 120, 180, 240, 300, 360]);
//            var axLft = d3.axisLeft(yScale);
    
            // Step 6
            var axBot = g.append("g")
             .attr("transform", "translate(0," + height + ")")
             .attr('id', 'x-axis')
            // .call(axBot);
             .call(d3.axisBottom(xScale).ticks(7).tickValues([0, 60, 120, 180, 240, 300, 360]));
            
            var axLft = g.append("g")
             .attr('id', 'y-axis')
             //.call(axLft);
             .call(d3.axisLeft(yScale));
                var zoomCallback = function(){
                   d3.selectAll('circle').attr("transform", d3.event.transform);
                   d3.selectAll('path').attr("transform", d3.event.transform);

                   var newX = d3.event.transform.rescaleX(axBot);
                   var newY = d3.event.transform.rescaleY(axLft);
                   console.log(newX);
                   console.log(newY);

                    xScale.call(d3.axisBottom(d3.event.transform.rescaleX(axBot)));
                    yScale.call(d3.axisLeft(d3.event.transform.rescaleY(axLft)));

//                    xScale.call(d3.axisBottom(d3.event.transform.rescaleX(xScale)));
//                    yScale.call(d3.axisLeft(d3.event.transform.rescaleY(yScale)));
                }

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

                d3.select('svg').call(zoom);

I’ve completed the example and commented it fairly thoroughly and it runs on codepen. Part of the problem with running that code locally is you’re using D3v5 and mine uses the current version of D3 (v7.6.x or so). Regardless, the example shows the principles behind doing this correctly so I would use the example and the documentation and start small with zooming a shape and proceeding from there to zoom a plot and then start trying to rescale axes.

Yes, I changed yours to v7 and it works. I also changed mine to v7 and it doesn’t. The projects I will be working on are done in v5 and all my teaching books also use v5 so it should work in v5.

I updated my program and now the scaling works, only the axes lines are moved and the actual line chart also moves and the whole thing doesn’t go back into its original position.

            // Step 6
            var axBot = g.append("g")
             .attr("transform", "translate(0," + height + ")")
             .attr('id', 'x-axis')
            // .call(axBot);
             .call(d3.axisBottom(xScale).ticks(7).tickValues([0, 60, 120, 180, 240, 300, 360]));
            
            var axLft = g.append("g")
             .attr('id', 'y-axis')
             //.call(axLft);
             .call(d3.axisLeft(yScale));


                var zoomCallback = function(){
                   d3.selectAll('circle').attr("transform", d3.event.transform);
                   d3.selectAll('path').attr("transform", d3.event.transform);

                   var newX = d3.event.transform.rescaleX(xScale);
                   var newY = d3.event.transform.rescaleY(yScale);
                   console.log(newX);
                   console.log(newY);

                    axBot.call(d3.axisBottom(d3.event.transform.rescaleX(xScale)));
                    axLft.call(d3.axisLeft(d3.event.transform.rescaleY(yScale)));

I included an id for the axes: pointline, that way it works, only panning doesn’t work anymore and the graph is still moved.