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.
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.
We will be creating a JSON file to contain the relation and image information, the HTML, and the accompanying JavaScript.
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" },
links
to the model."links": [ { "source": 1, "target": 0 } { "source": 3, "target": 0 } ]
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>
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); }
var force = d3.layout.force() .gravity(.04) .distance(350) .charge(-200) .size([width, height]);
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); }
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 });
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 + ")"; }); }); }());
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
.
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.
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
.
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.
Learn more on D3 force layout and the related functionality from https://github.com/mbostock/d3/wiki/Force-Layout.
3.144.48.204