官术网_书友最值得收藏!

Displaying a tree

In this recipe, we will take a look into how to display data in a tree-like layout. We are going to visualize a small family tree of Linux represented via JSON file. Additionally, will be using the D3.js file for manipulating the DOM to display the data.

Getting ready

First, we need to have the data that is going to be used for the visualization. We need to get the tree.json file that is part of the examples for this recipe.

How to do it...

We will write the HTML and the backing JavaScript code that should generate data from a JSON file:

  1. Let's first take a look a the structure of the JSON data:
    {
      "name": "GNU/Linux",
      "url": "http://en.wikipedia.org/wiki/Linux",
      "children": [
        {
          "name": "Red Hat",
          "url": "http://www.redhat.com",
          "children": [ .. ]
       } ]
    ...
    }

    Each object has a name attribute representing the distribution name, a url attribute that has a link to the official web page, and the optional children attribute that can contain a list of other objects.

  2. Next step would be to create the page using the HTML5 doctype, and adding the dependency to D3.js and a CSS file called tree.css:
    <!DOCTYPE html>
    <html>
      <head>
        <title>Linux Tree History</title>
        <script src="http://d3js.org/d3.v2.js"></script>
        <link type="text/css" rel="stylesheet" href="tree.css"/>
      </head>
  3. In the body section, we are going to add a <div> tag having an id called location that we are going to use as placeholder, and additionally include a JavaScript file called tree.js that will be used to include the logic for mapping the data:
      <body>
        <div id="location"></div>
        <script type="text/javascript" src="tree.js"></script>
      </body>
    </html>
  4. Let's start with creating the display area in the tree.js file. First, we create the anonymous function that provides private state of the variables used inside:
    (function() {
  5. We then set up the size of the generated image with given width and height. For simplicity, we set them to fixed values:
    var width = 1000,
              height = 600;
  6. Afterwards, we set up a standard D3 layout tree:
      var tree = d3.layout.tree()
              .size([height, width - 200]);
      var diagonal = d3.svg.diagonal()
              .projection(function(d) {
                return [d.y, d.x];
              });
  7. As we need to designate and create the actual SVG, we pick the location using the id previously chosen in the HTML called location, and append SVG element:
      var vis = d3.select("#location").append("svg")
              .attr("width", width)
              .attr("height", height)
              .append("g")
              .attr("transform", "translate(60, 0)");
  8. We also need to read out the data from tree.json and somehow create nodes and links with the given hierarchy:
    d3.json("tree.json", function(json) {
        var nodes = tree.nodes(json);
        vis.selectAll("path.link")
              .data(tree.links(nodes))
              .enter().append("path")
              .attr("class", "link")
              .attr("d", diagonal);
        var node = vis.selectAll("g.node")
                .data(nodes)
                .enter().append("g")
                .append("a")
                .attr("xlink:href", function(d) {
                     return d.url;
                  })
                .attr("class", "node")
                .attr("transform", function(d) {
                    return "translate(" + d.y + "," + d.x + ")";
                  });
    
        node.append("circle")
                .attr("r", 20);
    
        node.append("text")
                .attr("dx", -19)
                .attr("fill", "white")
                .attr("dy", -19)
                .style("font-size", "20")
                .text(function(d) {
                  return d.name;
                });
  9. We can style the page using CSS, picking color for the link background of the page and the circle:
     .node circle {
         fill: #fc0;
         stroke: steelblue;
         stroke-width: 1px;
    }
    .link {
      fill: none;
      stroke: #fff;
      stroke-width: 5.0px;
    }
    body{  
        background-color: #000;
     }

How it works…

The line d3.layout.tree() creates a new tree layout with default settings, where it is assumed that each input in the data element has a child array.

With d3.svg.diagonal(), we create a generator with default accessor functions. The returned function can generate the path data for a cubic Bézier connecting the nodes where we have tangents for smoothing the line.

Note

More information on Bézier curve can be found at http://en.wikipedia.org/wiki/Bézier_curve. There is some mathematics behind it, but the simplest explanation would be that it is a line affected by certain points making it a good pick to define curvy lines.

As we wanted to have the tree from left to right instead of the the default, which is from top to bottom, we need to change the default behavior by doing a projection:

var diagonal = d3.svg.diagonal()
          .projection(function(d) {
              return [d.y, d.x];
          });

The function will use [d.y, d.x] instead of the default [d.x,d.y]. One thing that you may have noticed is the .append("g") function that adds the SVG g element, which is a container element used for grouping together various related elements. We can have multiple nested elements inside, one within another, to an arbitrary depth, allowing us to create groups on various levels:

<g>
      <g>
      <g>
       </g>
     </g>
   </g>

To read the JSON data we've used the following:

d3.json("tree.json", function(json) { … }

That does an AJAX call to the tree.json resource.

Note

Note that, by default, your browser will not allow cross-domain requests. This includes requests to your local filesystem. To overcome this, please use a local web server explained in Appendix A, Installing Node.js and Using npm. Another option is to use JSONP as a great workaround, because with this security restriction there are some shortcomings. In Chapter 8, Communicating with Servers, we cover the issues and the reasoning behind these restrictions.

For more information, take a look at the W3C page at http://www.w3.org/TR/cors/.

We then automatically map the data from the JSON file with tree.nodes(json), where some assumptions are made on what we have inside the data; for example, we can have a parent node or children nodes.

After that, we selected all the path.link using W3C selectors that resemble a lot like the jQuery ones:

vis.selectAll("path.link")

Using .data, we bind them with link information that is returned by tree.links:

.data(tree.links(nodes))

What happens in the background is that D3's tree layout has a links function that accepts an array of nodes, and returns an array of objects representing the links from parent to child for each of these nodes. Links for the leafs will not be created. The information stored in the returned object has a source or the parent node and target or the child node. Now, in the following part, there is the .enter() function that's very much D3 magic. What happens is that, for every element in the array that is part of .data([theArray]) and has no corresponding DOM element found in the selection, it simply "enters inside the data" allowing us to use .append, .insert, .select, or .empty operators. In our case, we want to create SVG path elements having a CSS class of link and a d attribute calculated using the diagonal function we previously defined:

           .enter()
           .append("path")
           .attr("class", "link")
           .attr("d", diagonal)

So, for each data element, it will create <path class='link' d='dataCalucatedByDiagonal' />.

The SVG path element is a concept used to represent a line drawing that we would do with a pen, for example, having various types of geometry and representation. The d attribute contains the path data designated with moveto(M), lineto(L), curve( cubic and quadratic besiers), arc(A), closepath(Z), vertical lineto (V), and so on.

It's good to know what is generated by D3 for us in order to understand more completely how it works. Let's say we want to display a simple line:

The SVG code would be as follows:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <g style="stroke: red; fill: none;">
    <path d="M 10 30 L 200 10"/>
 </g>
</svg>

Examining the path data values, we can see that it means move pen(M) to (10,30) and draw line(L) to (200,10).

In our example with the tree, we have the lines drawn using paths, so the next step would be to draw the nodes. We apply the same procedure where we select all the old g.node elements and enter the node data, but instead of creating the <path/> element, we just append "g" and additionally add an <a> element with the <xlink:href> attribute:

…
            .append("a")
            .attr("xlink:href", function(d) {
                 return d.url;
              })

As we are already automatically iterating all the data nodes, we can access d.url, retrieving the URL for each node and setting it as a link to all the inner elements that we are going to add later.

Don't forget that we need to rotate the coordinates, because we want the tree to be displayed from left to right:

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

After this, we can append other elements to each of the elements, so in order to create the circle, we add the following:

    node.append("circle")
            .attr("r", 20);

That creates an SVG circle with radius of 20px, also, we append the <text/> element that will display the distribution name:

   node.append("text")
            .attr("dx", -19)
            .attr("dy", -19)
             ...

Notice that we are moving the text element with (-19,-19) in order to avoid overlapping with the circle and the lines, and that is it.

There's more...

The first thing you must do is play around with the values that are constant, such as the image size or text offset. This will help you better understand how changes affect the layout. There are various different functions to generate the layout, you can create it in a radial fashion or make it dendrite-like.

There are various ways to add interaction where you can have updates on certain portion of the code, make some parts animated, or even include HTML inside the SVG.

主站蜘蛛池模板: 浙江省| 涟水县| 新源县| 莒南县| 阳东县| 汝州市| 玉林市| 亳州市| 兴宁市| 石嘴山市| 普宁市| 原平市| 雷山县| 棋牌| 四会市| 宁夏| 迭部县| 马尔康县| 泰州市| 彭泽县| 黄冈市| 桑日县| 龙胜| 江都市| 新巴尔虎左旗| 寻甸| 石城县| 邓州市| 五指山市| 洪湖市| 澄江县| 江川县| 黄石市| 上林县| 义乌市| 宿州市| 尤溪县| 依兰县| 木兰县| 永丰县| 宁蒗|