T. ksaps
T. ksaps

Reputation: 87

Change a color of a certain line in d3.js

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:

enter image description here

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: enter image description here

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

Answers (2)

Andrew Reid
Andrew Reid

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

codeepic
codeepic

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

Related Questions