Displaying a force directed graph

In this recipe, we will create a graph with some of the characters of the play Hamlet by William Shakespeare. The idea is to visualize the connections between the characters in a fun and interactive way. The type of graph that will be visualized is known as force directed graph.

Displaying a force directed graph

Getting ready

In order to visualize the connections between the characters, they need to be stored somehow. There is a sample data.json file that is a part of the code examples that you can use. Although we do encourage you to create your own sample data or at least play around with the existing one, but for the purpose of simplicity, we will be using the one provided in the code examples.

How to do it...

We will be creating a JSON file to contain the relation and image information, the HTML, and the accompanying JavaScript.

  1. First, we can start with the creation of the data for the recipe. We can define the nodes list, where the object would be placed with the properties name that will designate the name of the node, icon will be the URL of the image, and group will be
    {
      "nodes": [
        {
          "name": "Hamlet",
            icon":"http://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Bernhardt_Hamlet2.jpg/165px-Bernhardt_Hamlet2.jpg"
        },
        {
          "name": "King Claudius",
          "icon": "http://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Massalitinov_and_Knipper_in_Hamlet_1911.jpg/167px-Massalitinov_and_Knipper_in_Hamlet_1911.jpg"
        },
  2. After adding the nodes in the data, we also need information about how they are connected. To do that we will add a list of links to the model.
    "links": [
      {
        "source": 1,
        "target": 0
      }
      {
        "source": 3,
        "target": 0
      }
    ]
  3. Now we can proceed with the creation of the HTML file. For this implementation we will be using D3.js, so we need to include it and also setup two CSS classes, one for the link and the other for the node text.
    <script src="http://d3js.org/d3.v2.min.js"></script>
    <style>
    .link {
      stroke: #aaa;
    }
    .node text {
      pointer-events: all;
      font: 14px sans-serif;
      cursor: pointer;
      user-select: none;
    }
    </style>
  4. After this, we can start adding the parts in the main script. As in the previous examples, we will first add the SVG into the body element with some predefined size.
    (function (){
      var width = 960,    height = 600;
      var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);
    }
  5. Now we can create the layout for the graph.
    var force = d3.layout.force()
    .gravity(.04)
    .distance(350)
    .charge(-200)
    .size([width, height]);
  6. The next step is to map the data from the JSON document with the force layout and create all of the links and nodes.
    d3.json("data.json", function(json) {
      force.nodes(json.nodes)
      .links(json.links)
      .start();
      var link = svg.selectAll(".link")
      .data(json.links)
      .enter().append("line")
      .attr("class", "link");
      var node = svg.selectAll(".node")
      .data(json.nodes)
      .enter().append("g")
      .attr("class", "node")
      .call(force.drag);
    }
  7. We then append image from the model, defined as icon and text with the name of the node.
    node.append("image")
    .attr("xlink:href", function(d){return d.icon;})
    .attr("x", -32)
    .attr("y", -32)
    .attr("width", 100)
    .attr("height", 100);
    
    node.append("text")
    .attr("dx", -32)
    .attr("dy", -32)
    .text(function(d) { return d.name });
  8. Also on force changes and updates, we will set up a listener that will update the links and nodes positions.
    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 + ")"; });
      });
    }());

How it works...

First, we will take a look at the CSS, and more specifically at pointer-events that we have set to all. This setting makes the element to be the target of mouse-events, when the pointer is in the interior or over the perimeter, and can only be used on the SVG elements. In order to disable the selection of the text, we use the CSS property user-select, and set it to the value of none.

Tip

user-select is not consistent across browsers, and in order to use it, we can add browser specific CSS hack, such as the following:

-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;

The layout used for this recipe is d3.layout.force() that does not create a fixed visual representation, but instead we define the parameters, such as friction, distance, and gravity strength. Depending on the data and mouse interactions, we get different views.

var force = d3.layout.force()
.gravity(.04)
.distance(350)
.charge(-200)
.size([width, height]);

When constructing the layout after setting the parameters and the data information about the links and nodes, we need to call the start() method.

force.nodes(json.nodes)
.links(json.links)
.start();

We want to create the g element for all the nodes from our data, and set the appropriate CSS class node.

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

Also, add a behavior to allow interactive dragging using .call(force.drag).

The g element represents a container that can be used to group other elements. Transformations that are applied to the g element are also performed on all of its child elements. This feature makes the element a good pick for organizing different section of view blocks.

Note

More on the g element can be found in the SVG specification from http://www.w3.org/TR/SVG/struct.html#Groups.

The force.drag() method is predefined in the d3.layout.force() method. Drag event is fixed to work on mouseover to allow catch of moving nodes. When the mousedown event is received, the nodes are dragged to the mouse position. Interesting is that this behavior supports the touch events from mobile devices, such as iOS or Android. In order to disable the click events for the nodes while dragging, mouseup is captured and stopped from propagating.

To create an image for the nodes, we add SVG image tag with xlink:href to the URL from the data stored in d.icon.

node.append("image")
.attr("xlink:href", function(d){return d.icon;})

In order to have the update from the layout, there are tick events that are dispatched on each tick of the visualization. In order to keep the elements updated, we added a listener for the event.

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 + ")"; });
  });

The listener sets the correct positions for the movements to the link and node.

There's more...

One of the more obvious options here is to add more interactions to the visualization. Nodes can be made collapsible and links can be added to the nodes. Relationships between the nodes can be set to more fine-grained levels There are ways to make the data refresh over time and reload certain portions of the graph. If needed, there can be preset expected layout, so that the node will try to confirm a certain positioning.

Note

Learn more on D3 force layout and the related functionality from https://github.com/mbostock/d3/wiki/Force-Layout.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.48.204