Reputation: 87
I have implemented a network with d3.js in javascript and I would like to change a color of a link from grey to red according to a json received. The JSON shows the source and the target node that will be connected. The schema of the nodes architecture is the one below:
Now, when the JSON is received, I parse the data and I append a new line connecting the source and the target node to red with the code below:
svg.selectAll()
.data(data.links)
.enter()
.append("line")
.attr("x1", function(d) { return nodes[source].x; })
.attr("y1", function(d) { return nodes[source].y; })
.attr("x2", function(d) { return nodes[target].x; })
.attr("y2", function(d) { return nodes[target].y; })
.attr("stroke","#b72b34")
.attr("stroke-width", 2)
Then, after 2secs I use the same code to change again the color of the line to grey. Is there another more easier way to do it?
The result that I have now is:
As you can see the user is able to see the lines added in the past, though this is not the wanted image. I want only to change the color of the existing links (as shown in schema 1).
Thank you in advance.
My code:
var nodes = [
{ x: width/2, y: height/4, id: 0, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: 60, y: height-150, id: 1, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: width-100, y: height-150, id: 2, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: 60, y: height-50, id: 3, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: width-100, y: height-50, id: 4, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Inkjet-Printer-icon.png"}
];
var links = [
{ source: 1, target: 2 },
{ source: 1, target: 3 },
{ source: 1, target: 4 },
{ source: 1, target: 0 },
{ source: 2, target: 3 },
{ source: 2, target: 1 },
{ source: 2, target: 4 },
{ source: 2, target: 0 },
{ source: 3, target: 4 },
{ source: 3, target: 1 },
{ source: 3, target: 2 },
{ source: 3, target: 0 },
{ source: 4, target: 1 },
{ source: 4, target: 2 },
{ source: 4, target: 3 },
{ source: 4, target: 0 },
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 0, target: 3 },
{ source: 0, target: 4 },
];
svg.selectAll()
.data(links)
.enter()
.append("line")
.attr("x1", function(d) { return nodes[d.source].x; })
.attr("y1", function(d) { return nodes[d.source].y; })
.attr("x2", function(d) { return nodes[d.target].x; })
.attr("y2", function(d) { return nodes[d.target].y; })
.attr("stroke-width", function (d) { return Math.sqrt(d.value); })
.attr("stroke","#f6f6f6")
svg.selectAll()
.data(nodes)
.enter()
.append("image")
.attr("x", function(d) { return d.x - 30/2; })
.attr("y", function(d) { return d.y - 30/2; })
.attr("width", 45)
.attr("height", 45)
.attr("xlink:href",function(d) { return d.url; })
setInterval(function(test){
var url = "http://..."
d3.json(url, function(error, data) {
for (var i = 0; i < data.links.length; i++) {
source = findNode(data.links[i].source);
target = findNode(data.links[i].target);
svg.selectAll()
.data(data.links)
.enter()
.append("line")
.attr("x1", function(d) { return nodes[source].x; })
.attr("y1", function(d) { return nodes[source].y; })
.attr("x2", function(d) { return nodes[target].x; })
.attr("y2", function(d) { return nodes[target].y; })
.attr("stroke","#b72b34")
.attr("stroke-width", function (d) { return Math.sqrt(d.value);
})
;
}
});
}, 5000);
Upvotes: 3
Views: 2886
Reputation: 38181
You are currently appending new lines each time you update the graph:
svg.selectAll()
.data(data.links)
.enter()
.append("line")
svg.selectAll()
is an empty selection, the same as svg.selectAll(null)
. So an item is created in the DOM for every item in the data array ever update. This will slow things down after a while.
We could select circles with svg.selectAll("circle")
in order to update (or exit/enter old/new lines) but the new data array will be assigned to existing lines in order of their index, not whether the data pairs. We can do a key for each element with .selectAll().data()
(the second argument for .data()
), but this must be a string.
Rather than use a key, I'll propose using we could give each line an id (when initially appending the lines) representing its source and target:
.attr("id", function(d) { return "link-"+d.source+"-"+d.target; })
Then when we process the json data for updating the graph we select the proper lines. Once selected we apply the style. Using the delay on a transition we can ensure the transition back to normal is completed after two seconds:
newData.links.forEach(function(d) {
d3.select("#link-"+d.source+"-"+d.target)
.attr("stroke","red")
.transition()
.delay(1500)
.duration(500)
.attr("stroke","#f6f6f6")
})
This portion of code fits in your timeout function:
var width = 600;
var height = 400;
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var nodes = [
{ x: width/2, y: height/4, id: 0, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: 60, y: height-150, id: 1, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: width-100, y: height-150, id: 2, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: 60, y: height-50, id: 3, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: width-100, y: height-50, id: 4, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Inkjet-Printer-icon.png"}
];
var links = [
{ source: 1, target: 2 },
{ source: 1, target: 3 },
{ source: 1, target: 4 },
{ source: 1, target: 0 },
{ source: 2, target: 3 },
{ source: 2, target: 1 },
{ source: 2, target: 4 },
{ source: 2, target: 0 },
{ source: 3, target: 4 },
{ source: 3, target: 1 },
{ source: 3, target: 2 },
{ source: 3, target: 0 },
{ source: 4, target: 1 },
{ source: 4, target: 2 },
{ source: 4, target: 3 },
{ source: 4, target: 0 },
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 0, target: 3 },
{ source: 0, target: 4 },
];
svg.selectAll()
.data(links)
.enter()
.append("line")
.attr("x1", function(d) { return nodes[d.source].x; })
.attr("y1", function(d) { return nodes[d.source].y; })
.attr("x2", function(d) { return nodes[d.target].x; })
.attr("y2", function(d) { return nodes[d.target].y; })
.attr("stroke-width", function (d) { return Math.sqrt(d.value); })
.attr("id", function(d) { return "link-"+d.source+"-"+d.target; })
.attr("stroke","#f6f6f6")
svg.selectAll()
.data(nodes)
.enter()
.append("image")
.attr("x", function(d) { return d.x - 45/2; })
.attr("y", function(d) { return d.y - 45/2; })
.attr("width", 45)
.attr("height", 45)
.attr("xlink:href",function(d) { return d.url; })
setInterval(function(){
// randomly links instead of calling external file:
var data = links.filter(function(d) { if (Math.random() < 0.5) return d; })
data.forEach(function(d) {
d3.select("#link-"+d.source+"-"+d.target)
.attr("stroke","red")
.transition()
.delay(1500)
.duration(500)
.attr("stroke","#f6f6f6")
})
}, 3000); // sped up for demonstration
<script src="https://d3js.org/d3.v3.min.js"></script>
For the sake of comparison, here's a key provided to .data() as an alternative method. For it we convert the target and source into a string much like we did for the id, but apply it as the key in .data(). This allows us to skip the for each loop. It's a more canonical d3 approach:
var width = 600;
var height = 400;
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var nodes = [
{ x: width/2, y: height/4, id: 0, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: 60, y: height-150, id: 1, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: width-100, y: height-150, id: 2, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: 60, y: height-50, id: 3, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Computer-icon.png"},
{ x: width-100, y: height-50, id: 4, url: "http://icons.iconarchive.com/icons/icons-land/vista-hardware-devices/128/Inkjet-Printer-icon.png"}
];
var links = [
{ source: 1, target: 2 },
{ source: 1, target: 3 },
{ source: 1, target: 4 },
{ source: 1, target: 0 },
{ source: 2, target: 3 },
{ source: 2, target: 1 },
{ source: 2, target: 4 },
{ source: 2, target: 0 },
{ source: 3, target: 4 },
{ source: 3, target: 1 },
{ source: 3, target: 2 },
{ source: 3, target: 0 },
{ source: 4, target: 1 },
{ source: 4, target: 2 },
{ source: 4, target: 3 },
{ source: 4, target: 0 },
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 0, target: 3 },
{ source: 0, target: 4 },
];
svg.selectAll()
.data(links, function(d) { return d.source+"-"+d.target })
.enter()
.append("line")
.attr("x1", function(d) { return nodes[d.source].x; })
.attr("y1", function(d) { return nodes[d.source].y; })
.attr("x2", function(d) { return nodes[d.target].x; })
.attr("y2", function(d) { return nodes[d.target].y; })
.attr("stroke-width", function (d) { return Math.sqrt(d.value); })
.attr("stroke","#f6f6f6")
svg.selectAll()
.data(nodes)
.enter()
.append("image")
.attr("x", function(d) { return d.x - 45/2; })
.attr("y", function(d) { return d.y - 45/2; })
.attr("width", 45)
.attr("height", 45)
.attr("xlink:href",function(d) { return d.url; })
setInterval(function(){
// randomly links instead of calling external file:
var data = links.filter(function(d) { if (Math.random() < 0.5) return d; })
d3.selectAll("line")
.data(data, function(d) { return d.source+"-"+d.target; })
.attr("stroke","red")
.transition()
.delay(1500)
.duration(500)
.attr("stroke","#f6f6f6")
}, 3000); // sped up for demonstration
<script src="https://d3js.org/d3.v3.min.js"></script>
Upvotes: 2
Reputation: 4102
You can cache the line selection in a variable to reuse it in your timeout. Have a look at the code below. I also added an optional 300ms transition.
const redLine = svg.selectAll()
.data(data.links)
.enter()
.append("line")
.attr("x1", function(d) { return nodes[source].x; })
.attr("y1", function(d) { return nodes[source].y; })
.attr("x2", function(d) { return nodes[target].x; })
.attr("y2", function(d) { return nodes[target].y; })
.attr("stroke","#b72b34")
.attr("stroke-width", 2)
// drop this code inside setTimeout() instead of the whole block of code from
// above that you repeat
redLine
.transition(300)
.attr("stroke","grey")
.attr("stroke-width", 1)
Upvotes: 0