Reputation: 23312
I've created a simple D3 Force Layout graph. Please check it out in the JSFiddle here.
The graph is very basic - it features cities as nodes connected to nodes representing the ocuntry they are in. For simplicity, I've made only six nodes.
I've created a function called deleteNodeOnClick()
and set it on the nodes
like this
var nodeEnter = node.enter()
.append('g')
.attr('class', 'node')
.on("click", deleteNodeOnClick)
When you click on a node in the graph, that node gets removed from the data (actually for simplicity the first node gets removed from the data for now) however it does not get removed from the visual graph. You can look in the console and see that it is in fact removed from the data.
Why not? I am completely stumped.
var data = {
nodes: [{
name: "Canada"
}, {
name: "Montreal"
}, {
name: "Toronto"
}, {
name: "USA"
}, {
name: "New York"
}, {
name: "Los Angeles"
}],
links: [{
source: 0,
target: 1
}, {
source: 0,
target: 2
}, {
source: 3,
target: 4
}, {
source: 3,
target: 5
}, ]
};
var node;
var link;
var force;
var width = 400,
height = 400;
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
force = d3.layout.force()
.size([width, length])
.nodes(data.nodes)
.links(data.links)
.gravity(.1)
.alpha(0.01)
.charge(-400)
.friction(0.5)
.linkDistance(100)
.on('tick', forceLayoutTick);
var link = svg.selectAll(".link")
.data(data.links);
var linkEnter = link.enter()
.append('line')
.attr('class', 'link');
link.exit().remove();
node = svg.selectAll('.node')
.data(data.nodes, function(d){
return d.name;
});
node.exit().remove();
var nodeEnter = node.enter()
.append('g')
.attr('class', 'node')
.on("click", deleteNodeOnClick)
//.attr('r', 8)
//.attr('cx', function(d, i){ return (i+1)*(width/4); })
//.attr('cy', function(d, i){ return height/2; })
.call(force.drag);
nodeEnter
.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 10)
.style("fill", "purple");
nodeEnter
.append("text")
.text(function(d) { return d.name })
.attr("class", "label")
.attr("dx", 0)
.attr("dy", ".35em");
force.start();
function forceLayoutTick(){
node.attr("transform", function(d) {
// Keep in bounding box
d.x = Math.max(10, Math.min(width - 10, d.x));
d.y = Math.max(10, Math.min(height - 10, d.y));
return "translate(" + d.x + "," + d.y + ")";
});
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; });
};
function deleteNodeOnClick(d){
var dataBefore = JSON.parse(JSON.stringify(data.nodes));
// Just delete the first node, for demonstration purposes
data.nodes.splice(0, 1);
console.info("Node should be removed", dataBefore, data.nodes);
}
#graph {
width: 100%;
height: 100%;
}
#graph svg {
background-color: #CCC;
}
.link {
stroke-width: 2px;
stroke: black;
}
.node {
background-color: darkslategray;
stroke: #138;
width: 10px;
height: 10px;
stroke-width: 1.5px;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.label {
display: block;
}
Upvotes: 0
Views: 1681
Reputation: 102174
In D3, changing the data doesn't automagically change the SVG (or canvas, or HTML...) elements. You have to "repaint" your dataviz.
The good news is that you have (almost) all the selections. So, just to show you the general idea, I put all the rendering code inside a draw
function, which is called on click:
function deleteNodeOnClick(d){
data.nodes = data.nodes.filter(function(e){
return e.name !== d.name;
});
draw();
}
Check the demo:
var data = {
nodes: [{
name: "Canada"
}, {
name: "Montreal"
}, {
name: "Toronto"
}, {
name: "USA"
}, {
name: "New York"
}, {
name: "Los Angeles"
}],
links: [{
source: 0,
target: 1
}, {
source: 0,
target: 2
}, {
source: 3,
target: 4
}, {
source: 3,
target: 5
}, ]
};
var node;
var link;
var force;
var width = 400,
height = 400;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
draw();
function draw() {
force = d3.layout.force()
.size([width, height])
.nodes(data.nodes)
.links(data.links)
.alpha(0.01)
.charge(-400)
.friction(0.5)
.linkDistance(100)
.on('tick', forceLayoutTick);
var link = svg.selectAll(".link")
.data(data.links);
var linkEnter = link.enter()
.append('line')
.attr('class', 'link');
link.exit().remove();
node = svg.selectAll('.node')
.data(data.nodes, function(d) {
return d.name;
});
node.exit().remove();
var nodeEnter = node.enter()
.append('g')
.attr('class', 'node')
.on("click", deleteNodeOnClick)
//.attr('r', 8)
//.attr('cx', function(d, i){ return (i+1)*(width/4); })
//.attr('cy', function(d, i){ return height/2; })
.call(force.drag);
nodeEnter
.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 10)
.style("fill", "purple");
nodeEnter
.append("text")
.text(function(d) {
return d.name
})
.attr("class", "label")
.attr("dx", 0)
.attr("dy", ".35em");
force.start();
function forceLayoutTick() {
node.attr("transform", function(d) {
// Keep in bounding box
d.x = Math.max(10, Math.min(width - 10, d.x));
d.y = Math.max(10, Math.min(height - 10, d.y));
return "translate(" + d.x + "," + d.y + ")";
});
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;
});
};
};
function deleteNodeOnClick(d) {
data.nodes = data.nodes.filter(function(e) {
return e.name !== d.name;
});
draw();
}
#graph svg {
background-color: #CCC;
}
.link {
stroke-width: 2px;
stroke: black;
}
.node {
background-color: darkslategray;
stroke: #138;
width: 10px;
height: 10px;
stroke-width: 1.5px;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.label {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body></body>
Of course, as I said, that is just to give you the general idea: for instance, the click doesn't remove the links. But now you know how to put it to work.
Upvotes: 3
Reputation: 34168
Your function does not do anything to the graph: (just the data)
function deleteNodeOnClick(d){
var dataBefore = JSON.parse(JSON.stringify(data.nodes));
// Just delete the first node, for demonstration purposes
data.nodes.splice(0, 1);
console.info("Node should be removed", dataBefore, data.nodes);
}
re-render or remove from the graph...
Example, just to remove (specific) "dot"
function deleteNodeOnClick(d) {
var theElement = node.filter(function(da, i) {
if (i === d.index) {
console.log(da);
return true;
}
})
console.dir(theElement);
theElement.remove();
var dataBefore = JSON.parse(JSON.stringify(data.nodes));
// Just delete the first node, for demonstration purposes
data.nodes.splice(d.index, 1);
console.info("Node should be removed", dataBefore, data.nodes);
}
Note that does NOT remove the "line" connecting the OTHER node, I will leave that exercise up to you to do.
More compact version without all the logging etc.
function deleteNodeOnClick(d) {
d3.select(node[0][d.index]).remove();
data.nodes.splice(d.index, 1);
}
(added for OTHERS visiting this site) For d3 version 4 this would be
function deleteNodeOnClick(d) {
d3.select(node._groups[0][d.index]).remove();
data.nodes.splice(d.index, 1);
}
Upvotes: 0