Article
0 comment

Graph libraries – Arbor.js

Arbor.js is written by Christian Swineheart from Samizdat Drafting Co. The documentation is concise even though the main page navigation as animated tree layout always drives me crazy :) The example I present here only depends on arbor.js and jQuery. Graph definition is for shortness and clarity done by calls to addNode  and addEdge  methods of the ParticleSystem renderer.

How does it look?

As you can see, the physical simulation is much more fluid and undamped than with Alchemy.js. I’m pretty much shure this can be changed to some degree with a different initialization of the particle system as described in the reference documentation. Once again, the complete source code, in this case consisting only of an index.html file, can be found in the GitHub-Mark-32px GitHub repository.

How does it work?

Once again in the header we start by loading the stuff needed:

<script src="/graphs/common/jquery-2.1.3.js"></script>
<script src="/graphs/common/arbor.js"></script>
<script src="/graphs/common/arbor-tween.js"></script>

arbor-tween.js is not really needed for this example as it allows gradual transitions in the data object of nodes and edges. Arbor.js by default renders its graph in a HTML5 canvas, in the body we need to define this:

<canvas id="viewport" width="800" height="500"></canvas>

The remainder of the file consists of a function defining and binding the renderer object to the canvas and constructing the graph:

(function ($) {
        var Renderer = function (canvas) {
                var canvas = $(canvas).get(0);
                var ctx = canvas.getContext("2d");
                var particleSystem;
                var that = {
                        init: function (system) {
                                particleSystem = system;
                                particleSystem.screenSize(canvas.width, canvas.height);
                                particleSystem.screenPadding(100);
                                that.initMouseHandling()
                        },
                        redraw: function () {
                                ctx.fillStyle = "white";
                                ctx.fillRect(0, 0, canvas.width, canvas.height);
                                particleSystem.eachEdge(function (edge, pt1, pt2) {
                                        ctx.strokeStyle = edge.data.linkcolor;
                                        ctx.lineWidth = 3;
                                        ctx.beginPath();
                                        ctx.moveTo(pt1.x, pt1.y);
                                        ctx.lineTo(pt2.x, pt2.y);
                                        ctx.stroke();
                                });
                                particleSystem.eachNode(function (node, pt) {
                                        ctx.beginPath();
                                        ctx.arc(pt.x, pt.y, 15, 0, 2 * Math.PI);
                                        ctx.fillStyle = node.data.nodecolor;
                                        ctx.fill();
                                        ctx.font = "18px Arial";
                                        ctx.fillStyle = "#000000";
                                        ctx.fillText(node.data.name, pt.x + 20, pt.y + 5);
                                });
                        },
                        initMouseHandling: function () {
                                var dragged = null;
                                var handler = {
                                        clicked: function (e) {
                                                var pos = $(canvas).offset();
                                                _mouseP = arbor.Point(e.pageX - pos.left, e.pageY - pos.top);
                                                dragged = particleSystem.nearest(_mouseP);
                                                if (dragged && dragged.node !== null) {
                                                        dragged.node.fixed = true;
                                                }
                                                $(canvas).bind('mousemove', handler.dragged);
                                                $(window).bind('mouseup', handler.dropped);
                                                return false;
                                        },
                                        dragged: function (e) {
                                                var pos = $(canvas).offset();
                                                var s = arbor.Point(e.pageX - pos.left, e.pageY - pos.top);
                                                if (dragged && dragged.node !== null) {
                                                        var p = particleSystem.fromScreen(s);
                                                        dragged.node.p = p
                                                }
                                                return false;
                                        },
                                        dropped: function (e) {
                                                if (dragged === null || dragged.node === undefined) return;
                                                if (dragged.node !== null) dragged.node.fixed = false;
                                                dragged.node.tempMass = 1000;
                                                dragged = null;
                                                $(canvas).unbind('mousemove', handler.dragged);
                                                $(window).unbind('mouseup', handler.dropped);
                                                _mouseP = null;
                                                return false;
                                        }
                                };
                                $(canvas).mousedown(handler.clicked);
                        }
                }
                return that;
        }
        $(document).ready(function () {
                var sys = arbor.ParticleSystem(700, 700, 0.5);
                sys.renderer = Renderer("#viewport");
                sys.addNode('Node 1', {name: "Node 1", nodecolor: "#88cc88"});
                sys.addNode('Node 2', {name: "Node 2", nodecolor: "#888888"});
                sys.addNode('Node 3', {name: "Node 3", nodecolor: "#888888"});
                sys.addNode('Node 4', {name: "Node 4", nodecolor: "#888888"});
                sys.addNode('Node 5', {name: "Node 5", nodecolor: "#888888"});
                sys.addNode('Node 6', {name: "Node 6", nodecolor: "#888888"});
                sys.addEdge('Node 1', 'Node 3', {linkcolor: "#888888"});
                sys.addEdge('Node 1', 'Node 2', {linkcolor: "#888888"});
                sys.addEdge('Node 2', 'Node 5', {linkcolor: "#ff8888"});
                sys.addEdge('Node 2', 'Node 4', {linkcolor: "#ff8888"});
                sys.addEdge('Node 4', 'Node 5', {linkcolor: "#ff8888"});
                sys.addEdge('Node 5', 'Node 6', {linkcolor: "#888888"});
        })
})(this.jQuery);

Lines 2 to 73 define the Renderer, in detail:

  • get the HTML5 canvas (line 3)
  • grab its 2D context (line 4)
  • an init function for the particle system (lines 7 to 12)
  • a redraw function actually drawing the graph (lines 13 to 33) by iterating over all nodes and edges
  • defining a pretty basic mouse handling (lines 34 to 69)

In line 75 we get a particle system, set the rendering to the canvas in line 76 and define nodes (lines 76 to 81) and edges (lines 82 to 87). That’s pretty much all.

Leave a Reply

Required fields are marked *.