Since D3v4 its possible to define .forceCenter
or .forceX
and .forceY
, instead of .size
in D3v3.
var force = d3.forceSimulation()
.force("center", d3.forceCenter(width/2, heigth/2))
or
var force = d3.forceSimulation()
.force("x", d3.forceX(500))
.force("y", d3.forceY(500))
So it is possible to set coords, then it must be also possible to use the x and y value of another DOM element as an reference. A proper use case would be to visually combine d3 force graphs. The question would be how to use an DOM element as a reference, as it seems rocket science to get those DOM element.x and element.y values.
- .getBBox() returns wrong coords,
- d3.transform was removed by API,
- .attr("x") returns null
The goal is to get the x and y position from outerNode A and use those values for .force("x") and .force("y"). Those values for sure can be updated in the tick function with the help of an own function but for now I am struggling to even get those coords.
var innerLayout = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(50))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX(870)) //## <--- DOM element.x reference
.force("y", d3.forceY(370)) //## <--- DOM element.y reference
.force("collision", d3.forceCollide().radius(6))
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3v6 Pack</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<style>
body {
background-color: #e6e7ee;
}
circle {
fill: whitesmoke;
stroke: black;
stroke-width: 1px;
}
</style>
<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.attr("class", "svg")
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")
var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")
////////////////////////
// outer force layout
var outerData = {
"nodes": [
{ "id": "A" },
{ "id": "B" },
],
"links": [
{ "source": "B", "target": "A" },
]
}
var outerLayout = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
.force("charge", d3.forceManyBody().strength(-650))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(50))
var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerData.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)
var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerData.nodes, function (d) { return d.id; })
.enter()
.append("g")
.attr("class", "outer")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
outerNodes
.append("circle")
.attr("r", 40)
outerNodes.selectAll("text")
.data(d => [d])
.join("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("id", function(d) { return "text" + d.id })
.text(function (d) {
return d.id
})
outerLayout
.nodes(outerData.nodes)
.on("tick", outerTick)
outerLayout
.force("link")
.links(outerData.links)
////////////////////////
// inner force layouts
var innerAdata = {
"nodes": [
{ "id": "B1" },
{ "id": "B2" },
{ "id": "B3" },
],
"links": [
{ "source": "B1", "target": "B2" },
{ "source": "B2", "target": "B3" },
{ "source": "B3", "target": "B1" }
]
}
var innerLayout = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(50))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX(250)) //## <--- DOM element.x reference
.force("y", d3.forceY(250)) //## <--- DOM element.y reference
.force("collision", d3.forceCollide().radius(6))
var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerAdata.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.5)
var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerAdata.nodes, function (d) { return d.id; })
.enter()
.append("g")
.attr("class", "inner")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
innerNodes
.append("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6);
innerLayout
.nodes(innerAdata.nodes)
.on("tick", innerAtick)
innerLayout
.force("link")
.links(innerAdata.links)
function outerTick() {
outerLinks
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function innerAtick() {
innerLinks
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
innerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function dragStarted(event, d) {
if (!event.active)
outerLayout.alphaTarget(0.3).restart();
innerLayout.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)
outerLayout.alphaTarget(0);
innerLayout.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>