Documentation

Documentation

Don’t hesitate to dive into the source code! It is well described with comments and the library archive also includes some example files worth checking out.

Example

This commented example demonstrates most of what you can do with the Dracula library. It also serves as a quick tutorial on the usage.

var g = new Graph();
 
/* add a simple node */
g.addNode("strawberry");
g.addNode("cherry");
 
/* add a node with a customized label */
g.addNode("id34", { label : "Tomato" });
 
/* Advanced node example: Add a node with a customized shape
   (the Raphael graph drawing implementation can draw this shape, please
   consult the RaphaelJS reference for details http://raphaeljs.com/)
   Note: Here's the place for even more tweaking! */
 
/* First: Write a custom node render function. */
var render = function(r, n) {
        /* the Raphael set is obligatory, containing all you want to display */
        var set = r.set().push(
            /* custom objects go here */
            r.rect(n.point[0]-30, n.point[1]-13, 62, 86)
                .attr({"fill": "#fa8", "stroke-width": 2, r : "9px"}))
                .push(r.text(n.point[0], n.point[1] + 30, n.label)
                    .attr({"font-size":"20px"}));
        /* custom tooltip attached to the set */
        set.items.forEach(
            function(el) {
                el.tooltip(r.set().push(r.rect(0, 0, 30, 30)
                    .attr({"fill": "#fec", "stroke-width": 1, r : "9px"})))});
        return set;
    };
 
g.addNode("id35", {
    label : "meat\nand\ngreed" ,
    /* filling the shape with a color makes it easier to be dragged */
    render : render
});
 
/* connect nodes with edges */
g.addEdge("strawberry", "cherry");
g.addEdge("cherry", "apple");
 
/* a directed connection, using an arrow */
g.addEdge("id34", "cherry", { directed : true } );
 
/* customize the colors of that edge */
g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Label" });
 
/* add an unknown node implicitly by adding an edge */
g.addEdge("strawberry", "apple");
 
/* layout the graph using the Spring layout implementation */
var layouter = new Graph.Layout.Spring(g);
layouter.layout();
 
/* draw the graph using the RaphaelJS draw implementation */
var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
renderer.draw();

61 Responses to Documentation

  1. Iñaki R. says:

    Hi!

    Is there any posibility of insert an image into a node? Or better, icons instead of rectangles.

    Great job :D

    • Johann Philipp says:

      Yes, for now you can write your own render function to render the nodes. Try something like this:

      var render = function(r, n) {
              /* the Raphael set is obligatory, containing
                  all you want to display */
              var set = r.set().push(
                  r.image("image.png", 10, 10, 80, 80));
              return set;
          };
       
      g.addNode("myID", {
          label : "myLabel" ,
          render : render
      });

      See: http://raphaeljs.com/reference.html#image

      But the positions will sometimes not be integer and the picture will look blurry. I am going to address this issue… also, I am going to provide an easier way.

  2. Pingback: Dracula — рисуем графы на JavaScript | Записки программиста

  3. Yahasana says:

    Hi Philipp,

    Thanks for you great work.

    How can I remove a node or edge dynamically?

    or as a feature request

    • Johann Philipp says:

      I’m working on it :-)

      • Johnny says:

        Did you already manage to add this feature? It’s highly needed ;)

        • Kanchana Welagedara says:

          This is a really cool API,have been playing around. Can we group set of call graphs nodes together and
          visualize the seperation of it.

          /BR
          Kanchana

          • Johann Philipp says:

            Hi there! Nodes repulse and attract each other according to their connections. Attractions can be changed (you currently have to go through the code to see this) but anyway you can use this mechanism in order to implement grouping. In fact, most graphs should group automatically. See http://lackoftalent.org/michael/ for an example (click on “graph beta” at the top). Good luck! I’ll write about grouping and changing the forces soon, I’m pretty occupied currently…

      • vijay says:

        Hi i am vijay, I am new to this place.I have a question can we set the distance of nodes based on label values.
        e.g: I have set amount on the label so based on amount can we set the distance less amount less distance great amount great distance

        Thanx a lot.

  4. cristina says:

    Hi Philipp,
    is there the possiblity to add a label to an Edge?

  5. anurag says:

    Hi … is der a method to draw other shapes(example: triangle or a rhombus).

  6. Hi

    Good work with the graphs. I’m trying to build a mind mapping application using dracula and raphael. I was wondering if there is any way that you can choose one node to always be centred in the middle of the canvas?

  7. Yuguang says:

    How do I register a click event on a node?

  8. Yuguang says:

    apparently, raphael has
    c.node.onclick = function () {
    c.attr(“fill”, “red”);
    }

  9. ccattuto says:

    Hi, great work ! How can I have simple straight lines for the edges, instead of the (more CPU-intensive) pretty arcs ?

    • lad says:

      Line 74 of dracula_graffle.js, change

      var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");

      to

      var path = ["M", x1.toFixed(3), y1.toFixed(3), "L", x4.toFixed(3), y4.toFixed(3)].join(",");

  10. Dan Brickley says:

    OK I’m an idiot :) Just updated to a more recent version and the index.html demo does what I was asking for. Or seems to… Thanks!

  11. jokester says:

    Wonderful job. thanks.

    but sorry, did not found info about license or something, though it is open to public.
    wondering if I may use it in a commerical company (in a inner presentation, not in product to sell).
    looking forward to reply.

    and:
    being almost innocence about JS, sorry for not being able to directly contribute to this lib.
    (I am personally using perl to generate networkgraph.js from a C project, and layout&render it with the Dracula lib)

    • Johann Philipp says:

      Hey Jokester, the license is MIT – so commercial products are not a problem at all! I should put a notice somewhere…

      Cheers.

  12. baqi says:

    thanks a lot, I have used your library in
    http://www.textminingthequran.com/apps/similarity.php

    I am working to enhance this further.
    How can I place the label in the center of the oval instead of beneath it. Also, I want the label clickable.

    thanks

    • Johann Philipp says:

      Very nice! You can create custom nodes, see the the custom render function part in this Documentation. Also, one of my examples had a label placed in the center (meat). For adding click events to the nodes, check out the Raphaël JS documentation. Good luck to you!

      • pihentagy says:

        Say you have a node:
        g.addNode(“c2″, { label: “Work”})

        and your svg will look like:

        I thought label is for display purposes, and id is the identity. Am I wrong?

        • pihentagy says:

          Sorry, no preview, second try:

           
          • pihentagy says:

            Last try: note that the id is Work

            <ellipse cx=”640.5″ cy=”359.5″ rx=”30″ ry=”20″ fill=”#bf5600″ stroke=”#bf5600″ stroke-width=”2″ id=”Work” style=”cursor: move;” fill-opacity=”0.6″/>

  13. Omid says:

    Hi, I want to use your library in a gui of a graph program that runs in the browser. I want to add a bottun to the page for creating the nodes. How can I do this? I have some problems with javascript and your renderer and layouter objects. Excuse me for my English.

  14. Rob Field says:

    Hi, this is a great looking library.

    I was just curious if there is anyway for a node to have an edge pointing back onto itself? I’m just wondering if I am missing a trick somewhere.

    Thanks, keep up the good work.

    • Johann Philipp says:

      Sorry, currently there’s no such feature. I’d be thankful for any implementation though :-)

    • Tara Roys says:

      The nodes do have edges pointing back to themselves. If you put g.addEdge(“Cherry”, “Cherry”) it will create a link to itself on the right-hand side of the node. Unfortunately, it just looks like a litle arrow pointing to the node, no bigger than this: <-.

      You can cheat a little bit by adding another node

      g.addNode("extranode"); and link it to your previous node like so:

      g.addEdge("Cherry", "extranode");
      g.addEdge("extranode", "Cherry");

      If you create a custom render runction, you can change the extranode's appearance so that it looks like a lable, and voila- you have created a node that points to itself (or at least looks like it.)

  15. Skully says:

    Hi,

    is it possible to not auto-layout the graph? Like when i want to set the positions for each node on my own. Is it possible to set coordinates for the nodes?

  16. Kanchana Welagedara says:

    Would like to know wehther we ca group the nodes ?

    • Kanchana Welagedara says:

      Hi Philip

      I have become a big Fan of your simple still elegant call graph API.So, I have used your call graph API in one of my application.I need to make the connections and nodes to be more organized with out over lapping to each other.Shuffle function which is given in above example is not avoiding the
      over lapping nodes and connections.
      I’m looking for some solution to draw the call graphs in a neat way,may be to make the less the size of a node, and to maintain the length of a connection lines.
      Also as I earlier asked about grouping..
      Please consider following scenario of dependency analysis and if I’m using call graphs for that represent such dependencies in a call graph..

      1. Class A calls Class B and Class C
      2. If Class A changed ,B and C will be impacted
      3. There is another set of classes P,Q and R ,P has Changed ,which consumes B.
      4. But B,P,Q are from Same Module,A and C are separately for A,B, C and P,Q,R those will be two separated call graphs groups.
      5. If I wanted to indicate and group again to show what node(A,B,C,D,P,R,Q) by Module on top of already drew graphs and on the same screen as an additional information (Page),What can be done?
      can we show B,P,Q highlighted ? sparking with a flash line while they are attached to the initial grouping of changed impacts?

      Really appreciate your comments
      Thanks
      Kanchana

  17. Dennis-GAO says:

    is there any function to delete an edge?

    • Luis says:

      I found the same need when working with the lib. What I did is that I created a separate data structure to manage the graph information, with two arrays, one for the nodes and other for the edges. Whenever something changes on my custom data struct, it plots the whole graph. The bad thing is that it redraws everything, changing colors and positions each time. If it works for you you can do that.


      /*
      * Edge
      */
      function Edge(node,target){
      this.node=undefined;
      this.targets=new Array();
      node ? this.node=node : null;
      target ? this.addTarget(target) : null;
      }
      Edge.prototype = {
      constructor:Edge,
      create:function(node,targets){
      var r = null;
      if(node&&targets){
      this.node=node;
      this.addTarget(targets);
      r=node;
      }
      return r;
      },
      addTarget:function(target){
      var r = false;
      if(target&&this.node!=undefined){
      if(typeof target == "object"){
      r = new Array();
      for(var i=0;i<target.length;i++){
      var j=0,found=false;
      while(j<this.targets.length&&!found){
      this.targets[j]==target[i] ? found=true : null;
      j++;
      }
      if(j==this.targets.length){
      this.targets.push(target[i]);
      r.push(target[i]);
      }
      }
      }
      else{
      var i=0,found=false;
      while(i<this.targets.length&&!found){
      this.targets[i]==target ? found=true : null;
      i++;
      }
      if(!found){
      this.targets.push(target);
      r = target;
      }
      }
      }
      return r;
      },
      removeTarget:function(target){
      var r = null;
      if(target){
      if(typeof target == "object"){
      r = new Array();
      for(var i=0;i<target.length;i++){
      var j=0;
      while(this.targets[j]!=undefined){
      if(this.targets[j]==target[i]){
      r.push(target[i]);
      this.targets.splice(j,1);
      j--;
      }
      j++;
      }
      }
      }
      else{
      var i=0;
      while(this.targets[i]!=undefined){
      if(this.targets[i]==target){
      r = target;
      this.targets.splice(i,1);
      i--;
      }
      i++;
      }
      }
      }
      return r;
      },
      hasTarget:function(target){
      if(typeof target != "object"){
      for(var i=0;i<this.targets.length;i++)
      if(this.targets[i] == target)
      return true;
      return false;
      }
      else{
      for(var i=0;i<target.length;i++)
      if(!this.hasTarget(target[i]))
      return false;
      return true;
      }
      }
      }

      /*
      * DirectedGraph
      */
      function DirectedGraph(init){
      this.nodes=new Array();
      this.edges=new Array();
      this.isBidirectional = false;
      this.matrix = new Array();
      if(typeof init == "object"){
      init.type=="bidirectional" ? this.setType("bidirectional") : null;
      typeof init.nodes == "object" ? this.addNode(init.nodes) : null;
      typeof init.edges == "object" ? this.addEdges(init.edges) : null;
      /*if(typeof init.buildMatrix == "boolean" && this.nodes.length>0){

      }*/
      }
      }
      DirectedGraph.prototype = {
      constructor: DirectedGraph,
      /*
      * setType
      * Sets the type of Graph. It must be empty to
      * set the type. Can be: unidirectional (set by default),
      * or bydirectional.
      */
      setType: function(how){
      if(this.isEmpty()){
      switch(how){
      case "unidirectional":
      this._isBidirectional=false;
      break;
      case "bidirectional":
      this._isBidirectional=true;
      break;
      default:
      return false;
      }
      return true;
      }
      return false;
      },
      /*
      * isEmpty
      * Checks if the array of nodes is empty
      * @return True if is empty, false if it isn't
      */
      isEmpty: function(){
      if(this._nodes._length==0)
      return true;
      return false;
      },
      /*
      * reset
      * Creates a new array for the nodes and for the
      * edges (it empties the Graph), and leaves only
      * the type of Graph value
      */
      reset: function(){
      this._nodes = new LinkedList();
      this._edges = new LinkedList();
      },
      /*
      * addNode
      * Adds a node to the Graph, verifying first if the
      * value of the node exists already.
      * @return true if the node was inserted, and
      * false if it wasn't because it was already there
      */
      addNode: function(a){
      if(typeof a != "object"){
      if(!this.hasNode(a)){
      this.nodes.push(a);
      return a;
      }
      return false;
      }
      else{
      var r = new Array();
      for(var i=0;i<a.length;i++)
      if(this.addNode(a[i]))
      r.push(a[i]);
      }
      return r;
      },
      /*
      * hasNode
      * Checks if the given value is contained on the Graph
      * @return true if it is. False if it isn't.
      */
      hasNode: function(a){
      if(typeof a != "object"){
      for(var i=0;i<this.nodes.length;i++)
      if(this.nodes[i] == a)
      return true;
      return false;
      }
      else{
      for(var i=0;i<a.length;i++)
      if(!this.hasNode(a[i]))
      return false;
      return true;
      }
      },
      /*
      * removeNode
      */
      removeNode: function(a){
      if(typeof a != "object"){
      var i=0;
      while(i<this.nodes.length){
      if(this.nodes[i]==a){
      this.nodes.splice(i,1);
      i--;
      return a;
      }
      i++;
      }
      return false;
      }
      else{
      var r = new Array();
      for(var i=0;i<a.length;i++)
      if(this.removeNode(a[i]))
      r.push(a[i]);
      }
      return r;
      },
      /*
      * addEdge
      */
      addEdge: function(from,to){
      if(from&&to){
      if(!this.hasNode(from))
      this.addNode(from);
      if(typeof to == "object"){
      if(this.hasEdge(from)){
      var arr = this.edges[this.indexOfEdge(from)].targets.slice();
      for(var i=0;i<arr.length;i++)
      to.push(arr[i]);
      }
      to.sort();
      for(var i=1;i<to.length;i++)
      if(to[i]==to[i-1]){
      to.splice(i,1);
      i--;
      }
      for(var i=0;i<to.length;i++)
      !this.hasNode(to[i]) ? this.addNode(to[i]) : null;
      }
      else if(!this.hasNode(to))
      this.addNode(to);
      if(!this.hasEdge(from)){
      this.edges.push(new Edge(from,to));
      return "added edge:["+from+"]->["+to.toString()+"]";
      }
      else{
      if(this.edges[this.indexOfEdge(from)].addTarget(to))
      return "updated edge:["+from+"] with ["+to.toString()+"]";
      }
      }
      return false;
      },
      /*
      * addEdges
      */
      addEdges: function(edges){
      for(var i=0;i<edges.length;i++)
      this.addEdge(edges[i][0],edges[i][1]);
      },
      /*
      * indexOfEdge
      */
      indexOfEdge: function(from){
      for(var i=0;i<this.edges.length;i++)
      if(from==this.edges[i].node)
      return i;
      return false;
      },
      /*
      * hasEdge
      */
      hasEdge: function(which){
      for(var i=0;i<this.edges.length;i++)
      if(which==this.edges[i].node)
      return true;
      return false;
      },
      /*
      * removeEdge
      */
      removeEdge: function(which){
      if(this.hasEdge(which)){
      this.edges.splice(this.indexOfEdge(which),1);
      return which;
      }
      return false;
      },
      /*
      *
      */
      removeEdgeTarget: function(node,target){
      if(this.hasEdge(node)){
      if(typeof target != "object"){
      var arr = this.edges[this.indexOfEdge(node)].targets.slice();
      for(var i=0;i<arr.length;i++)
      if(arr[i] == target){
      arr.splice(i,1);
      this.edges[this.indexOfEdge(node)].targets = arr;
      return target;
      }
      return false;
      }
      else{
      var r = new Array();
      for(var i=0;i<target.length;i++)
      if(this.removeEdgeTarget(node,target[i]))
      r.push(target[i]);
      if(r.length==0)
      return false;
      else
      return r;
      }
      }
      else
      return false;
      }
      /*
      TODO: {} Add functions to:

      Build matrix
      Draw matrix

      Update functions:

      removeNode:
      - Find if there's an edge object whos node is the node to be
      removed, and remove that edge object.
      - Find if there's edges pointing to the node to be removed and
      remove those targets from their edge objects
      }
      */
      }

  18. Peter says:

    Hi,

    great lib!

    I want to use your library for the visualization of protein interactions. But i think i noticed a bug. When you display a graph and scroll the page afterwards the tooltips are not placed where they should. They change position according to the visual part of the page.

    Do you know this problem or may it be my mistake.

    Hopefully you can fix it.

    • Peter says:

      I just figured out, that this only happens when a page is scrollable, so only when the graph is stored on a big page or a too small iframe. When you use a big enough iframe it works well.

      • entropy says:

        I fixed that without needing to resort to an iframe.
        I changed the tooltip code to this:

        Raphael.el.tooltip = function (tp) {
        this.tp = tp;
        this.tp.o = {x: 0, y: 0};
        this.tp.hide();
        this.hover(
        function(event){
        this.mousemove(function(event){
        this.tp.translate(event.clientX -
        this.tp.o.x,event.clientY - this.tp.o.y + $(window).scrollTop());
        this.tp.o = {x: event.clientX, y: event.clientY + $(window).scrollTop()};
        });
        this.tp.show().toFront();
        },
        function(event){
        this.tp.hide();
        this.unmousemove();
        });
        return this;
        };

        • Peter says:

          Hi entropy,

          yes this fixes the problem of a page moving with a scrollbar, but only if the Raphael svg starts at the coordinates x=o,y=0 of the page, otherwise you have to adjust the position of a tooltip in the render function with negative values. E.g. if you have a toolbar on the left side.

          but thanks, it helps a lot.

          Peter

  19. ivan says:

    Is there a way to have two distinct nodes with identical labels? All the documented examples use the node label to uniquely identify the node..

  20. Moritz says:

    Hey

    Really great job!

    I was just wondering if there is a nice way to do edges from one node to itself. So far, the edge will be a really small loop, I think it would be better to have a bigger loop, that one can actually see.

  21. David says:

    Really nice job! I was wondering whether there is a way to customize the edges, say I want change the length of the edge. Could you provide me a way of doing that?

    Thanks!

  22. Iuri Sampaio says:

    How would I add auxiliary boxes for each node with text and html tags on it.
    I was reading the render proc bellow and it actually make a extra box to pop up once we hover the mouse under the node.
    How ever I could not figure out how to amend it in order to write html text within those boxes.
    Where can I find samples or tutorials for it?
    var render = function(r, n) {
    /* the Raphael set is obligatory, containing all you want to display */
    var set = r.set().push(
    /* custom objects go here */
    r.rect(n.point[0]-30, n.point[1]-16, 80, 80)
    .attr({“fill”: “#fa8″, “stroke-width”: 2, r : “9px”}))
    .push(r.text(n.point[0], n.point[1] + 30, n.label)
    .attr({“font-size”:”20px”}));
    /* custom tooltip attached to the set */
    set.items.forEach(
    function(el) {
    el.tooltip(r.set().push(r.rect(0, 0, 50, 100)
    .attr({“fill”: “#fdddc”, “stroke-width”: 1, r : “10px”})
    .attr({“font-size”:”20px”})))});
    return set;
    };

  23. vaibhav says:

    is there any way to clear graph?
    i have a list of records for each it will generate graph when i will click on next for next record’s graph it will still showing old one’s nodes and edges.

    so how to clear the old graph or replace with new graph?

  24. pihentagy says:

    How can I simulate dragging? (This way I can simulate manual aligning for example)

  25. vaibhav says:

    Is there any possibility to add custom attrs on edge?

  26. Bernal says:

    Is there any possibility to add custom objects into the node, like a check box, or a button?

    Any Idea! thanks!

  27. areszen says:

    hi!
    Really great library! I noticed that the nodes usually start from right area (parent) to left area (children) and bottom area (parent) to top area(children). Is there any way to do the opposite? From top to bottom and left to right? Really appreciate your help.

  28. Paulo Fernando says:

    Does anyone knows how to change the color of the nodes? I need to sort the nodes using color, but I don’t know how to do that yet :/ I really appreciate if anyone can help me.
    Thanks.

  29. krolser says:

    Hello.
    This is really great library! Thank you for work.

    I have one question.
    I create roadmap of tasks http://zombieisland.org/eng/tasks-roadmap.

    How do I place items like this?
    http://zombieisland.org/img/zi-tasks-roadmap-manual.jpg

  30. Tom says:

    Hi Johann,
    can we set the distance of nodes based on label values.
    Thanx a lot in advance.

  31. Chris says:

    I was playing with your library and I think it is great. Very powerful and I love the direct representation.

    One thing I did notice is that when dealing with directed edges, if more than one terminates at a particular node, it is sometimes impossible to tell which edge is directed. This is because they both terminate at the same point on the node and the arrows overlap. Is this something you have looked into fixing?

  32. ZhiWei says:

    When I use the code below I found it’s not easy to locate the rectangle and the text position exactly where I can see on my screen , especially when the n.label is very long or has many word break.

    I try to modify the parameters on the functions : r.rect() and r.text() .
    After I do that, the UI behavior is so strange that I can’t display the node normally.

    var render = function(r, n) {
    /* the Raphael set is obligatory, containing all you want to display */
    var set = r.set().push(
    /* custom objects go here */
    r.rect(n.point[0], n.point[1]+13, 162, 86)
    .attr({“fill”: “#fa8″, “stroke-width”: 2, r : “9px”}))
    .push(r.text(n.point[0] + 80, n.point[1] + 60, n.label)
    .attr({“font-size”:”20px”}));
    return set;
    };

    g.addNode(“bkn”, {label:”12tdkfiekdasfjldfdsadfB1″,render:render});
    g.addNode(“bkn”,{label:”123\n456\n789234234\n”,render:render});

  33. Hi,
    How can I add click event to any node in the graph. I need to do it urgently. Please answer.
    Also i cannot see any ID in the eclipse created for the nodes? Is there any way to add IDs to the node elements?

    Thanks.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">