Posted by kyle on August 3, 2006 11:26 PM | bookmark / share: |
In this tutorial, you'll learn how to integrate JavaScript Force Directed and Tree graphs into your web page or application using:
- HTML and CSS
- JavaScript with the DOM
- or JavaScript with SVG
Click here to see what we're building...
If you're new to JSViz, or even new to JavaScript, this tutorial should be enough to get you rolling. Let's start out with this HTML document:
<html>
<head>
<title>JSViz 0.2 Tutorial</title>
<script language="JavaScript">
function init() {
}
</script>
</head>
<body onload="init()">
<center><br>
<div id="frame" style="border:3px dashed #ccccdd;width:500px;height:350px;"></div>
</center>
</body>
</html>
The "frame" div that we've created will hold our graph. It's going to be useful to have the dimensions of the frame available in the next several steps, so let's add this code to the init() function:
// Get the dimensions of our 'frame' element
var frame = document.getElementById('frame');
var frameLeft = parseInt(frame.offsetLeft);
var frameTop = parseInt(frame.offsetTop);
var frameWidth = parseInt(frame.style.width);
var frameHeight = parseInt(frame.style.height);
Now it's decision time!
Which graph model will work best with your data? Force Directed Graphs look neat and can handle all kinds of connected graphs. Tree Graphs render faster, but can only support data that can be organized into branches and leaves (no circuits).
How do you want to create and display your graph? It depends on you and your audience. You can make a graph node out of virtually any HTML element on your page or create elements using JavaScript and your graph will display on most browsers. If you plan on just using shapes and text, SVG is prettier and maybe a little faster, but is only currently supported by FireFox 1.5 and above. Users with other browsers will require a plug-in.
I'm going to dive into detail on Force Directed Graphs with a DOM view. The interfaces to other models and views are very similar, so you'll learn how the libraries work. At the end of the article, you'll find an example worked up for the other Model / View combinations.
First, we need to import the appropriate libraries. Add these script imports to the <head> section of your HTML page.
<script src="EventHandler.js"></script>
<script src="Timer.js"></script>
<script src="GraphModel.js"></script>
<script src="FDGraphModel.js"></script>
<script src="GraphView.js"></script>
<script src="DOMGraphView.js"></script>
<script src="DOMFDGraphController.js"></script>
Now we're going to do something strange ... You see, Internet Explorer is a little wonky when it comes to moving images around the screen. I can hardly believe it, but if we don't preload an image, IE tries to fetch it every time we reposition it! Since we're constantly moving images around, this results in hundreds or thousands of requests! The images flicker or don't even display until the graph settles. Dean Edwards and others have tackled this problem in clever ways, but I'm taking the cowards way out.
Add this to the <body> to preload all of the images we'll use in this tutorial:
<div id="img_preload" style="position:absolute;display:none;">
<img src="http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=12&pt=8&b=656565&c=99ee55">
<img src="http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=24&pt=8&b=656565&c=ee9944">
<img src="http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=16&pt=8&b=656565&c=eeee66">
<img src="http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=9&pt=8&b=656565&c=6688ee">
</div>
Now we'll instantiate a controller. Most of your application's interaction with the graph takes place through this object.
Add the following to the init() function:
// Instantiate the Controller
var controller = new DOMFDGraphController( frameWidth, frameHeight, frameLeft, frameTop );
Next, we'll create a root node. In a Tree graph, this is the trunk of the Tree (it's gotta start somewhere, right?). In a Force Directed Graph, all of the nodes share an edge with the root. It keeps them from floating out into space. In your application, you may find it practical to have multiple root nodes.
You can create the node with HTML or JavaScript>
To use HTML, add the following to the body of the document:
<div id="domRoot" style="width:12px;height:12px;
background-image:url('http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=12&pt=8&b=656565&c=99ee55');
display:none;">
<img width="1" height="1"
></div>
Then append the following to the init() function:
var domRoot = document.getElementById('root');
Or, to use JavaScript, append the following to the init() function:
var domRoot = document.createElement("div");
domRoot.style.width = "12px";
domRoot.style.height = "12px";
domRoot.innerHTML = '<img width="1" height="1">'; // hacky, but necessary to render in IE
// we use the background-image rather than a foreground image so we don't confuse the browser's
// image drag-and-drop feature
domRoot.style.backgroundImage = "url('http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=12&pt=8&b=656565&c=99ee55')";
document.body.appendChild( domRoot );
Then we add the root node to our graph, positioned in the center of our frame:
var root = controller.addRootNode( domRoot, false, 8, true, parseInt(frameWidth/2), parseInt(frameHeight/2) );
There are a bunch of parameters to addRootNode(). Here's quick overview:
- domElement: A reference to an element in the DOM that should either be cloned, or added directly to the graph.
- doClone: A boolean indicated that specifies whether the domElement should be cloned or added directly.
- mass: The mass of the new node, used in force calculations.
- isStatic: A boolean that indicates whether this node can move.
- startX: The initial x-coordinate of this node's position.
- startY: The initial y-coordinate of this node's position.
Now we're ready to add other nodes to the graph! The easiest way to do this is to add a clone of the root node:
var nodeA = root.addEdge( root, true, false );
Your application will probably create nodes to represent a dataset located on your HTML page or accessible via an AJAX call. There are a lot of different ways you can express your data. Let's experiment with different kinds nodes and edges, and different ways you can add them to your graph. All of this code can be appended to the init() function. First we'll add a large node with more mass:
var domB = document.createElement("div");
domB.style.width = "24px";
domB.style.height = "24px";
domB.innerHTML = '';
domB.style.backgroundImage = "url('http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=24&pt=8&b=656565&c=ee9944')";
document.body.appendChild( domB );
var nodeB = root.addEdge( domB, false, false, 40, 8 );
Then we'll add a child node to this new node:
var domC = document.createElement("div");
domC.style.width = "9px";
domC.style.height = "9px";
domC.innerHTML = '';
domC.style.backgroundImage = "url('http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=9&pt=8&b=656565&c=6688ee')";
document.body.appendChild( domC );
var nodeC = nodeB.addEdge( domC, false, true, 30, 3 );
Now we'll try a text node:
var domD = document.createElement("div");
domD.style.display = "inline";
domD.innerHTML = 'text';
document.body.appendChild( domD );
var nodeD = nodeB.addEdge( domD, false, true, 30, 4 );
We can clone that text node and change it's contents:
var nodeE = root.addEdge( domD, true, false, 50, 4 );
nodeE.viewNode.innerHTML = 'more text';
And some other arbitrary nodes:
for( var i=0; i<4; i++ ) {
root.addEdge( domRoot, true, false );
root.addEdge( domC, true, false, 50, 3 );
}
How about another static node?
var domF = document.createElement("div");
domF.style.width = "16px";
domF.style.height = "16px";
domF.innerHTML = '';
domF.style.backgroundImage =
"url('http://www.kylescholz.com/cgi-bin/bubble.pl?title=&r=16&pt=8&b=656565&c=eeee66')";
document.body.appendChild( domF );
var nodeF =
controller.addRootNode( domF, false, 6, true, parseInt(frameWidth/2)+50, parseInt(frameHeight/2) );
Finally, an HTML table to top it all off:
var d1 = document.createElement("td");
d1.innerHTML="column 1";
var d2 = document.createElement("td");
d2.innerHTML="column 2";
var tr = document.createElement("tr");
tr.appendChild(d1);
tr.appendChild(d2);
var tbody = document.createElement("tbody");
tbody.appendChild( tr );
var table = document.createElement("table");
table.border=1;
table.appendChild( tbody );
document.body.appendChild(table);
nodeF.addEdge( table, false, false, 50, 3 );
That's it! Give it a shot in your app and leave us a link!
Comments
It would be neat if you had some kind if Wiki where people could post their work.
Posted by: Anonymous | August 4, 2006 7:34 AM
Hello Kyle,
JSViz seems to be the best JS graph visualization package currently available, thanks for the great work!
Could you perhaps give some basic hints in your tutorial on how to enrich the functionality of the UI?
E.g. I'd like to know how to have a context menu come up when I right-click a node so I could let the user edit some node properties. Etc.
Or perhaps things like this will be tackled in the upcoming redesign only?
Best regards, Thilo
Posted by: Thilo Ernst | August 29, 2006 9:56 AM
Thilo, Thanks! I've got a couple of other requests for tutorials around enhancing the user experience, so I'll bump these up on my todo list. I'll try to do some posts for next week.
I've been super-busy with my real job and some major enhancements to JSViz so I haven't been posting lately, but we've really got some huge improvements in the works that I think you'll like. Watch for details soon!
Posted by: Kyle Scholz | August 31, 2006 2:55 PM
Looks very cool. I'm glad you were able to use my SVG implementation!
Posted by: Ted Mielczarek | September 15, 2006 10:20 PM
Be sure to check out the latest on JSViz:
http://www.kylescholz.com/blog/projects/jsviz/
Posted by: Kyle Scholz | October 20, 2006 8:43 AM