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();
Hi!
Is there any posibility of insert an image into a node? Or better, icons instead of rectangles.
Great job
Yes, for now you can write your own render function to render the nodes. Try something like this:
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.
Pingback: Dracula — рисуем графы на JavaScript | Записки программиста
Hi Philipp,
Thanks for you great work.
How can I remove a node or edge dynamically?
or as a feature request
I’m working on it
Did you already manage to add this feature? It’s highly needed
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
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…
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.
Hi Philipp,
is there the possiblity to add a label to an Edge?
that’s actually in the examle…
Hi … is der a method to draw other shapes(example: triangle or a rhombus).
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?
How do I register a click event on a node?
apparently, raphael has
c.node.onclick = function () {
c.attr(“fill”, “red”);
}
Hi, great work ! How can I have simple straight lines for the edges, instead of the (more CPU-intensive) pretty arcs ?
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(",");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!
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)
Hey Jokester, the license is MIT – so commercial products are not a problem at all! I should put a notice somewhere…
Cheers.
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
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!
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?
Sorry, no preview, second try:
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″/>
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.
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.
Sorry, currently there’s no such feature. I’d be thankful for any implementation though
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.)
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?
Would like to know wehther we ca group the nodes ?
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
is there any function to delete an edge?
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
}
*/
}
sorry about that .. i didn’t indented the code on the comment
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.
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.
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;
};
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
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..
Sigh, never mind. I just saw the example:
g.addNode(“id34″, { label : “Tomato” });
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.
It’s not yet build in, but on my list
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!
No, not yet, sorry!
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;
};
Here’s a link on the representation library that I used, it’s SVG instead of HTML but you can do similar things with it, check it out: http://raphaeljs.com/ (in the example, r is the Raphael object)
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?
How can I simulate dragging? (This way I can simulate manual aligning for example)
Is there any possibility to add custom attrs on edge?
Is there any possibility to add custom objects into the node, like a check box, or a button?
Any Idea! thanks!
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.
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.
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
Hi Johann,
can we set the distance of nodes based on label values.
Thanx a lot in advance.
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?
I fixed these issues locally. Are you interested in patches?
Sounds great, please send me the patches to strathausen(at)gmail(dot)com or just via GitHub. Thanks a lot!
Hi Chris,
Would you be able to send me the patch for this issue?
Thanks.
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});
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.