What I got:
I got a D3 forced graph, with two different link types. I do visualize those two types differently. need
as a simple line and uses
dashed. To do so I got two different CSS classes for the links and simply switch the type if clicked. To finally visualize this changes I call the main initialize()
function again.
Whats the problem:
As soon as I click on one of those links and switch the type, the outdated lines are still visible. I miss the point how to avoid such behavior? How can I make sure, that the outdated lines are gone? I appreciate any hint.
Update:
I added svg.selectAll("line").remove()
before the re-init. But I doubt its best practice, further sometimes the lines just disappear completely.
var graph = {
"nodes": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"links": [
{
"source": 1,
"target": 2,
"type": "uses"
},
{
"source": 2,
"target": 3,
"type": "needs"
},
{
"source": 3,
"target": 1,
"type": "needs"
}
]
}
var svg = d3.select("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
var force = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id
}).distance(80))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(90))
initialize()
function initialize() {
link = svg.selectAll(".link")
.data(graph.links)
.join("line")
//.attr("class", "link")
.attr("class", function (d) {
if (d.type === "uses") {
return "uses"
} else {
return "needs"
}
})
.on("dblclick", function (event, d) {
if (d.type === "uses") {
d.type = "needs"
} else if (d.type === "needs") {
d.type = "uses"
}
svg.selectAll("line").remove()
initialize()
})
node = svg.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.selectAll("circle")
.data(graph.nodes)
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
force
.nodes(graph.nodes)
.on("tick", ticked);
force
.force("link")
.links(graph.links)
}
function ticked() {
// update link positions
link
.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;
});
// update node positions
node
.attr("transform", function (d) {
return "translate(" d.x ", " d.y ")";
});
}
function dragStarted(event, d) {
if (!event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
PosX = d.x
PosY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) force.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
body {
height: 100%;
background: #e6e7ee;
overflow: hidden;
margin: 0px;
}
line {
stroke-width: 6px;
}
line.uses {
stroke: grey;
stroke-dasharray: 5;
}
line.needs {
stroke: black;
}
line:hover {
stroke: goldenrod;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<svg id="svg"></svg>
</body>
</html>
CodePudding user response:
In the segment:
link = svg.selectAll(".link")
.data(graph.links)
.join("line")
//.attr("class", "link")
.attr("class", function (d) {
if (d.type === "uses") {
return "uses"
} else {
return "needs"
}
})
The selectin is selecting the link
class, but the lines actually have uses
or needs
. You can instead select the previous uses
and needs
:
link = svg.selectAll("line.uses,line.needs")
This will make the .join()
remove the unused lines of those classes.