Skip to content

Instantly share code, notes, and snippets.

@jfensign
Created June 11, 2014 14:11
Show Gist options
  • Save jfensign/0ac827ebb568605f2f49 to your computer and use it in GitHub Desktop.
Save jfensign/0ac827ebb568605f2f49 to your computer and use it in GitHub Desktop.
D3 force-directed graph Class
function forceGraph(opts) {
opts = opts || {};
opts.node = opts.node || {};
opts.link = opts.link || {};
var
Self = this,
defaults = {
el: opts.el,
node: {
click: opts.node.click || function(d) {
console.log(d);
},
dblclick: opts.node.dblclick || function(d) {
console.log(d);
},
gravity: opts.node.gravity || 2.8
},
link: {
click: opts.link.click || function(d) {
console.log(d);
},
dblclick: opts.link.dblclick || function(d) {
console.log(d);
},
distance: opts.link.distance || 25
}
},
extend = function() {
_.extend(defaults, opts)
}();
this.set = function(obj) {
_.extend(defaults, obj)
return Self.update();
};
this.addNode = function (opts) {
nodes.push(opts)
return Self.update();
};
this.hideLinks = function() {
$(".link").hide()
};
this.showLinks = function() {
$(".link").show()
return Self;
};
this.removeNode = function (id) {
var
i = 0,
n = findNode(id);
while (i < links.length) {
if((links[i]['source'] == n)||
(links[i]['target'] == n)) {
links.splice(i,1)
}
else ++i;
}
nodes.splice(findNodeIndex(id),1);
return Self.update();
}
this.addLink = function (source, target, type) {
links.push({
"source":findNode(source),
"target":findNode(target),
"type": type || null
});
return Self.update();
}
this.getNodes = function() {
return Self.nodes;
}
this.getLinks = function() {
return Self.links;
}
this.clearLinks = function() {
Self.links.length = 0;
return Self;
}
var
findLink = this.findLink = function(source, target) {
if(links) {
for(var i in links) {
if(links[i].source == source && links[i].target == target)
return links[i];
}
}
},
findNode = this.findNode = function(id) {
for (var i in nodes) {
if (nodes[i]["id"] === id)
return nodes[i]
}
},
findNodeIndex = this.findNodeIndex = function(id) {
for (var i in nodes) {
if (nodes[i]["id"] === id)
return i;
}
},
w = $(defaults.el).innerWidth(),
h = $(defaults.el).innerHeight(),
markerWidth = 6,
markerHeight = 6,
cRadius = 10,
refX = cRadius + (markerWidth * 1.2),
refY = -Math.sqrt(cRadius),
drSub = cRadius + refY,
vis = this.vis = d3.
select(defaults.el).
append("svg:svg").
attr("width", w).
attr("height", h),
force = this.force = d3.
layout.
force().
gravity(defaults.node.gravity || 4.8).
linkDistance(defaults.link.distance || 25).
charge(-4000).
size([w, h]),
nodes = this.nodes = force.nodes(),
links = this.links = force.links();
vis.
append("svg:defs").
selectAll("marker").
data(["forward", "backward", "resolved"]).
enter().append("svg:marker").
attr("id", String).
attr("viewBox", "5 -5 8 12").
attr("refX", refX).
attr("refY", refY).
attr("markerWidth", markerWidth).
attr("markerHeight", markerHeight).
attr("orient", "auto").
append("path").
attr("d", "M0,-5L10,0L0,5")
this.update = function () {
var
node = vis.
selectAll("circle").
data(d3.values(nodes), function(d) {
return d.id
}).
on("click", defaults.node.click),
nodeEnter = node.
enter().
insert("g").
attr("class", "node").
call(force.drag);
nodeEnter.
append("circle").
attr("r", function(d) {
var
max = 50,
t_two = (d.contentCount * 2);
return ((d.contentCount === 0 || !d.contentCount)
? 1
: t_two>max?max:t_two)
}).
attr("fill", function(d) {
return d.color || "#CCC"
}).
attr("id", function(d) {
return d._id
});
node.exit().remove();
var
link = vis.
selectAll("path.link").
data(links, function(d) {
return d.source.id + "-" + d.target.id;
}),
linkEnter = link.
enter().
insert("path").
attr("class", function (d) {
return d.type
? ("link " + (d.type == "document" ? d.type : ""))
: ("link " + (d.target.index < d.source.index
? "backward"
: "forward" ))
}).
attr("marker-end", function (d) {
return d.type
? ""
: "url(#" + (d.target.index < d.source.index
? "backward"
: "forward") + ")"
}).
style("stroke-width", 1.0).
attr("stroke-dasharray", 5);
link.exit().remove()
var
text = vis.
selectAll("text").
data(force.nodes()),
textEnter = text.enter();
textEnter.
append("text").
attr("x", 0).
attr("y", ".51em").
attr("class", "nodeText").
text(function (d) {
return (d.id + (d.contentCount
? "(" + d.contentCount + ")"
: ""))
});
text.exit().remove();
force.
on("tick", function() {
link.attr("d", function(d) {
var
dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy),
val = [
("M" + d.source.x),
(d.source.y + "A" + dr),
(dr + " 0 0,1 " + d.target.x),
d.target.y
].
join(",");
return val;
});
node.
attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.
attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
})
}).
start();
setTimeout(function() { force.stop(); }, 2000);
return Self;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment