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>
This example has reasonable utility for your desired outcome. The changes to your original based on this are:
d3.zoomto a variable rather than call it when creating thesvggattached to thesvgto a separate variable and then attachlinksContainerandnodesContainerto thatgzoomreference acts on thisgand notsvg.You can then utilise the code from the Observable in the click handler (which has a signature of
function(event, d)):The 'reset' is caused because a the use of
svg.attr("transform", ..some translation..)does not update the zoom so the moment you pan/ zoom again after setting this transform it resets to the last point that you had with the normalzoom.The click handler logic invokes
zoom.transformand sets the zoom at 1.2 (which you can adjust) and translates relative to thexandyof the clicked node.If you use the signature of
function(event, d)in theclickhandler you can reference the coordinates ofdmore easily than getting to them throughevent.