683 lines
25 KiB
JavaScript
683 lines
25 KiB
JavaScript
|
|
(function(window){
|
|
|
|
// this serves as the end of an edge when creating a link
|
|
function EdgeEnd(pos_x,pos_y){
|
|
this.x = pos_x;
|
|
this.y = pos_y;
|
|
|
|
this.get_pos = function(){
|
|
return new Vec2(this.x,this.y);
|
|
}
|
|
}
|
|
|
|
// connectors are start and end point of edge creation drags.
|
|
function Connector(graph,node,pos_x,pos_y){
|
|
var visible = false;
|
|
var conn_circle = graph.r.circle(node.get_pos().x + pos_x, node.get_pos().y + pos_y,4);
|
|
conn_circle.attr({'opacity':0, 'fill':graph.style.outline,'stroke':'none'});
|
|
conn_circle.transform(graph.get_transform());
|
|
var self = this;
|
|
|
|
this.update_pos = function(){
|
|
conn_circle.attr({'cx':node.get_pos().x + pos_x, 'cy':node.get_pos().y + pos_y});
|
|
}
|
|
this.get_pos = function(){
|
|
return new node.get_pos().add_xy(pos_x,pos_y);
|
|
}
|
|
function hover_in(){
|
|
if(!visible){ return;}
|
|
conn_circle.animate({'r':8},300,'elastic');
|
|
if(graph.creating_edge){
|
|
graph.target_node = node;
|
|
conn_circle.animate({'fill':graph.style.white,'stroke':graph.style.outline,'stroke-width':2},100,'linear');
|
|
}
|
|
}
|
|
function hover_out(){
|
|
if(!visible){ return;}
|
|
conn_circle.animate({'r':4, 'fill':graph.style.outline, 'stroke':'none'},400,'linear');
|
|
graph.target_node = null;
|
|
}
|
|
conn_circle.hover(hover_in,hover_out);
|
|
|
|
|
|
var drag_down = function(){
|
|
if(!visible){ return; }
|
|
self.ox = conn_circle.attr("cx");
|
|
self.oy = conn_circle.attr("cy");
|
|
self.edge_start = new EdgeEnd(self.ox,self.oy);
|
|
self.edge_end = new EdgeEnd(self.ox, self.oy);
|
|
self.edge_tmp = new GraphEdge(graph,'',self.edge_start,self.edge_end,true);
|
|
graph.creating_edge = true;
|
|
}
|
|
var drag_move = function(dx,dy){
|
|
if(!visible){ return; }
|
|
self.edge_end.x = self.ox + dx;
|
|
self.edge_end.y = self.oy + dy;
|
|
self.edge_tmp.update();
|
|
}
|
|
var drag_up = function(){
|
|
if(!visible){ return; }
|
|
graph.creating_edge = false;
|
|
self.edge_tmp.remove();
|
|
if(graph.target_node){
|
|
edge_prop = GraphEdge.creation_callback(node,graph.target_node);
|
|
if(edge_prop){
|
|
new GraphEdge(graph,edge_prop.label, node,graph.target_node);
|
|
}
|
|
}
|
|
}
|
|
conn_circle.drag(drag_move,drag_down,drag_up);
|
|
|
|
function show(){
|
|
if(!visible){
|
|
conn_circle.animate({'opacity':1}, 100, 'linear');
|
|
visible = true;
|
|
}
|
|
}
|
|
function hide(){
|
|
if(visible){
|
|
conn_circle.animate({'opacity':0}, 100, 'linear');
|
|
visible = false;
|
|
}
|
|
}
|
|
this.show = show;
|
|
this.hide = hide;
|
|
}
|
|
|
|
function Graph(r,style){
|
|
var self = this;
|
|
var nodes = []; // list of all nodes in the graph
|
|
var edges = []; // list of all edges in the graph
|
|
var graph = {}; // graph[n1.uid][n2.uid] -> list of all edges from n1 to n2
|
|
var links = {} // links[n.uid] -> list of all edges from or to n
|
|
var uid = 1; // all nodes and edges have an uid used to order their display when they are curved
|
|
|
|
self.creating_edge = false; // true if we are dragging a new edge onto a node
|
|
self.target_node = null; // this holds the target node when creating an edge and hovering a connector
|
|
self.r = r; // the raphael instance
|
|
self.style = style; // definition of the colors, spacing, fonts, ... used by the elements
|
|
var tr_x = 0, tr_y = 0; // global translation coordinate
|
|
var scale = 1; // global scale
|
|
|
|
var background = r.rect(0,0,'100%','100%').attr({'fill':'white', 'stroke':'none', 'opacity':0});
|
|
|
|
// return the global transform of the scene
|
|
this.get_transform = function(){
|
|
return "T"+tr_x+","+tr_y
|
|
}
|
|
|
|
// translate every element of the graph except the background.
|
|
// elements inserted in the graph after a translate_all() must manually apply transformation
|
|
// via get_transform()
|
|
var translate_all = function(dx,dy){
|
|
tr_x += dx;
|
|
tr_y += dy;
|
|
tstr = self.get_transform();
|
|
|
|
r.forEach(function(el){
|
|
if(el != background){
|
|
el.transform(tstr);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Graph translation when background is dragged
|
|
var bg_drag_down = function(){
|
|
px = py = 0;
|
|
}
|
|
var bg_drag_move = function(x,y){
|
|
var dx = x - px;
|
|
var dy = y - py;
|
|
px = x;
|
|
py = y;
|
|
translate_all(dx,dy);
|
|
}
|
|
var bg_drag_up = function(){}
|
|
background.drag( bg_drag_move, bg_drag_down, bg_drag_up);
|
|
|
|
$(background.node).bind('mousewheel',function(event,delta){
|
|
translate_all(0,delta*20);
|
|
});
|
|
|
|
|
|
//adds a node to the graph and sets its uid.
|
|
this.add_node = function (n){
|
|
nodes.push(n);
|
|
n.uid = uid++;
|
|
};
|
|
//return the list of all nodes in the graph
|
|
this.get_node_list = function(){
|
|
return nodes;
|
|
};
|
|
//adds an edge to the graph and sets its uid
|
|
this.add_edge = function (n1,n2,e){
|
|
edges.push(e);
|
|
e.uid = uid++;
|
|
if(!graph[n1.uid]) graph[n1.uid] = {};
|
|
if(!graph[n1.uid][n2.uid]) graph[n1.uid][n2.uid] = [];
|
|
if(!links[n1.uid]) links[n1.uid] = [];
|
|
if(!links[n2.uid]) links[n2.uid] = [];
|
|
|
|
graph[n1.uid][n2.uid].push(e);
|
|
links[n1.uid].push(e);
|
|
if(n1 != n2){
|
|
links[n2.uid].push(e);
|
|
}
|
|
};
|
|
//removes an edge from the graph
|
|
this.remove_edge = function(edge){
|
|
edges = _.without(edges,edge);
|
|
var n1 = edge.get_start();
|
|
var n2 = edge.get_end();
|
|
links[n1.uid] = _.without(links[n1.uid],edge);
|
|
links[n2.uid] = _.without(links[n2.uid],edge);
|
|
graph[n1.uid][n2.uid] = _without(graph[n1.uid][n2.uid],edge);
|
|
}
|
|
//return the list of edges from n1 to n2
|
|
this.get_edge_list = function(n1,n2){
|
|
var list = [];
|
|
if(!graph[n1.uid]) return list;
|
|
if(!graph[n1.uid][n2.uid]) return list;
|
|
return graph[n1.uid][n2.uid];
|
|
};
|
|
//returns the list of all edge connected to n
|
|
this.get_linked_edge_list = function(n){
|
|
if(!links[n.uid]) return [];
|
|
return links[n.uid];
|
|
}
|
|
//return a curvature index so that all edges connecting n1,n2 have different curvatures
|
|
this.get_edge_curvature = function(n1,n2,e){
|
|
var el_12 = this.get_edge_list(n1,n2);
|
|
var c12 = el_12.length;
|
|
var el_21 = this.get_edge_list(n2,n1);
|
|
var c21 = el_21.length;
|
|
if(c12 + c21 == 1){ // only one edge
|
|
return 0;
|
|
}else{
|
|
var index = 0;
|
|
for(var i = 0; i < c12; i++){
|
|
if (el_12[i].uid < e.uid){
|
|
index++;
|
|
}
|
|
}
|
|
if(c21 == 0){ // all edges in the same direction
|
|
return index - (c12-1)/2.0;
|
|
}else{
|
|
return index + 0.5;
|
|
}
|
|
}
|
|
};
|
|
// Returns the angle in degrees of the edge loop. We do not support more than 8 loops on one node
|
|
this.get_loop_angle = function(n,e){
|
|
var loop_list = this.get_edge_list(n,n);
|
|
var lc = loop_list.length;
|
|
|
|
var slots = []; // the 8 angles where we can put the loops
|
|
for(var angle = 0; angle < 360; angle += 45){
|
|
slots.push(Vec2.new_polar_deg(1,angle));
|
|
}
|
|
|
|
//we assign to each slot a score. The higher the score, the closer it is to other edges.
|
|
var links = this.get_linked_edge_list(n);
|
|
for(var i = 0; i < links.length; i++){
|
|
var edge = links[i];
|
|
if(!edge.is_loop || edge.is_loop()){
|
|
continue;
|
|
}
|
|
var end = edge.get_end();
|
|
if (end == n){
|
|
end = edge.get_start();
|
|
}
|
|
var dir = end.get_pos().sub(n.get_pos()).normalize();
|
|
for(var s = 0; s < slots.length; s++){
|
|
var score = slots[s].dot(dir);
|
|
if(score < 0){
|
|
score = -0.2*Math.pow(score,2);
|
|
}else{
|
|
score = Math.pow(score,2);
|
|
}
|
|
if(!slots[s].score){
|
|
slots[s].score = score;
|
|
}else{
|
|
slots[s].score += score;
|
|
}
|
|
}
|
|
}
|
|
//we want the loops with lower uid to get the slots with the lower score
|
|
slots.sort(function(a,b){ return a.score < b.score ? -1: 1; });
|
|
|
|
var index = 0;
|
|
for(var i = 0; i < links.length; i++){
|
|
var edge = links[i];
|
|
if(!edge.is_loop || !edge.is_loop()){
|
|
continue;
|
|
}
|
|
if(edge.uid < e.uid){
|
|
index++;
|
|
}
|
|
}
|
|
index = index % slots.length;
|
|
|
|
return slots[index].angle_deg();
|
|
}
|
|
}
|
|
|
|
// creates a new Graph Node on Raphael document r, centered on [pos_x,pos_y], with label 'label',
|
|
// and of type 'circle' or 'rect', and of color 'color'
|
|
function GraphNode(graph,pos_x, pos_y,label,type,color){
|
|
var self = this;
|
|
var r = graph.r;
|
|
var sy = graph.style.node_size_y;
|
|
var sx = graph.style.node_size_x;
|
|
var node_fig = null;
|
|
var selected = false;
|
|
this.update_time = 0;
|
|
this.connectors = [];
|
|
this.uid = 0;
|
|
|
|
graph.add_node(this);
|
|
|
|
if(type == 'circle'){
|
|
node_fig = r.ellipse(pos_x,pos_y,sx/2,sy/2);
|
|
}else{
|
|
node_fig = r.rect(pos_x-sx/2,pos_y-sy/2,sx,sy);
|
|
}
|
|
node_fig.attr({'fill':color, 'stroke':graph.style.outline,'stroke-width':1,'cursor':'pointer'});
|
|
node_fig.transform(graph.get_transform());
|
|
|
|
$(node_fig.node).addClass('foobar');
|
|
|
|
var node_label = r.text(pos_x,pos_y,label);
|
|
node_label.attr({'fill':graph.style.text,'cursor':'pointer'});
|
|
node_label.transform(graph.get_transform());
|
|
$(node_label.node).css('text-shadow',"1px 2px 3px rgba(0,0,0,0.3)");
|
|
|
|
|
|
// sets the center position of the node
|
|
var set_pos = function(pos){
|
|
if(type == 'circle'){
|
|
node_fig.attr({'cx':pos.x,'cy':pos.y});
|
|
}else{
|
|
node_fig.attr({'x':pos.x-sx/2,'y':pos.y-sy/2});
|
|
}
|
|
node_label.attr({'x':pos.x,'y':pos.y});
|
|
for(var i = 0; i < self.connectors.length; i++){
|
|
self.connectors[i].update_pos();
|
|
}
|
|
var edges = graph.get_linked_edge_list(self);
|
|
for(var i = 0; i < edges.length; i++){
|
|
edges[i].update();
|
|
}
|
|
}
|
|
// returns the figure used to draw the node
|
|
var get_fig = function(){
|
|
return node_fig;
|
|
}
|
|
// returns the center coordinates
|
|
var get_pos = function(){
|
|
if(type == 'circle'){
|
|
return new Vec2(node_fig.attr('cx'), node_fig.attr('cy'));
|
|
}else{
|
|
return new Vec2(node_fig.attr('x') + sx/2, node_fig.attr('y') + sy/2);
|
|
}
|
|
}
|
|
// return the label string
|
|
var get_label = function(){
|
|
return node_label.attr("text");
|
|
}
|
|
// sets the label string
|
|
var set_label = function(text){
|
|
node_label.attr({'text':text});
|
|
}
|
|
var get_bound = function(){
|
|
if(type == 'circle'){
|
|
return new BEllipse(get_pos().x,get_pos().y,sx/2,sy/2);
|
|
}else{
|
|
return BRect.new_centered(get_pos().x,get_pos().y,sx,sy);
|
|
}
|
|
}
|
|
// selects this node and deselects all other nodes
|
|
var set_selected = function(){
|
|
if(!selected){
|
|
node_fig.attr({'stroke':graph.style.selected, 'stroke-width':2});
|
|
selected = true;
|
|
var nodes = graph.get_node_list();
|
|
for(var i = 0; i < nodes.length; i++){
|
|
if(nodes[i] != self){
|
|
nodes[i].set_not_selected();
|
|
}
|
|
}
|
|
for(var i = 0; i < self.connectors.length; i++){
|
|
self.connectors[i].show();
|
|
}
|
|
}
|
|
}
|
|
// deselect this node
|
|
var set_not_selected = function(){
|
|
if(selected){
|
|
node_fig.animate({'stroke':graph.style.outline,'stroke-width':1},100,'linear');
|
|
selected = false;
|
|
}
|
|
for(var i = 0; i < self.connectors.length; i++){
|
|
self.connectors[i].hide();
|
|
}
|
|
}
|
|
|
|
this.set_pos = set_pos;
|
|
this.get_pos = get_pos;
|
|
this.set_label = set_label;
|
|
this.get_label = get_label;
|
|
this.get_bound = get_bound;
|
|
this.get_fig = get_fig;
|
|
this.set_selected = set_selected;
|
|
this.set_not_selected = set_not_selected
|
|
|
|
|
|
//select the node and play an animation when clicked
|
|
var click_action = function(){
|
|
if(type == 'circle'){
|
|
node_fig.attr({'rx':sx/2 + 3, 'ry':sy/2+ 3});
|
|
node_fig.animate({'rx':sx/2, 'ry':sy/2},500,'elastic');
|
|
}else{
|
|
var cx = get_pos().x;
|
|
var cy = get_pos().y;
|
|
node_fig.attr({'x':cx - (sx/2) - 3, 'y':cy - (sy/2) - 3, 'ẃidth':sx+6, 'height':sy+6});
|
|
node_fig.animate({'x':cx - sx/2, 'y':cy - sy/2, 'ẃidth':sx, 'height':sy},500,'elastic');
|
|
}
|
|
set_selected();
|
|
}
|
|
node_fig.click(click_action);
|
|
node_label.click(click_action);
|
|
|
|
//move the node when dragged
|
|
var drag_down = function(){
|
|
this.opos = get_pos();
|
|
}
|
|
var drag_move = function(dx,dy){
|
|
// we disable labels when moving for performance reasons,
|
|
// updating the label position is quite expensive
|
|
// we put this here because drag_down is also called on simple clicks ... and this causes unwanted flicker
|
|
var edges = graph.get_linked_edge_list(self);
|
|
for(var i = 0; i < edges.length; i++){
|
|
edges[i].label_disable();
|
|
}
|
|
set_pos(this.opos.add_xy(dx,dy));
|
|
}
|
|
var drag_up = function(){
|
|
//we re-enable the
|
|
var edges = graph.get_linked_edge_list(self);
|
|
for(var i = 0; i < edges.length; i++){
|
|
edges[i].label_enable();
|
|
}
|
|
|
|
}
|
|
node_fig.drag(drag_move,drag_down,drag_up);
|
|
node_label.drag(drag_move,drag_down,drag_up);
|
|
|
|
//allow the user to create edges by dragging onto the node
|
|
function hover_in(){
|
|
if(graph.creating_edge){
|
|
graph.target_node = self;
|
|
}
|
|
}
|
|
function hover_out(){
|
|
graph.target_node = null;
|
|
}
|
|
node_fig.hover(hover_in,hover_out);
|
|
node_label.hover(hover_in,hover_out);
|
|
|
|
function double_click(){
|
|
GraphNode.double_click_callback(self);
|
|
}
|
|
node_fig.dblclick(double_click);
|
|
node_label.dblclick(double_click);
|
|
|
|
this.connectors.push(new Connector(graph,this,-sx/2,0));
|
|
this.connectors.push(new Connector(graph,this,sx/2,0));
|
|
this.connectors.push(new Connector(graph,this,0,-sy/2));
|
|
this.connectors.push(new Connector(graph,this,0,sy/2));
|
|
}
|
|
|
|
GraphNode.double_click_callback = function(node){
|
|
console.log("double click from node:",node);
|
|
}
|
|
|
|
// creates a new edge with label 'label' from start to end. start and end must implement get_pos_*,
|
|
// if tmp is true, the edge is not added to the graph, used for drag edges.
|
|
// replace tmp == false by graph == null
|
|
function GraphEdge(graph,label,start,end,tmp){
|
|
var self = this;
|
|
var r = graph.r;
|
|
var update_time = -1;
|
|
var curvature = 0; // 0 = straight, != 0 curved
|
|
var s,e; // positions of the start and end point of the line between start and end
|
|
var mc; // position of the middle of the curve (bezier control point)
|
|
var mc1,mc2; // control points of the cubic bezier for the loop edges
|
|
var elfs = graph.style.edge_label_font_size || 10 ;
|
|
var label_enabled = true;
|
|
this.uid = 0; // unique id used to order the curved edges
|
|
var edge_path = ""; // svg definition of the edge vector path
|
|
|
|
if(!tmp){
|
|
graph.add_edge(start,end,this);
|
|
}
|
|
|
|
//Return the position of the label
|
|
function get_label_pos(path){
|
|
var cpos = path.getTotalLength() * 0.5;
|
|
var cindex = Math.abs(Math.floor(curvature));
|
|
var mod = ((cindex % 3)) * (elfs * 3.1) - (elfs * 0.5);
|
|
var verticality = Math.abs(end.get_pos().sub(start.get_pos()).normalize().dot_xy(0,1));
|
|
verticality = Math.max(verticality-0.5,0)*2;
|
|
|
|
var lpos = path.getPointAtLength(cpos + mod * verticality);
|
|
return new Vec2(lpos.x,lpos.y - elfs *(1-verticality));
|
|
}
|
|
|
|
//Straight line from s to e
|
|
function make_line(){
|
|
return "M" + s.x + "," + s.y + "L" + e.x + "," + e.y ;
|
|
}
|
|
//Curved line from s to e by mc
|
|
function make_curve(){
|
|
return "M" + s.x + "," + s.y + "Q" + mc.x + "," + mc.y + " " + e.x + "," + e.y;
|
|
}
|
|
//Curved line from s to e by mc1 mc2
|
|
function make_loop(){
|
|
return "M" + s.x + " " + s.y +
|
|
"C" + mc1.x + " " + mc1.y + " " + mc2.x + " " + mc2.y + " " + e.x + " " + e.y;
|
|
}
|
|
|
|
//computes new start and end line coordinates
|
|
function update_curve(){
|
|
if(start != end){
|
|
if(!tmp){
|
|
curvature = graph.get_edge_curvature(start,end,self);
|
|
}else{
|
|
curvature = 0;
|
|
}
|
|
s = start.get_pos();
|
|
e = end.get_pos();
|
|
|
|
mc = s.lerp(e,0.5); //middle of the line s->e
|
|
var se = e.sub(s);
|
|
se = se.normalize();
|
|
se = se.rotate_deg(-90);
|
|
se = se.scale(curvature * graph.style.edge_spacing);
|
|
mc = mc.add(se);
|
|
|
|
if(start.get_bound){
|
|
var col = start.get_bound().collide_segment(s,mc);
|
|
if(col.length > 0){
|
|
s = col[0];
|
|
}
|
|
}
|
|
if(end.get_bound){
|
|
var col = end.get_bound().collide_segment(mc,e);
|
|
if(col.length > 0){
|
|
e = col[0];
|
|
}
|
|
}
|
|
|
|
if(curvature != 0){
|
|
edge_path = make_curve();
|
|
}else{
|
|
edge_path = make_line();
|
|
}
|
|
}else{ // start == end
|
|
var rad = graph.style.edge_loop_radius || 100;
|
|
s = start.get_pos();
|
|
e = end.get_pos();
|
|
|
|
var r = Vec2.new_polar_deg(rad,graph.get_loop_angle(start,self));
|
|
mc = s.add(r);
|
|
p = r.rotate_deg(90);
|
|
mc1 = mc.add(p.set_len(rad*0.5));
|
|
mc2 = mc.add(p.set_len(-rad*0.5));
|
|
|
|
if(start.get_bound){
|
|
var col = start.get_bound().collide_segment(s,mc1);
|
|
if(col.length > 0){
|
|
s = col[0];
|
|
}
|
|
var col = start.get_bound().collide_segment(e,mc2);
|
|
if(col.length > 0){
|
|
e = col[0];
|
|
}
|
|
}
|
|
edge_path = make_loop();
|
|
}
|
|
}
|
|
|
|
update_curve();
|
|
var edge = r.path(edge_path).attr({'stroke':graph.style.edge, 'stroke-width':2, 'arrow-end':'block-wide-long', 'cursor':'pointer'}).insertBefore(graph.get_node_list()[0].get_fig());
|
|
var labelpos = get_label_pos(edge);
|
|
var edge_label = r.text(labelpos.x, labelpos.y - elfs, label).attr({'fill':graph.style.edge_label, 'cursor':'pointer', 'font-size':elfs});
|
|
|
|
edge.transform(graph.get_transform());
|
|
edge_label.transform(graph.get_transform());
|
|
$(edge_label.node).css('text-shadow',"1px 2px 3px rgba(0,0,0,0.3)");
|
|
|
|
|
|
//since we create an edge we need to recompute the edges that have the same start and end positions as this one
|
|
if(!tmp){
|
|
var edges_start = graph.get_linked_edge_list(start);
|
|
var edges_end = graph.get_linked_edge_list(end);
|
|
var edges = edges_start.length < edges_end.length ? edges_start : edges_end;
|
|
for(var i = 0; i < edges.length; i ++){
|
|
if(edges[i] != self){
|
|
edges[i].update();
|
|
}
|
|
}
|
|
}
|
|
|
|
function label_enable(){
|
|
if(!label_enabled){
|
|
label_enabled = true;
|
|
edge_label.animate({'opacity':1},100,'linear');
|
|
self.update();
|
|
}
|
|
}
|
|
function label_disable(){
|
|
if(label_enabled){
|
|
label_enabled = false;
|
|
edge_label.animate({'opacity':0},100,'linear');
|
|
}
|
|
}
|
|
//update the positions
|
|
function update(){
|
|
update_curve();
|
|
edge.attr({'path':edge_path});
|
|
if(label_enabled){
|
|
var labelpos = get_label_pos(edge);
|
|
edge_label.attr({'x':labelpos.x, 'y':labelpos.y - 14});
|
|
}
|
|
}
|
|
//TODO remove from graph
|
|
function remove(){
|
|
edge.remove();
|
|
edge_label.remove();
|
|
if(!tmp){
|
|
graph.remove_edge(self);
|
|
}
|
|
}
|
|
|
|
function double_click(){
|
|
GraphEdge.double_click_callback(self);
|
|
}
|
|
edge.dblclick(double_click);
|
|
edge_label.dblclick(double_click);
|
|
|
|
|
|
this.label_enable = label_enable;
|
|
this.label_disable = label_disable;
|
|
this.update = update;
|
|
this.remove = remove;
|
|
this.is_loop = function(){ return start == end; };
|
|
this.get_start = function(){ return start; };
|
|
this.get_end = function(){ return end; };
|
|
}
|
|
|
|
GraphEdge.double_click_callback = function(edge){
|
|
console.log("double click from edge:",edge);
|
|
}
|
|
|
|
// this is the default edge creation callback. It is called before an edge is created
|
|
// It returns an object containing the properties of the edge.
|
|
// If it returns null, the edge is not created.
|
|
GraphEdge.creation_callback = function(start,end){
|
|
var edge_prop = {};
|
|
edge_prop.label = 'new edge!';
|
|
return edge_prop;
|
|
}
|
|
|
|
// returns a new string with the same content as str, but with lines of maximum 'width' characters.
|
|
// lines are broken on words, or into words if a word is longer than 'width'
|
|
function wordwrap( str, width) {
|
|
// http://james.padolsey.com/javascript/wordwrap-for-javascript/
|
|
width = width || 32;
|
|
var cut = true;
|
|
var brk = '\n';
|
|
if (!str) { return str; }
|
|
var regex = '.{1,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)');
|
|
return str.match( RegExp(regex, 'g') ).join( brk );
|
|
}
|
|
|
|
window.CuteGraph = Graph;
|
|
window.CuteNode = GraphNode;
|
|
window.CuteEdge = GraphEdge;
|
|
|
|
window.CuteGraph.wordwrap = wordwrap;
|
|
|
|
|
|
})(window);
|
|
|
|
/*
|
|
window.onload = function(){
|
|
//Example
|
|
var style = { "background" :'url("grid.png")',
|
|
"edge" :"#A0A0A0",
|
|
"edge_label" :"#555",
|
|
"text" :"#333",
|
|
"outline" :"#000",
|
|
"selected" :"#0097BE",
|
|
"gray" :"#DCDCDC",
|
|
"white" :"#FFF",
|
|
"node_size_x" : 110,
|
|
"node_size_y" : 80,
|
|
"edge_spacing" : 100,
|
|
"edge_label_font_size" : 10
|
|
"edge_loop_radius": 50 };
|
|
|
|
var r = new Raphael(document.getElementById("canvas_container"),'100%','100%');
|
|
|
|
var g = new CuteGraph(r,style);
|
|
|
|
var n1 = new CuteNode(g,100,250,'Hello World','circle',colors.white);
|
|
var n2 = new CuteNode(g,400,250,'Hello Planet','rect',colors.white);
|
|
var n3 = new CuteNode(g,250,400,'Lonely Node','rect',colors.gray);
|
|
var e1 = new CuteEdge(g,'test',n1,n2);
|
|
}*/
|
|
|