- HTML5 Data and Services Cookbook
- Gorgi Kosev Mite Mitreski
- 1458字
- 2021-08-06 16:49:59
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:
- 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, aurl
attribute that has a link to the official web page, and the optionalchildren
attribute that can contain a list of other objects. - Next step would be to create the page using the HTML5 doctype, and adding the dependency to
D3.js
and a CSS file calledtree.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>
- In the
body
section, we are going to add a<div>
tag having anid
calledlocation
that we are going to use as placeholder, and additionally include a JavaScript file calledtree.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>
- 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() {
- We then set up the size of the generated image with given
width
andheight
. For simplicity, we set them to fixed values:var width = 1000, height = 600;
- 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]; });
- As we need to designate and create the actual SVG, we pick the location using the
id
previously chosen in the HTML calledlocation
, and append SVG element:var vis = d3.select("#location").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(60, 0)");
- 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; });
- 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.
- Mastering Natural Language Processing with Python
- 技術(shù)領(lǐng)導(dǎo)力:程序員如何才能帶團(tuán)隊(duì)
- Blockly創(chuàng)意趣味編程
- C語言程序設(shè)計(jì)實(shí)踐教程
- Angular開發(fā)入門與實(shí)戰(zhàn)
- Bootstrap 4 Cookbook
- Learning PHP 7
- Windows Embedded CE 6.0程序設(shè)計(jì)實(shí)戰(zhàn)
- Java程序員面試筆試寶典(第2版)
- 軟件測試技術(shù)
- INSTANT LESS CSS Preprocessor How-to
- Mastering High Performance with Kotlin
- Abaqus GUI程序開發(fā)指南(Python語言)
- 鋒利的SQL
- 深入理解TypeScript