Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert d3v6 Observable example using zoom.center

Tags:

I have a project where I'm trying to zoom to specific points. In d3v3, I could do that with this function

function zoomTo(place, pscale) {
  
  projection = d3.geo.mercator()
        .scale((1 << pscale) / 2 / Math.PI)
        .translate([width / 2, height / 2])
    
  center = projection(place);

return zoom
  .scale(projection.scale() * 2 * Math.PI)
  .translate([width - center[0], height - center[1]]);

}

Zoom has changed, though, and this no longer works in d3v6.

I found an example on Observable that does exactly what I want using d3v6: https://observablehq.com/d/9035ed7049aaa8c6

However, I'm not sure how to translate it to vanilla javascript. Below is my attempt. I'm getting this error:

d3.zoom(...).scaleExtent(...).on(...).center is not a function

It runs fine in Observable, though, so clearly I'm missing something. Any help would be greatly appreciated! (Also, I don't need the buttons that are at the top of the Observable example, so I commented those out.)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Zoom to Point</title>
    <script src="https://files-7y4qaclbi.vercel.app/d3-zoom.js"></script>
    <script src="https://d3js.org/d3.v6.min.js"></script>

</head>

<body>
    <script>

        // const form = html`<form>
        //   <button name="in" type="button">Zoom In</button>
        //   <button name="out" type="button">Zoom Out</button>
        //   <button name="random" type="button">Random</button>
        //   <button name="reset" type="button">Reset</button>
        // </form>`;
        //   form.in.onclick = () => chart.zoomIn();
        //   form.out.onclick = () => chart.zoomOut();
        //   form.random.onclick = () => chart.zoomRandom();
        //   form.reset.onclick = () => chart.zoomReset();
        //   return form;

        const radius = 6

        const step = radius * 5

        const width = 500
        const height = 500

        const theta = Math.PI * (3 - Math.sqrt(5))

        const data = Array.from({ length: 1000 }, (_, 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)
            ];
        })

        const quadtree = d3.quadtree()
            .addAll(data)

        // let mutable debug = undefined;

        let focussed;

        function findCloser(that, coords) {
            const p = d3.zoomTransform(that).invert(coords);
            focussed = quadtree.find(p[0], p[1]);
            return d3.zoomTransform(that).apply(focussed);
        }

        const zoom = d3
            .zoom()
            .scaleExtent([1, 40])
            .on("zoom", zoomed)
            .center(function (event, d) {
                // mutable debug = { event, d, r: Math.random() };
                let coords = d3.pointer(event, this);
                focussed = null;

                if (event.deltaY < 0) {
                    coords = findCloser(this, coords);
                }

                g.selectAll("circle").attr("r", p =>
                    p == focussed ? radius + 3 : radius
                );

                return coords;
            });

        const svg = d3.select('body').append("svg")
            .attr('width', width)
            .attr('height', height)
            .on("click", reset);

        const g = svg.append("g");

        g.selectAll("circle")
            .data(data)
            .join("circle")
            .attr("cx", ([x]) => x)
            .attr("cy", ([, y]) => y)
            .attr("r", radius)
            .attr("fill", (d, i) => d3.interpolateRainbow(i / 360))
            .on("click", clicked);

        svg.datum("I'm your worst datum").call(zoom); //.on("mousemove", mousemove);

        function random() {
            const [x, y] = data[Math.floor(Math.random() * data.length)];
            svg
                .transition()
                .duration(2500)
                .call(
                    zoom.transform,
                    d3.zoomIdentity
                        .translate(width / 2, height / 2)
                        .scale(40)
                        .translate(-x, -y)
                );
        }

        function reset() {
            svg
                .transition()
                .duration(750)
                .call(
                    zoom.transform,
                    d3.zoomIdentity,
                    d3.zoomTransform(svg.node()).invert([width / 2, height / 2])
                );
        }

        function clicked(event, [x, y]) {
            event.stopPropagation();
            svg
                .transition()
                .duration(750)
                .call(
                    zoom.transform,
                    d3.zoomIdentity
                        .translate(width / 2, height / 2)
                        .scale(40)
                        .translate(-x, -y),
                    d3.pointer(event, svg.node())
                );
        }

        function zoomed(event) {
            g.attr("transform", event.transform);
        }

        // return Object.assign(svg.node(), {
        //     zoomIn: () =>
        //         svg
        //             .transition()
        //             .call(zoom.scaleBy, 2, findCloser(svg.node(), [width / 2, height / 2])),
        //     zoomOut: () => svg.transition().call(zoom.scaleBy, 0.5),
        //     zoomRandom: random,
        //     zoomReset: reset
        // });
    </script>

</body>

</html>

Here it is in a Plunker, as well, if that's helpful: https://plnkr.co/edit/JaiKhTgEXzIFWiar?open=lib%2Fscript.js

like image 287
sprucegoose Avatar asked Jul 26 '21 13:07

sprucegoose


1 Answers

There is no zoom.center in any d3-zoom release, from the first one to the current (v3.0.0 at the time of writing).

That zoom.center method was created by that Observable's author, which actively contributes to D3 (see here). You can see the proposal here: https://github.com/d3/d3-zoom/pull/212

However, if you inspect the code the author is using, you'll see that adding your own center method is not complicated. All you need is:

zoom.center = function(_) {
    return arguments.length ? (center = typeof _ === "function" ? _ : constant([+_[0], +_[1]]), zoom) : center;
  };

...and...

function defaultCenter(event) {
  return d3Selection.pointer(event, this);
}

With a couple of center.apply(this, arguments) that you can find by having a look at that file.

like image 74
Gerardo Furtado Avatar answered Sep 30 '22 09:09

Gerardo Furtado