Reputation: 341
I am trying to understand the join()
function, in D3v6. Obvously I misunderstand the official D3 join() documentation. https://github.com/d3/d3-selection/blob/v2.0.0/README.md#selection_join, because I thought I can use join() instead of the typical append(), enter(), remove(), exit() pattern and save tons of code.
The code below creates 3 nodes at the beginning, with a context menu. I can add and remove data call the initialize()
function again to redraw the nodes. The problem is, that the "old nodes" are still there. Instead of adding only 1 node, the whole node groups are redrawn.
What did I miss?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3 JOIN Test</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<script>
var nodes = [
{
"id": 1,
},
{
"id": 2,
},
{
"id": 3,
}
]
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
d = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
initialze()
function initialze() {
node = nodesContainer.selectAll(".node")
.data(nodes, d => d.id)
.join("circle")
.attr("r", 30)
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
.on("contextmenu", contextMenu)
simulation
.nodes(nodes)
.on("tick", ticked);
}
function mouseEnter(event, d) {
thisNode = d
d3.select(this).style("fill", "lightblue")
}
function mouseLeave(d) {
d3.select(this).style("fill", "whitesmoke")
}
function contextMenu(event, d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function () {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNodeClicked)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function addNodeClicked() {
addNode(thisNode)
}
function addNode(d) {
var newID = Math.floor(Math.random() * 100000)
nodes.push({ "id": newID, })
initialze()
}
function removeNodeClicked() {
removeNode(thisNode)
}
function removeNode(d) {
var indexOfNodes = nodes.indexOf(d)
nodes.splice(indexOfNodes, 1)
initialze()
}
function ticked() {
node
.attr("transform", function (d) {
return "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 = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
Upvotes: 2
Views: 274
Reputation: 102188
You're selecting by a class that you never set. Therefore, just do:
.attr("class", "node")
Here is your code with that change:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3 JOIN Test</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<script>
var nodes = [
{
"id": 1,
},
{
"id": 2,
},
{
"id": 3,
}
]
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
d = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
initialze()
function initialze() {
node = nodesContainer.selectAll(".node")
.data(nodes, d => d.id)
.join("circle")
.attr("r", 30)
.attr("class", "node")
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
.on("contextmenu", contextMenu)
simulation
.nodes(nodes)
.on("tick", ticked);
}
function mouseEnter(event, d) {
thisNode = d
d3.select(this).style("fill", "lightblue")
}
function mouseLeave(d) {
d3.select(this).style("fill", "whitesmoke")
}
function contextMenu(event, d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function () {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNodeClicked)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function addNodeClicked() {
addNode(thisNode)
}
function addNode(d) {
var newID = Math.floor(Math.random() * 100000)
nodes.push({ "id": newID, })
initialze()
}
function removeNodeClicked() {
removeNode(thisNode)
}
function removeNode(d) {
var indexOfNodes = nodes.indexOf(d)
nodes.splice(indexOfNodes, 1)
initialze()
}
function ticked() {
node
.attr("transform", function (d) {
return "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 = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
Upvotes: 1