Wednesday 29 March 2023

D3 force graph - zoom to node

I am trying to establish a 'clickToZoom' function. On node click the view should focus the clicked node. The event.transform object returns {k, x, y}. So far I thought I can receive those values from the clicked node and set the svg.attr("transform", "newValues"), which I do. Obvously it does not work like expected.

The view does change but seems to reset.

 var width = window.innerWidth,
            height = window.innerHeight;

        var svg = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height)
            .call(d3.zoom().on("zoom", function (event) {
                svg.attr("transform", event.transform)
                console.log(event.transform)
            }))
            .append("g")

        ////////////////////////
        // outer force layout

        var data = {
            "nodes": [{
                "id": "A",
            },
            {
                "id": "B",
            },
            {
                "id": "C",
            },
            {
                "id": "D",
            },
            {
                "id": "E",
            },
            {
                "id": "F",
            },
            {
                "id": "G",
            },],
            "links": [{
                "source": "A",
                "target": "B"
            },
            {
                "source": "B",
                "target": "C"
            },
            {
                "source": "C",
                "target": "D"
            },
            {
                "source": "D",
                "target": "E"
            },
            {
                "source": "E",
                "target": "F"
            },
            {
                "source": "F",
                "target": "G"
            },]
        }

        var simulation = d3.forceSimulation()
            .force("size", d3.forceCenter(width / 2, height / 2))
            .force("charge", d3.forceManyBody().strength(-5000))
            .force("link", d3.forceLink().id(function (d) {
                return d.id
            }).distance(250))

        linksContainer = svg.append("g").attr("class", "linkscontainer")
        nodesContainer = svg.append("g").attr("class", "nodesContainer")


        links = linksContainer.selectAll(".linkPath")
            .data(data.links)
            .enter()
            .append("path")
            .attr("class", "linkPath")
            .attr("stroke", "red")
            .attr("fill", "transparent")
            .attr("stroke-width", 3)


        nodes = nodesContainer.selectAll(".nodes")
            .data(data.nodes, function (d) {
                return d.id;
            })
            .enter()
            .append("g")
            .attr("class", "nodes")
            .call(d3.drag()
                .on("start", dragStarted)
                .on("drag", dragged)
                .on("end", dragEnded)
            )
            .on("click", function(d) {

                //d3.zoomTransform(d3.select(this))
                
                let nodeX = d.srcElement.__data__.x
                let nodeY = d.srcElement.__data__.y

                zoomToNode(nodeX, nodeY)                
            })

        nodes.selectAll("circle")
            .data(d => [d])
            .enter()
            .append("circle")
            .attr("class", "circle")
            .style("stroke", "blue")
            .attr("r", 40)

        simulation
            .nodes(data.nodes)
            .on("tick", tick)

        simulation
            .force("link")
            .links(data.links)

        function tick() {
            links.attr("d", function (d) {
                var dx = (d.target.x - d.source.x),
                    dy = (d.target.y - d.source.y),
                    dr = Math.sqrt(dx * dx + dy * dy)

                return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
            })

            nodes
                .attr("transform", d => `translate(${d.x}, ${d.y})`);
        }

        function dragStarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }

        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }

        function dragEnded(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }

        function zoomToNode(thisX, thisY) {
            let transformValue = {"k": 2, "x": thisX, "y": thisY}

            console.log(transformValue)

            svg.attr("transform", transformValue)

            svg.attr("transform", event.transform)
        }
    .link {
        stroke: #000;
        stroke-width: 1.5px;
    }

    .nodes {
        fill: whitesmoke;
        cursor: pointer;
    }

    .buttons {
        margin: 0 1em 0 0;
    }
<!DOCTYPE html>
<html lang="de">

<head>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
    <meta charset="utf-8">

    <!-- jQuery -->
    <script src="https://code.jquery.com/jquery-3.6.3.js"></script>
    <!-- D3 -->
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <!-- fontawesome stylesheet https://fontawesome.com/ -->
    <script src="https://kit.fontawesome.com/98a5e27706.js" crossorigin="anonymous"></script>
</head>


<body>

</body>

</html>


from D3 force graph - zoom to node

No comments:

Post a Comment