Thursday, 14 March 2013

d3.js force directed graph example (basic)

The following post is a portion of the D3 Tips and Tricks document which is free to download. To use this post in context, consider it with the others in the blog or just download the pdf  and / or the examples from the downloads page:-)
-------------------------------------------------------

Basic force directed graph showing directionality

As explained in the previous post, the example graphs explained here are a combination of Mike Bostock's  Mobile Patent Suits graph and  Force-Directed Graph with Mouseover graph.

The data for this example graph has been altered from the data that was comprised of litigants in the mobile patent war to fictitious peoples names and associated values (to represent the strength of the links between the two). In the original examples the data was contained in the graph code. In the following example it is loaded from a csv file named force.csv. The values loaded are as follows;

source,target,value
Harry,Sally,1.2
Harry,Mario,1.3
Sarah,Alice,0.2
Eveie,Alice,0.5
Peter,Alice,1.6
Mario,Alice,0.4
James,Alice,0.6
Harry,Carol,0.7
Harry,Nicky,0.8
Bobby,Frank,0.8
Alice,Mario,0.7
Harry,Lynne,0.5
Sarah,James,1.9
Roger,James,1.1
Maddy,James,0.3
Sonny,Roger,0.5
James,Roger,1.5
Alice,Peter,1.1
Johan,Peter,1.6
Alice,Eveie,0.5
Harry,Eveie,0.1
Eveie,Harry,2.0
Henry,Mikey,0.4
Elric,Mikey,0.6
James,Sarah,1.5
Alice,Sarah,0.6
James,Maddy,0.5
Peter,Johan,0.7


The code is as follows;
<!DOCTYPE html>
<meta charset="utf-8">
<script type="text/javascript" src="d3/d3.v3.js"></script>
<style>

path.link {
  fill: none;
  stroke: #666;
  stroke-width: 1.5px;
}

circle {
  fill: #ccc;
  stroke: #fff;
  stroke-width: 1.5px;
}

text {
  fill: #000;
  font: 10px sans-serif;
  pointer-events: none;
}

</style>
<body>
<script>

// get the data
d3.csv("data/force.csv", function(error, links) {

var nodes = {};

// Compute the distinct nodes from the links.
links.forEach(function(link) {
    link.source = nodes[link.source] || 
        (nodes[link.source] = {name: link.source});
    link.target = nodes[link.target] || 
        (nodes[link.target] = {name: link.target});
    link.value = +link.value;
});

var width = 960,
    height = 500;

var force = d3.layout.force()
    .nodes(d3.values(nodes))
    .links(links)
    .size([width, height])
    .linkDistance(60)
    .charge(-300)
    .on("tick", tick)
    .start();

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

// build the arrow.
svg.append("svg:defs").selectAll("marker")
    .data(["end"])
  .enter().append("svg:marker")
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
  .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");

// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
    .data(force.links())
  .enter().append("svg:path")
    .attr("class", "link")
    .attr("marker-end", "url(#end)");

// define the nodes
var node = svg.selectAll(".node")
    .data(force.nodes())
  .enter().append("g")
    .attr("class", "node")
    .call(force.drag);

// add the nodes
node.append("circle")
    .attr("r", 5);

// add the text 
node.append("text")
    .attr("x", 12)
    .attr("dy", ".35em")
    .text(function(d) { return d.name; });

// add the curvy lines
function tick() {
    path.attr("d", function(d) {
        var dx = d.target.x - d.source.x,
            dy = d.target.y - d.source.y,
            dr = Math.sqrt(dx * dx + dy * dy);
        return "M" + 
            d.source.x + "," + 
            d.source.y + "A" + 
            dr + "," + dr + " 0 0,1 " + 
            d.target.x + "," + 
            d.target.y;
    });

    node
        .attr("transform", function(d) { 
            return "translate(" + d.x + "," + d.y + ")"; });
}

});

</script>
</body>
</html>
 In a similar process to the one we went through when highlighting the function of the Sankey diagram, where there are areas that we have covered before, I will gloss over some details on the understanding that you will have already seen them explained in an earlier section (most likely the basic line graph example).

The first block we come across is the initial html section;
<!DOCTYPE html>
<meta charset="utf-8">
<script type="text/javascript" src="d3/d3.v3.js"></script>
<style>
The only thing slightly different with this example is that we load the d3.v3.js script earlier. This has no effect on running the code.

The next section loads the Cascading Style Sheets;
path.link {
  fill: none;
  stroke: #666;
  stroke-width: 1.5px;
}

circle {
  fill: #ccc;
  stroke: #fff;
  stroke-width: 1.5px;
}

text {
  fill: #000;
  font: 10px sans-serif;
  pointer-events: none;
}
 We set styles for three elements and all the settings laid out are familiar to us from previous work.

Then we move into the JavaScript. Our first line loads our csv data file (`force.csv`) from our `data` directory.
d3.csv("data/force.csv", function(error, links) {
Then we declare an empty object (I still tend to think of these as arrays even though they're strictly not).
var nodes = {};
 This will contain our data for our nodes. We don't have any separate node information in our data file, it's just link information, so we will be populating this in the next section...
links.forEach(function(link) {
    link.source = nodes[link.source] || 
        (nodes[link.source] = {name: link.source});
    link.target = nodes[link.target] || 
        (nodes[link.target] = {name: link.target});
    link.value = +link.value;
});
 This block of code looks through all of out data from our csv file and for each link adds it as a node if it hasn't seen it before. It's quite clever how it works as it employs a neat JavaScript shorthand method using the double pipe (`||`) identifier.

So the line (expanded)...
link.source=nodes[link.source] || (nodes[link.source]={name: link.source});
 ... can be thought of as saying “*If* `link.source` *does not equal any of the `nodes` values then create a new element in the `nodes` object with the name of the `link.source` value being considered.*”. It could conceivably be written as follows (this is untested);
if (link.source != nodes[link.source]) {
    nodes[link.source] = {name: link.source}
};
 Then the block of code goes on to test the `link.target` value in the same way. Then the `value` variable is converted to a number from a string if necessary (`link.value = +link.value;`).

The next block sets the size of our svg area that we'll be using;
var width = 960,
    height = 500;
 The next section introduces the `force` function.
var force = d3.layout.force()
    .nodes(d3.values(nodes))
    .links(links)
    .size([width, height])
    .linkDistance(60)
    .charge(-300)
    .on("tick", tick)
    .start();
 Full details for this function are found on the D3 Wiki, but the following is a rough description of the individual settings.

`var force = d3.layout.force()` makes sure we're using the `force` function.

`.nodes(d3.values(nodes))` sets our layout to the array of `nodes` as returned by the function `d3.values` (https://github.com/mbostock/d3/wiki/Arrays#wiki-d3_values). Put simply, it sets the nodes to the `nodes` we have previously set in our object.

`.links(links)` does for links what `.nodes` did for nodes.

`.size([width, height])`  sets the available layout size to our predefined values. If we were using `gravity` as a force in the graph this would also set the gravitational centre. It also sets the initial random position for the elements of our graph.

`.linkDistance(60)` sets the target distance between linked nodes.  As the graph begins and moves towards a steady state, the distance between each pair of linked nodes is computed and compared to the target distance; the links are then moved towards or away from each other, so as to converge on the set distance.

Setting this value (and other force values) can be something of a balancing act. For instance, here is the result of setting the `.linkDistance` to 160.


Here the charged nodes are trying to arrange themselves at an appropriate distance, but the length of the links means that their arrangement is not very pretty. Likewise if we change the value to 30 we get the following;


Here the link distance allows for a symmetrical layout, but the distance is too short to be practical.

`.charge(-300)` sets the force between nodes. Negative values of `charge` results in node repulsion, while a positive value results in node attraction. In our example, if we vary the value to 150 we get this result;


It's not exactly easy to spot, but the graph feels a little 'lazy'. The nodes don't find their equilibrium easily or at all. Setting the value higher than 300 (for our example) keeps all the nodes nice and spread out, but where there are other separate discrete linked nodes (as there are in our example) they tend to get forced away from the center of the defined area.

`.on("tick", tick)` runs the animation of the force layout one 'step'. It's these progression of steps that give the force layout diagram it's fluid movement.

`.start();` Starts the simulation; this method must be called when the layout is first created.

The next block of our code is the standard section that sets up our svg container.
var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
 The next block of our code is used to create our arrowhead marker. I will be the first to admit that it has entered a realm of svg expertise that I do not have and the amount of extra memory power I would need to accumulate to understand it sufficiently to explain won't be occurring in the near future. Please accept my apologies and as a small token of my regret, accept the following links as an invitation to learn more: http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute and http://www.w3schools.com/svg/svg_reference.asp?.
What is useful to note here is that we define the label for our marker as `end`. We will use this in the next section to reference the marker as an object. This particular section of the code caused me some small amount of angst. The problem being when I attempted to adjust the width of the link lines in conjunction with the `value` set in the data for the link, it would also adjust the stroke-width of the arrowhead marker. Then when I attempted to adjust for the positioning of the arrow on the path, I could never get the maths right. Eventually I decided to stop struggling against it and the encode the value as line in a couple of different ways. One as opacity using discrete boundaries and the other using variable line width, but with the arrowheads a common size. We will cover both those solutions in the coming sections.
svg.append("svg:defs").selectAll("marker")
    .data(["end"])
  .enter().append("svg:marker")    
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
  .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");
 The `.data(["end"]) ` line sets our tag for a future part of the script to find this block and draw the marker.

`.attr("refX", 15)` sets the offset of the arrow from the centre of the circle. While it is designated as the `X` offset, because the object is rotating, it doesn't correspond to the x (left and right) axis of the screen. The same is true of the `.attr("refY", -1.5)` line.

The `.attr("markerWidth", 6)` and `.attr("markerHeight", 6)` lines set the bounding box for the marker. So varying these will vary the space available for the marker.

The next block of code adds in our links as paths and uses the `#end` marker to draw the arrowhead on the end of it.
var path = svg.append("svg:g").selectAll("path")
    .data(force.links())
  .enter().append("svg:path")
    .attr("class", "link")
    .attr("marker-end", "url(#end)");
 Then we define what our nodes are going to be.
var node = svg.selectAll(".node")
    .data(force.nodes())
  .enter().append("g")
    .attr("class", "node")
    .call(force.drag);
 This uses the `nodes` data and adds the `.call(force.drag);` function which allows the node to be dragged by the mouse.

The next block adds the nodes as an svg circle.
node.append("circle")
    .attr("r", 5);
And then we add the name of the node with a suitable offset.
node.append("text")
    .attr("x", 12)
    .attr("dy", ".35em")
    .text(function(d) { return d.name; });
 The last block of JavaScript is the `ticks` function. This block is responsible for updating the graph and most interestingly drawing the curvy lines between nodes.
function tick() {
    path.attr("d", function(d) {
        var dx = d.target.x – d.source.x,
            dy = d.target.y – d.source.y,
            dr = Math.sqrt(dx * dx + dy * dy);
        return "M" + 
            d.source.x + "," + 
            d.source.y + "A" + 
            dr + "," + dr + " 0 0,1 " + 
            d.target.x + "," + 
            d.target.y;
    });

    node
        .attr("transform", function(d) { 
            return "translate(" + d.x + "," + d.y + ")"; });
}
 This is another example where there are some easily recognizable parts of the code that set the x and y points for the ends of each link (`d.source.x`, `d.source.y` for the start of the curve and `d.target.x`, `d.target.y` for the end of the curve) and a transformation for the node points, but the cleverness is in the combination of the math for the radius of the curve (`dr = Math.sqrt(dx * dx + dy * dy);`) and the formatting of the svg associated with it. This is sadly beyond the scope of what I can comfortable explain, so we will have to be content with “the magic happens here”.

The end result should be a tidy graph that demonstrates nodes and directional links between them.


The code and data for this example can be found as Basic Directional Force Layout Diagram on bl.ocks.org.

The above description (and heaps of other stuff) is in the D3 Tips and Tricks book that can be accessed from the downloads page of d3noob.org (Hey! IYou can get it for free. Why not?)

57 comments:

  1. Can you suggest me a graph suitable for representation of a path from A to B which can have many possible paths. what i am looking for is a static graph (not movable).

    ReplyDelete
    Replies
    1. Hmm... It's possible that a Sankey diagram would fit the bill. http://www.d3noob.org/2013/02/sankey-diagrams-what-is-sankey-diagram.html
      http://www.d3noob.org/2013/02/sankey-diagrams-description-of-d3js-code.html
      http://www.d3noob.org/2013/02/formatting-data-for-sankey-diagrams-in.html
      It's possible to have aconsiderable amount of control over the appearance and placement of nodes and links. so it might be close. Any chance of finding a picture of what you're trying to achieve and posting a link to it? perhaps even an indication of the sort of data you're dealing with?

      Delete
  2. would it be possible to highlight(change color) of the neighboring nodes / links on mouse hover over node please?
    I am really trying to understand this, but I have only been able to understand some code from your example. but not enough to modify it to find all links between selected nodes.
    can you kindly help please?

    ReplyDelete
    Replies
    1. I wish I could, but I don't know of a solution. However, I can tell you what I would do if I needed to find out. I'd check out Chris Viau's excellent resource here (http://biovisualize.github.io/d3visualization/) and have a good search for force layout examples that might suit the bill. The next technique I'd use is to do a google search using 'force graph' and 'd3.js' or something similar. Turn on image search and see if you can see anything similar to what you're looking for. It's kind of a visual grep for images (if you're familiar with the term). I'm sorry I don't have a solution, but I hope that the steps that I would take to finding a solution work for your case. Good luck.

      Delete
  3. Noob here to all things web/JS/D3 so wanted to say THANK YOU for all your efforts in sharing your experiences. It's been very helpful.

    I'm still crawling my way through this example and got stuck at the '||' part. It appears that that section of code will modify 'links' in addition to 'nodes'?

    So, written differently (easier to me), I think it would be something like:
    ```
    if( link.source != nodes[ link.source]) {
    nodes[link.source] = {name: link.source };
    link.source = nodes[ link.source ]; // new
    }
    ```
    and another one for target?

    ReplyDelete
    Replies
    1. From memory, the '||' part should only be adding links as nodes where they don't already exist.
      I have to admit to not being sure about your modified version. It's been too long since I played with this code for me to answer off the top of my head. The best answer would be to play with the code a bit and see what turns up. I can't count the number of times I learned something new from playing with and breaking my code :-). I'd be interested to see how it went!

      Delete
    2. Yes, it must be odd answering nit picky line item questions about something from 6 months ago! I suppose my post is for others who run into the same thing.

      To answer my own question, yes, the following code:

      links.forEach( function(link) {
      link.source = nodes[link.source] ||
      (nodes[link.source] = {name: link.source});
      ...
      }
      modifies both 'links' and 'nodes'.
      The modification to 'nodes' is described in the content of this blog.
      'links' is modified as well.
      When first read in, an element in links is an object that looks like:
      source: "Harry"
      target: "Sally"
      value: "1.2"

      But, after the loop above is executed, the same element looks like:
      source: Object
      name: "Harry"
      target: Object
      name: "Sally"
      value: 1.2

      which comes from the assignment in the
      link.source = nodes[link.source]
      part of the code.

      Okay, time for me to move on to the next block of the code ...

      Delete
    3. Wow, that is really interesting and well explained! That will certainly help others understand the code in relation to the structure of the data (It helped me :-)). Thanks for sharing.

      Delete
    4. One final point (that I learned the hard way yesterday):

      link.source = nodes[link.source]

      copies a "pointer" to nodes[link.source], not just the contents. This is common knowledge I suppose if you know JavaScript, but it was entirely new to me.

      Beware, fellow noobs, this has serious implications! This means that in a later part of the code, if say 'nodes.Harry' is modified (as it will be by d3.layout.force()), then without any further coding on your part, any link in the variable 'links' to/from Harry will reflect these changes! Furthermore, the opposite is true as well: modify the source/target info of a link and the corresponding entry in 'nodes' will be changed.

      Very handy I'm sure, but treacherous, treacherous ...!

      Delete
    5. Call yourself a noob? Sounds a bit like you might be moving into 'dangerously competent' territory! :-)
      Well done. That's interesting behavior and great to share. Thanks again.

      Delete
    6. The double pipe is just the logical operator OR.
      a || b -> if a or b than true
      If the first operand is true, the second can be ignored. This often is used for better performance.

      In your code its more or less the same as:

      if (link.source in nodes) {
      link.source=nodes[link.source];
      }
      else {
      nodes[link.source]={name: link.source};
      link.source=nodes[link.source];
      }

      Delete
  4. Hello. I have four attributes source, target, link and type in csv. All the data in text format. which graph can be suited for my requirement. i tried with Hierarchical Edge Bundling (http://bl.ocks.org/mbostock/1044242). Butit did not satisfy my requirement. i couldn't be able to link with all my attributes. Please suggest any d3 graph.

    ReplyDelete
    Replies
    1. I think that force directed would be useful to try. Go to this site (http://biovisualize.github.io/d3visualization/#title=all) and search for 'force' by title. There are plenty of examples. Good luck.

      Delete
  5. Hi All,
    I want to display some information in between the source and the target nodes,(ideally in the center of the lines joining the source and the target) is there any way i can do that?
    any help will be greatly appreciated.

    Thanks,
    Ashish

    ReplyDelete
    Replies
    1. Hi there. This is a very interesting question.
      I can think of two ways that I would approach the problem.
      The first would be to somehow iterate along the path the forms the links curve until you got halfway and present your circle there. Mike Bostock has done some interesting work in this area, but you would have to understand the topic to a high level (IMHO).
      The second (and perhaps the easier of the two) would be to calculate the position arithmetically. This would involve having to understand the portion of the code that I have labelled above as "The magic happens here" but I see this as achievable with enough study.
      This IS a good question Ashish and If I was you I would ask a question on Stack Overflow to see if there were some other particularly clever answers. Remember, the secret to getting a good answer on SO is to write a good question :-). Good luck.

      Delete
    2. Thanks D3Noob, i am trying to build a graph using the above explained example, just that i need to show distance in between the nodes,so thought of asking, do u have any other graph example, wherein there's some info displayed over the edge (lines joining the source and the target) i can use that.
      I will post this on the Stack OverFlow to check if i get any luck :)
      Thanks,
      Ashish

      Delete
    3. Sorry, I don' have any other examples, but check out Chris Viau's D3 gallery here (http://christopheviau.com/d3list/gallery.html) to see if there's something close. Good luck

      Delete
    4. Hi D3Noob,
      I was able to get the text written onto the lines :)
      Please have a check over this fiddle.
      http://jsfiddle.net/ashishyete/DEeNB/

      Thank you so much for your wonderful explanation on this above example and help :)

      Thanks,
      Ashish

      Delete
    5. NICE!!! Great job Ashish The text looks good and the slider is a nice touch to show the variation in the node radius and arrow size. Awesome work.

      Delete
    6. Hi D3noob,
      One more update,
      this is actually i wanted and its working now, so thought of sharing making it helpful for the people who need :)
      The text which i am displaying is static and it wont change and it will be same for all the nodes.
      My requirement was a bit different,
      I am sending one more Parameter in the JSON array, so my JSON array looks something like

      var links = [
      {source: "Apple", target: "Nokia", type: "resolved",distance:"25"},
      {source: "Qualcomm", target: "Nokia", type: "resolved",distance:"30"},
      {source: "Apple", target: "Motorola", type: "suit",distance:"35"},
      {source: "Microsoft", target: "Motorola", type: "suit",distance:"40"},
      {source: "Motorola", target: "Microsoft", type: "suit",distance:"45"}
      ];

      I wanted to display the distance tag over the lines joining the nodes.
      The solution to this answer is here.

      http://jsfiddle.net/DEeNB/36/

      Hope it helps some one needy :)

      Thanks,
      Ashish


      Delete
    7. Thanks for sharing Ashish. That's good stuff.

      Delete
  6. Excellent!
    Is it possible load the nodes with different colors and diameters?
    Thank you!

    ReplyDelete
    Replies
    1. That would seem possible. I imagine that there is a neat and efficient way of doing it, but in the crudest sense you could include a nasty 'if...' statement that would colour the nodes dependent on the source string. Let me stress that this IS nasty and requires that you hard code the values for each node. You could make it iteratively better by using an array or an external file or ideally some clever code that made a list of all unique nodes and coloured them.You could include a separate column in your initial data the had the colour of the source node in it even. The radius of the node would be simple. Even using the value that is in the example above it will work. The only downside would be messing about with the arrowhead on the node to make it look pretty. A very interesting idea.

      Delete
    2. Thanks D3noob.
      I thought to add some columns in the csv file, i.e. :

      source,target,value,colour,radius ...

      but I'll have dig deep into the code to capture the values and I still have no idea how to do it. Have you any idea about it? I'm trying but it seem no easy!
      Please, if you find some way to manage all this, post your idea here.
      Many thanks!

      Delete
    3. Thanks D3noob!
      I thought to add some columns in the csv file, i.e. :

      source,target,value,colour,radius ...

      But I'll have dig deep into the code to capture the values and I still have no idea how to do it. Have you any idea about it? I'm trying but it seem no easy!
      Please, if you find some way to manage all this, post your idea here.
      Many thanks!

      Delete
    4. ok, I'm trying this way:

      1) add a column "rad" to the csv file

      2) modify the code as below:

      // Compute the distinct nodes from the links.
      links.forEach(function(link) {
      link.source = nodes[link.source] ||
      (nodes[link.source] = {name: link.source});
      link.target = nodes[link.target] ||
      (nodes[link.target] = {name: link.target});
      link.value = +link.value;
      // --- code to capture the "rad" value ----
      link.rad = +link.rad;
      //--------------------------------------------------------
      });

      This seems work fine to capture the rad value from the csv file, but now I don't know how to assign the rad values to the "r" variables for each node.

      Delete
    5. OK, if I read it correctly you should be able to change the line that sets the radius for the nodes ( .attr("r", 5);) so that it accesses a function that looks at the radius value for each node. It should look a little like this .attr("r", (function(d) { return d.rad; })); Good work. Might I be so bold as to suggest that you set up a live example as a jsfiddle? That would provide an opportunity to look at the complete code. Cheers

      Delete
    6. Hi D3noob,
      I tried to insert
      .attr("r", (function(d) { return d.rad; }));
      but it doesn't work, the code give no error but no circle drawn.
      It seems too difficult for me to solve, I'm not an "expert" in javascript, I'll have to work more on the problem.
      Thanks!

      Delete
    7. If you can put up a jsfiddle of the code (the version that works without the radius resizing) that would help fault find.

      Delete
    8. ok, change dinamically the radius for the circles it's not easy, also because the arrows. Maybe would be great just to pass different colours to distinguish the nodes and group them in different tipology. Can you help me to understand how to pass dinamically the colours by the csv file?
      Thanks!!

      Delete
    9. Oki Doki... Here's a starting point for you. The following page shows the example above using a fixed palette that will allocate a colour depending on a unique node name. I'ts not exactly what you're looking for, but it's a start that you can play with :-) http://bl.ocks.org/d3noob/8043434 and https://gist.github.com/d3noob/8043434

      Delete
    10. As a result of putting that quick example together I have updated the D3 Tips and Tricks book (https://leanpub.com/D3-Tips-and-Tricks) to include the colouring of the nodes from a fixed palette. Thanks again for the question Gino (I give you credit for the suggestion in the book :-)).

      Delete
  7. Hi D3noob,
    this is the js pieces of code that I added:

    // --- code I adde to capture the rad value of the csv file columns ----
    link.rad = +link.rad;
    //-------------------------------------------------------------
    });

    ...
    ...

    //--- add the nodes
    node.append("circle")
    .attr("r", 8);
    /*----- this piece of code doesn't work ----------------
    .attr("r", (function(d) {
    return d.rad;
    }
    ));
    --------------------------------------------------------*/

    ...
    ...

    The HTML page and all the other js code is quite the same as your example.
    The force_5.js is a file just like your csv file, exept that there's one more column (the rad parameter) of integer values (range 6 - 20)

    If you can help me would be great.
    Thanks!

    //gino

    ReplyDelete
  8. Hi D3noob,
    the example in the link:

    http://bl.ocks.org/d3noob/5141278

    seems not work in IE8, the error message say "d3 is undefined".

    Have you any idea?

    ReplyDelete
    Replies
    1. The most likely problem will be ie's poor support of svg in versions of ie before 9.
      I had no problems running the example in chrome on my phone.

      Delete
  9. Nice tutorial. As I was playing around with this code I changed the style sheet to make it more colourful. This is when I noticed that the arrow at the end of the line does to exist centred over the line. If you zoom right in you can see where the line connects to the node as the arrow head is slightly offset. This is much more noticeable when there is a strong disparity in the colours. I was wondering how to shift the arrow such that the end of the arrow point coincides with the end of the line?

    Thanks Sean

    ReplyDelete
    Replies
    1. This is one of the mysteries of building an SVG object with the 'd' attribute. When I wrote this portion of the book, I had no idea. I have a slightly better idea now, but I'm still a long way from understanding. Sorry I cant be of more help.

      Delete
  10. Is it possible to remove the curve shape and just add an arrow to a straight line which makes a simple look

    ReplyDelete
    Replies
    1. sure is, just have a play with the `ticks` function and see what you come up with. I'd start by tinkering with the `dr` portion.
      Just remember, that ultimately this will result in two arrows overlaid on top of each other. so it would make one slightly redundant.

      Delete
    2. I hope as we defined marker-end it would include the fact bidirectional between the nodes A<------------>B, if we define marker-mid then it will be slightly redundant.
      I'm able to make an arrow at the end, what is happening is the arrow is behind the node, the challenge is so we have to subtract the radius of the node from the line x1,y1,x2,y2 in such a way that it just line touches the surface of the two nodes, if we can transform into that way then the problem will be solved

      Delete
    3. Put up a bl.ock of your progress. It would be cool to see.

      Delete
    4. Hi! I am working on a tree generation with d3.js, using simple tree and not force-directed graph(this shouldn't change anything though).
      What i am trying to do is change the color of the arrows so that they are the same as the link wich it's on.
      In my case I can't change set it to a definite value, as the tree is dynamically generated and links can be of various colors.
      I tried to change the marker arrow(very similar to yours) to make it a function like this:
      function arrow(color){
      var val;
      .data([val])
      ...
      ..attr("id", String)
      ...
      .style("fill",color)
      ...
      return "url(#" +val+ ")";}

      (the "..." are part of the code that are essentially the same as yours)
      When I add the arrow to the links i call the function instead of an id, like this:
      .attr("marker-end", arrow);
      But it doesn't work, the links are not created. I tried different ways such as using
      arrow(function(){ return this.getAttribute('fill');}) to get the color of the link, but in vain.
      Do you have any ideas?
      thanks :)

      Delete
    5. You can have a look in this fiddle where the arrows are showing in the center of the node, the marker-mid here is not working, I tried polyline also but nothing worked

      http://jsfiddle.net/rajasekhariitbbs/H2nJA/1/

      Delete
    6. It looks like you may have removed a bit too much from the original code. The section that builds the arrow needs to be reverted. Make small changes and check, Then make more small changes and check. That's pretty much all I do when faced with a problem that seems insurmountable. Then if it gets out of hand, go back to the last point where you knew what was going on. Good luck.

      Delete
    7. HI JC Gambino, apologies for the lateness of my reply. You describe an interesting problem, but I don't think we can solve it in the chat section of the blog :-). My advice would be to raise a question on Stack Overflow and make a JS fiddle of your code so that people can see what is going on. I have had some fantastic responses to questions and I can't recommend the process highly enough.

      Delete
  11. Hi D3noob,
    Thanks for sharing this article. It helped me a lot :) However I'm stuck with one thing. For each node, I want to show some extra information like type, contact, level etc in a tooltip apart from the node name as label. Here is how my model looks which fetches values from the database:

    this.source = DBUtil.GetDBString(dr, "ParentCode");
    this.target = DBUtil.GetDBString(dr, "ChildCode");
    this.nodetype = DBUtil.GetDBString(dr,"NodeType");
    this.nodecontact = DBUtil.GetDBString(dr,"NodeContact");
    this.nodelevel = DBUtil.GetDBString(dr,"NodeLevel");

    How do I define these parameters for each node in my JavaScript method.
    I was trying:

    links.forEach(function (link) {
    link.source = nodes[link.source] || (nodes[link.source] = { name: link.source });
    link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });

    link.target.nodetype = link.nodetype ;
    link.target.nodecontact = link.nodecontact ;
    link.target.nodelevel = link.nodelevel ;

    This works almost fine except it shows no data or "undefined" for the very 1st node i.e. the root node. Can you please suggest a workaround for this?

    Regards,
    Koushik.

    ReplyDelete
    Replies
    1. Glad the article is useful. I have to apologise that I won't be able to help you out directly because I'm crushed for time at the moment. However, I have some information on tool tips that is pretty transferable here (http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html) and here (http://www.d3noob.org/2014/05/including-html-link-in-d3js-tool-tip.html). The next best bet is to ask a question on Stack Overflow. Since it looks like you're pretty close to a solution, this should be a good option. Sorry for the brush off, but I didn't want to leave you hanging.

      Delete
  12. I am new to the game and found your book a great help in getting started with the graph designs. I am working on a force directed graph and was wondering if there is a simple way via a button to pause or stop the force so I can drag some nodes. I understand once I start the force any positioning I had done would be changed.

    Thanks
    Perry

    ReplyDelete
    Replies
    1. Hi there. Sorry for the late reply. Although I haven't tried this myself, I see that there are a number of examples of very similar questions and answers on Stack Overflow. Give them a try;
      http://stackoverflow.com/questions/16112108/how-to-stop-a-d3-force-graph-layout-simulation
      http://stackoverflow.com/questions/16568861/stop-force-layout-on-d3js-and-start-free-dragging-nodes
      http://stackoverflow.com/questions/14296309/freezing-force-directed-network-graph-and-make-draggable-in-d3

      Delete
  13. Thanks for this awesome example D3noob... i want to know how will the json file be like if we were to use the same example?

    ReplyDelete
    Replies
    1. It should look a bit like the following
      {"source":"Harry","target":"Sally","value":1.2},
      {"source":"Harry","target":"Mario","value":1.3},
      {"source":"Sarah","target":"Alice","value":0.2},
      {"source":"Eveie","target":"Alice","value":0.5},
      ... etc etc.
      Check out the section on JSON on page 233 in the book (https://leanpub.com/D3-Tips-and-Tricks) for a better explanation of why.

      Delete
  14. Hi D3noob, thank you for your explanation. I am a student of engineering and I am learning about graph optimization in my university. To explain how to some optimization algorithms work I need a directed graph from my data. So, Could I use your code to create a example graph for some slides?

    ReplyDelete
    Replies
    1. No problem. Please do.

      Delete
    2. Hi D3noob, thank you very much. But I want to ask you if it is possible to add text for the arcs (for example the distance).

      I really appreciate your time.

      Regards,

      Delete
    3. Hi Daniel. I'm sorry I don't have the time to be able to help out too much, but the following examples are probably pretty close to what you are thinking of?
      http://bl.ocks.org/jhb/5955887
      https://bl.ocks.org/mattkohl/146d301c0fc20d89d85880df537de7b0
      http://bl.ocks.org/donaldh/2926502
      Good luck
      Malcolm

      Delete