2

I am trying to create a dynamic hierarchical edge bundling based on M Bostock's (http://bl.ocks.org/mbostock/7607999) implementation:

enter image description here

The bl.ocks version of my data set is here: http://bl.ocks.org/ratnakarv/91ace0b5f77fff5ef0ab

Unlike the original, where a relation between Node 1 and Node 2 is one way (i.e either Node 1 can import Node 2 or other way around), in my data set, Node 1 and Node 2 can have two way import relationship. This happens way too often in business process flows, which is where my application of this is going to be.

  • Current challenge:

In this example- 'Assess' imports 'Create QOS degradation Report' and 'Create QOS...' imports 'Assess' as well. But when mouse is over, 'Assess', node - 'Create..' is shown in red, but the line is green. When mouse is over node- 'Create...', the import line as well as 'Assess' is shown red.

  • Desired Requirement:

My requirement is that if a two way import exists, then either - 1. the line as well as other node be shown in a third colour (other than red or green) or 2. the line as well as other node be shown as red.

Any pointers to this would help. I have a basic knowledge of D3, but I am not a data visualization expert and merely try using visualization in my field of work for better communication.

VividD
  • 10,456
  • 6
  • 64
  • 111
Ratnakar Vellanki
  • 219
  • 1
  • 2
  • 13

1 Answers1

3

There are a few ways to approach this, but this is the easiest one I can think of as of now.

Note that there are two paths between Assess and Create QoS Degradation Report in your bl.ock with equal but opposite values (as they are both a source and a target for each other). One of them is laid exactly on top the other, giving them the appearance of a single path. That's why the link between these two nodes is somewhat jagged, while the others are smooth. Perhaps we can take advantage of this.

If you adjust the definitions for link--source and link--target in your CSS to have an opacity value of less than 1, a partially transparent source link will overlap a partially transparent target link, giving the appearance of a "new", different color.

As for the node color, create a new CSS class node--both with the "new" color, and apply it to the node if both n.source and n.target are true in the mousovered function.

node
    .classed("node--both", function(n) { return n.source && n.target; })
    .classed("node--target", function(n) { return n.target; })
    .classed("node--source", function(n) { return n.source; });

It's not perfect, but here's a fiddle demonstrating this: http://jsfiddle.net/w2rfwokx/

The key is to choose the source and target link colors and opacity values appropriately (which I didn't do) so that you get a new color different enough from the source and target links and also the same color regardless of whether the source or the target link is "on top". In the current fiddle version, you can see that the colors slightly differ depending on which node is active. This thread or something similar might help.

You could also try manipulating the links array to merge the two identical paths into one and adding an attribute to indicate that this is a source-and-target link and use this attribute later while processing.

Update: You have the right idea in your comment. The color trick was more of a hack anyway.

The links array contains two items for the one path between the two-way import nodes. Let's remove one of them and also set an attribute in the other to indicate that this is a two-way import.

var unique_links = links.reduce(function(p,c) {
    var index=p.map(function(d,i) { if(d.source===c.target && d.target===c.source) return i;}).shift();
    if(!isNaN(index)) p[index].both=true; else p.push(c);
    return p;
 },[]);

Now unique_links has only one element per edge and that one element has both=true. Let's also pass the both attribute along to the bundle layout.

link = link
      .data(bundle(unique_links))
    .enter().append("path")
      .each(function(d) {
        d.source = d[0], 
        d.target = d[d.length - 1],
        d.both = unique_links.filter(function(v) { if (v.source===d.source && v.target===d.target) return v.both; }).shift();
      })
      .attr("class", "link")
      .attr("d", line);

The final step is to change the mouseovered function to set a new CSS class with a different color using both:

function mouseovered(d) {
  node
      .each(function(n) { n.target = n.source = false; });

  link
      .classed("link--both", function(l) { if((l.target===d || l.source===d) && l.both) return l.source.source = l.source.target = l.target.source = l.target.target = true;})
      .classed("link--target", function(l) { if (l.target === d && !l.both) return l.source.source = true; })
      .classed("link--source", function(l) { if (l.source === d && !l.both) return l.target.target = true; })
    .filter(function(l) { return l.target === d || l.source === d; })
      .each(function() { this.parentNode.appendChild(this); });

  node
      .classed("node--both", function(n) { return n.target && n.source; })
      .classed("node--target", function(n) { return n.target; })
      .classed("node--source", function(n) { return n.source; });
}

And reset the classes in mouseouted:

function mouseouted(d) {
  link
      .classed("link--both", false)
      .classed("link--target", false)
      .classed("link--source", false);

  node
      .classed("node--both", false)
      .classed("node--target", false)
      .classed("node--source", false);
}

Remember to define the new classes in CSS:

.link--both {
  stroke: orange;
}

.node--both {
  fill: orange;
}

Here's an updated fiddle with the complete code: http://jsfiddle.net/w2rfwokx/1/

Community
  • 1
  • 1
OrionMelt
  • 2,531
  • 18
  • 17
  • Thanks a lot OrionMelt. I tried making use of colour blending. Though it is quite clever and the node colour is taken care of, link colour is still a challenge. there is a contrast in colour from one way import only, the colours displayed are different when node 1 and node 2 are moused over (in this case node 1 and node 2 have a 2 way import). I tried combinations of several colours and opacity, but this seems to persist. I am trying to explore your other idea- playing with the links array. Any pointers/advice on where to target will be helpful? Thanks again – Ratnakar Vellanki Oct 06 '14 at 15:37
  • My understanding is this piece of code where link array is constructed is where change needs to be done. Currently if Node 1 and Node 2 have a 2 way link, then two array items are created in this array(permutation). What would probably need to be done is: after the array is constructed, check for redundancies, delete one of the duplicates and tag the other to say it is a two way link. This tag could be used while colouring. Please let know if this is the way to do it. In which case can you please help me with the code part. – Ratnakar Vellanki Oct 06 '14 at 19:19
  • It is a little puzzling, that when I was trying to operate with a bigger and different data set, the same code that worked superbly in [link](http://jsfiddle.net/w2rfwokx/1/) has a small issue. The fiddle with bigger and different data set is here [link](http://jsfiddle.net/w2rfwokx/3/). While everything else works fine, the 2 way link is not orange (as it should be). Differential diagnosis might point to data set, but data set in json format looks fine.... any pointers will be helpful – Ratnakar Vellanki Dec 01 '14 at 16:42
  • Looks like there was a bug in how we construct the unique_links array. The incorrect assumption was that the map function will always contain the matched index as the first element and shift would return this first element. However, if the first element happens to be undefined, shift would return undefined. Filtering the array for undefined before shifting fixes this issue. See updated fiddle here: http://jsfiddle.net/w2rfwokx/4/ – OrionMelt Dec 04 '14 at 07:00