how to update specific node in D3 - d3.js

I'm living updating nodes and links. I also want to change the radius of existing nodes(circles). how can i update eg a specific circle?
When i do this below it updates all circles.
node.select("circle").attr("r", circleradius);
when i update the node array nothing changes in the viz.
nodes[index].size = 2000;
this is how the outputted html looks like
this is the function update code:
function update() {
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update links.
link = link.data(links, function(d) { return d.target.id; });
link.exit().remove();
link.enter().insert("line", ".node")
.attr("class", "link");
// Update nodes.
node = node.data(nodes, function(d) { return d.id; });
node.exit().remove();
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.on("click", click)
.call(force.drag);
nodeEnter.append("circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 2 || 24.5; });
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
node.select("circle")
.style("fill", color);
}

Child elements are not automatically updated until you use d3.select. It's a bit of a hard thing to wrap your head around which I won't get into here.
But for practical purposes, what you need to do is, after you've created your force-directed layout and elements and such, if you want to change a data value used for size element 0 and see it update, the code needs to read like this:
nodes[0].size = 2000;
d3.selectAll("g").select("circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 2 || 24.5; });

Related

D3 Problems with merge and adding labels to siblings

I was trying to add labels to circles that are constantly moving in D3. One of the ways I found was to insert the label and the circle to a g tag. However, this causes errors whenever I want to EXIT the elements and merge them (which is not happening right now).
Is there any other way to do it? My graph right now seems to be with lower frames per second because it is not using the merge to update from the current x/y position to the new one.
The code is as follow:
var t = d3.transition()
.duration(0);
// JOIN new data with old elements.
var circles = g.append("g").selectAll('g')
.data(data.values)
.enter()
.append('g')
// EXIT elements
d3.selectAll("circle").transition()
.attr("class", "exit")
.remove();
// EXIT elements
d3.selectAll(".label")
.attr("class", "exit")
.remove();
// ENTER new elements present in new data.
// Circles
circles.append("circle")
.attr("class", "enter")
.attr("fill", function (d) { return color(d.country); })
.attr("cy", function (d) { return y(d.active_cases); })
.attr("cx", function (d) { return x(d.total_deaths) })
.merge(circles)
.transition(t)
.attr("r", 15)
// Labels
circles.append("text")
.attr("class", "label")
.attr("font-size", 10)
.style("text-anchor", "middle")
.style("fill", "white")
.attr("x", function (d) { return x(d.total_deaths) })
.attr("y", function (d) { return 3 + y(d.active_cases) })
.merge(circles)
.text(function (d) { return d.country })

d3js update redraw stacked bar graph

I am trying to make a stacked bar graph through d3js and have it update when new data is passed through an update function. I call this update function to initially call the graph and it works fine. However, when I change the data and call it again, it erases all the "rect" elements from the graph (When I console log the data, it appears to be passing through). How can I make the graph be redrawn appropriately? I have tried experimenting with the .remove() statement at the beginning, but without it the data doesn't pass through when the bars are redrawn.
function update(my_data) {
svg.selectAll(".year").remove();
var year = svg.selectAll(".year")
.data(my_data)
.enter().append("g")
.attr("class", "year")
.attr("transform", function(d) { return "translate(" + x0(d.Year) + ",0)"; });
var bar = year.selectAll(".bar")
.data( function(d){ return d.locations; });
bar
.enter().append("rect")
.attr("class", "bar")
.attr("width", x0.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
}
update(data);
It's hard to tell exactly what you're doing cause your question doesn't include the data or the DOM. It would help if you included a link to a work-in-progress jsFiddle or something.
If I had to guess what's going wrong, it looks like you're doing a nested join where each year gets bound to a g element and then each location gets bound to a rect inside each g element.
The issue is likely you are only specifying the enter behavior, but not the update behavior or the exit behavior. As a result, when you try to redraw, nothing updates and nothing exits - but new data elements will get added.
It would seem that is why you have to add the selectAll().remove() to get anything to redraw. By removing everything, all the data elements will trigger the enter condition and get added again.
Take a look at these tutorials to better understand how the enter/update/exit pattern works and how nested joins work.
General Update Pattern: https://bl.ocks.org/mbostock/3808218
Nested Selections: https://bost.ocks.org/mike/nest/
Also, here is a jsFiddle I wrote some time ago to demonstrate how to use nested selections and the general update pattern together:
https://jsfiddle.net/reblace/bWp8L/
var series = svg.selectAll("g.row").data(data, function(d) { return d.key; });
/*
* This section handles the "enter" for each row
*/
// Adding a g element to wrap the svg elements of each row
var seriesEnter = series.enter().append("g");
seriesEnter
.attr("class", "row")
.attr("transform", function(d, i){
return "translate(" + margin.left + "," + (margin.top + (span*i)) + ")";
})
.attr("opacity", 0).transition().duration(200).attr("opacity", 1);
// Adding a text label for each series
seriesEnter.append("text")
.style("text-anchor", "end")
.attr("x", -6)
.attr("y", boxMargin + (boxDim/2))
.attr("dy", ".32em")
.text(function(d){ return d.key; });
// nested selection for the rects associated with each row
var seriesEnterRect = seriesEnter.selectAll("rect").data(function(d){ return d.values; });
// rect enter. don't need to worry about updates/exit when a row is added
seriesEnterRect.enter().append("rect")
.attr("fill", function(d){ return colorScale(d)})
.attr("x", function(d, i){ return i*span + boxMargin; })
.attr("y", boxMargin)
.attr("height", boxDim)
.attr("width", boxDim);
/*
* This section handles updates to each row
*/
var seriesUpdateRect = series.selectAll("rect").data(function(d){ return d.values});
// rect update (Will handle updates after enter)
// rect enter
seriesUpdateRect.enter().append("rect")
.attr("x", function(d, i){ return i*span + boxMargin; })
.attr("y", boxMargin)
.attr("height", boxDim)
.attr("width", boxDim);
// rect enter + update
seriesUpdateRect
.attr("fill", function(d){ return colorScale(d)});
// Exit
seriesUpdateRect.exit();
/*
* This section handles row exit
*/
series.exit()
.attr("opacity", 1)
.transition().duration(200).attr("opacity", 0)
.remove();

How to partially update a graph after adding element?

I draw a graph from a set of nodes and links using the following update function with nodes and links arrays defined earlier in the code.
function update() {
var link = svg.selectAll(".link")
.data(links);
link.enter().insert("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes);
node.enter().append("svg:g")
.attr("class", "node")
.attr("id", function(d) { return d.name })
.on("dblclick", dblclick)
.on("mouseover", function(d) {drawTags(tags, d.name);})
.on("mouseout", function(d) {
d3.select("#"+d.name).selectAll(".tool")
.transition()
.delay(800)
.remove();
})
.call(force.drag);
node.append("circle")
.attr("class", "circle")
.attr("r", 40);
// display name in nodes if node structure
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
force.on("tick", function() {
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; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
force.start();
}
On dynamic update of the graph, after double click event on a node, I fetch additional nodes and links and display them. So far, I was able to append nodes and links to their corresponding arrays, hence update drawing. However, the update function would loop through all elements for the display.
Therefore I would like to pass newNodes and newEdges to the update function and only draw new elements. The issue is that new elements replace existing ones in the svg code rather than to be appended.
Is there a way to append new elements to previously existing ones in the drawing?
Full code is available on fiddle (https://jsfiddle.net/pducrot/4eyb81kx/) mouse over a node and click on any appearing tag to see what's happening.

How to prevent a mouseout after an click in D3?

I am working on developing a force directed graph in D3. Right now when I mouseover nodes it changes the opacity of the connected links from 0 to 1 and then when I mouseout it returns the opacity back to 0.
That works fine but what I am having trouble with is making a click on the node maintain the opacity of the links at 1 even after the mouseout event. Then I want to be able to click on other nodes to make their links opacity 1 as well. Then also be able to click on the some of the previously clicked nodes to be able to return the opacity of their associated links to 0.
In short, I want to be able to toggle the opacity of the associated links of a node with out it being affect by mouseout events. A sample of my current code is below. I am thinking I might have to set a new id to toggle on and off when I click on a node?
var nodeClick = function(d) {
svg.selectAll(".link")
.filter(function(p) {
return _(d.facets).contains(p.target.name)
})
.transition()
.style('stroke-opacity', 0.9);
};
var overText1 = function(d) {
svg.selectAll(".link")
.filter(function(p) {
return _(d.facets).contains(p.target.name)
})
.transition()
.style('stroke-opacity', 0.9);
};
var overText0 = function(d) {
svg.selectAll(".link")
.transition()
.duration(500)
.style('stroke-opacity', 0);
};
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", function (d) {
return d.group === 1 ? "nodeBig" : "node";
})
.attr("r", function(d) {return d.radius })
.style("fill", function (d) {
return color(d.group);
})
.on("mouseover", overText1)
.on('click', nodeClick)
.on('mouseout', overText0)
.call(force.drag);
I actually finally figured this out on my own. I created a lock field that accepts either a "true" or "false". Then I put an if statement in the mouseoff function that only enables mouseoff functionality on elements that don't have "true" in their lock field.

D3.js: How to make force directed graph faster

I've been trying to get my force directed graph to go faster/smoother, and it seems commenting this part out of the "tick" function does the trick. Of course, it also makes all the edges disappear although the nodes still move together as if attached by invisible threads.
I have about 2-3 hundred nodes in a network-ish graph and when setting the opacity of each element I also check it's weight and if it's 1 I remove it.I repeat this for all nodes and text labels. (and edges using d.target.weight)
Is it just a number of nodes that weighing down everything? After removing the elements down to 20 or so why is it still so slow? Do I really have to piggyback my removal onto .style("opacity", function(d){//do stuff, return 1;})?
force.on("tick", function() {
// edges.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; })
// .style("stroke", function(d){
// return d.source.color;
// });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
functions for drawing the svg elements if it helps:
//Create edges as lines
var edges = group.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", function(d,i){
if (d.target.weight === 1)
d3.select(this).remove();
return "#FFFFFF";
})
.style("opacity",0.5)
.style("stroke-width", 2);
//Create nodes as circles
var nodes = group.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.style("opacity",0.8)
.attr("r", function(d,i){
if (d.weight === 1)
d3.select(this).remove();
return nodeScale(d.weight * 2);
})
.style("fill", function(d, i) {
return d.color;
})
.call(force.drag);
var text = group.selectAll("text")
.data(dataset.nodes)
.enter()
.append("text")
.attr("fill","black")
.style("font-size",function(d){
return d.size;
})
.style("text-anchor", "middle")
.text(function(d){
return d.name;
})
.style("opacity",function(d){
if (d.weight === 1)
d3.select(this).remove();
else
return 0.8;
})
.on("mouseover",function(){
d3.select(this)
.style("opacity",1)
.style("font-size", 25);
})
.on("mouseout",function(){
d3.select(this)
.style("font-size", function(d) { return d.size; });
})
.call(force.drag);
Also the initiation function, became quite random after I fiddle around with it a lot:
(I also have a slider for each one that I play with when rendered)
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([50])
.charge([-2000])
.friction(0.5)
.gravity(0.5)
.theta(0.5)
.start();
I believe the problem was that removing an element still doesn't affect the actual dataset being used which is composed of hundred of nodes in this case.
Changing the code to removing the element from the dataset and using exit().remove() makes it go faster.

Resources