Search code examples
javascriptd3.jszooming

Type error after making standalone d3v5 zoom with tooltip code from Mike Bostocks oberservablehq example


I'm trying to create a single html+js file with the code from the download from Mike's awesome https://observablehq.com/@d3/zoom-with-tooltip The code below displays the data and mouseover works but fails on attempts to zoom with :

index3.html:70 Uncaught TypeError: Cannot destructure property 'transform' of 'undefined' as it is undefined.
at SVGSVGElement.zoomed (index3.html:70)
at H.apply (d3.v5.min.js:2)
at kt (d3.v5.min.js:2)
at w.emit (d3.v5.min.js:2)
at w.zoom (d3.v5.min.js:2)
at d3.v5.min.js:2
at d3.v5.min.js:2

<!DOCTYPE html>
<meta charset="utf-8">
<title>Zoom with Tooltip from https://observablehq.com/@d3/zoom-with-tooltip</title>

<body>
  <svg width="960" height="350"></svg>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
  var radius = 6;
  var theta = Math.PI * (3 - Math.sqrt(5));
  var step = 12;
  var width = 800;
  var height = 350;

  function data() { //(radius,step,theta,width,height){
    return (
      Array.from({
        length: 2000
      }, (_, i) => {
        const radius = step * Math.sqrt(i += 0.5),
          a = theta * i;
        return [
          width / 2 + radius * Math.cos(a),
          height / 2 + radius * Math.sin(a)
        ];
      })
    )
  };

  // Replaced by select below
  // const svg = d3.create("svg")
  //        .attr("viewBox", [0, 0, width, height]);

  const svg = d3.select('svg');

  // all code below unchanged

  const g = svg.append("g")
    .attr("class", "circles");

  g.append("style").text(`
        .circles {
          stroke: transparent;
          stroke-width: 1.5px;
        }
        .circles circle:hover {
          stroke: black;
        }
    `);

  g.selectAll("circle")
    .data(data)
    .join("circle")
    .datum(([x, y], i) => [x, y, i])
    .attr("cx", ([x]) => x)
    .attr("cy", ([, y]) => y)
    .attr("r", radius)
    .attr("fill", ([, , i]) => d3.interpolateRainbow(i / 360))
    .on("mousedown", mousedowned)
    .append("title")
    .text((d, i) => `circle ${i}`);

  svg.call(d3.zoom()
    .extent([
      [0, 0],
      [width, height]
    ])
    .scaleExtent([1, 8])
    .on("zoom", zoomed));

  function mousedowned(event, [, , i]) {
    d3.select(this).transition()
      .attr("fill", "black")
      .attr("r", radius * 2)
      .transition()
      .attr("fill", d3.interpolateRainbow(i / 360))
      .attr("r", radius);
  }

  function zoomed({
    transform
  }) {
    g.attr("transform", transform);
  }
</script>

I just can't see the problem, any help appreciated.


Solution

  • I think you were confused that d3.event would be the first argument passed to an event handler, while instead - at least for d3 v5 - it's a global variable you can access. Changing the signatures of zoomed and mousedowned fixed it for me.

    <!DOCTYPE html>
    <meta charset="utf-8">
    <title>Zoom with Tooltip from https://observablehq.com/@d3/zoom-with-tooltip</title>
    
    <body>
      <svg width="960" height="350"></svg>
    </body>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script>
      var radius = 6;
      var theta = Math.PI * (3 - Math.sqrt(5));
      var step = 12;
      var width = 800;
      var height = 350;
    
      function data() { //(radius,step,theta,width,height){
        return (
          Array.from({
            length: 2000
          }, (_, i) => {
            const radius = step * Math.sqrt(i += 0.5),
              a = theta * i;
            return [
              width / 2 + radius * Math.cos(a),
              height / 2 + radius * Math.sin(a)
            ];
          })
        )
      };
    
      // Replaced by select below
      // const svg = d3.create("svg")
      //        .attr("viewBox", [0, 0, width, height]);
    
      const svg = d3.select('svg');
    
      // all code below unchanged
    
      const g = svg.append("g")
        .attr("class", "circles");
    
      g.append("style").text(`
            .circles {
              stroke: transparent;
              stroke-width: 1.5px;
            }
            .circles circle:hover {
              stroke: black;
            }
        `);
    
      g.selectAll("circle")
        .data(data)
        .join("circle")
        .datum(([x, y], i) => [x, y, i])
        .attr("cx", ([x]) => x)
        .attr("cy", ([, y]) => y)
        .attr("r", radius)
        .attr("fill", ([, , i]) => d3.interpolateRainbow(i / 360))
        .on("mousedown", mousedowned)
        .append("title")
        .text((d, i) => `circle ${i}`);
    
      svg.call(d3.zoom()
        .extent([
          [0, 0],
          [width, height]
        ])
        .scaleExtent([1, 8])
        .on("zoom", zoomed));
    
      function mousedowned([, , i]) {
        d3.select(this).transition()
          .attr("fill", "black")
          .attr("r", radius * 2)
          .transition()
          .attr("fill", d3.interpolateRainbow(i / 360))
          .attr("r", radius);
      }
    
      function zoomed() {
        g.attr("transform", d3.event.transform);
      }
    </script>