Skip to content

Instantly share code, notes, and snippets.

@salami162
Created May 30, 2013 04:00
Show Gist options
  • Save salami162/5675647 to your computer and use it in GitHub Desktop.
Save salami162/5675647 to your computer and use it in GitHub Desktop.
An object created based on D3.js force graph. Thought worth to share it. e.g. var graph = new forceGraph ('#chart', 1000, 800, backboneView); graph.draw({ nodes : [...], links : [...] });
define([
'underscore',
'd3.v3'
], function (_, d3) {
function forceGraph (selector, width, height, view) {
this.margin = [20, 20, 20, 20];
this.r = 10;
this.width = (width - this.margin[1] - this.margin[3]) || 800;
this.height = (height - this.margin[0] - this.margin[2]) || 600;
this.svgChart = d3.select(selector)
.attr('width', this.width)
.attr('height', this.height);
this.force = d3.layout.force()
.gravity(0.05)
.charge(-150)
.size([this.width, this.height]);
this.responseView = view;
return this;
}
forceGraph.prototype.setDimensions = function (width, height) {
this.width = width || this.width;
this.height = height || this.height;
this.svgChart.attr('width', this.width)
.attr('height', this.height);
this.force.size([this.width, this.height]);
};
forceGraph.prototype._createMarkers = function () {
this.svgChart.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#999');
this.svgChart.append('svg:defs').append('svg:marker')
.attr('id', 'start-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M10,-5L0,0L10,5')
.attr('fill', '#999');
return this;
};
forceGraph.prototype._displayWeight = function (d) {
var self = this;
var links = this.svgChart.selectAll('path.link.' + d.name)
.classed('highlight', true);
_(links[0]).forEach(function(slink) {
var pathText = self.svgChart.append("text")
.attr('class','link_text')
.attr('x', 20)
.attr('dy', 25);
pathText.append('textPath')
.attr('xlink:href', function() {
return '#' + slink.id;
})
.attr('class','text_path')
.style('fill','#000')
.text(function(text, i) {
var ids = slink.id.split('_');
return ids.pop();
});
});
return this;
};
forceGraph.prototype._removeDisplay = function (d) {
this.svgChart
.selectAll('path.link.' + d.name)
.classed('highlight', false);
this.svgChart
.selectAll('text.link_text')
.remove();
return this;
};
forceGraph.prototype._addSticky = function (d) {
d.fixed = true;
d3.select(this).select('circle')
.classed('sticky', true);
return this;
};
forceGraph.prototype._onTick = function () {
var self = this;
var link = this.svgChart.selectAll('.link');
var node = this.svgChart.selectAll('.node');
// draw directed edges with proper padding from node centers
link.attr('d', function(d) {
var dsourcex = Math.min( self.width, Math.max(0, d.source.x) );
var dsourcey = Math.min( self.width, Math.max(0, d.source.y) );
var dtargetx = Math.min( self.width, Math.max(0, d.target.x) );
var dtargety = Math.min( self.width, Math.max(0, d.target.y) );
var deltaX = dtargetx - dsourcex,
deltaY = dtargety - dsourcey,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = d.left ? 17 : 12,
targetPadding = d.right ? 17 : 12,
sourceX = dsourcex + (sourcePadding * normX),
sourceY = dsourcey + (sourcePadding * normY),
targetX = dtargetx - (targetPadding * normX),
targetY = dtargety - (targetPadding * normY);
return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
});
node.attr("cx", function(d) { return d.x = Math.max(self.r, Math.min(self.width - self.r, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(self.r, Math.min(self.height - self.r, d.y)); });
node.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
return this;
};
forceGraph.prototype.draw = function (data) {
var self = this;
var longestPath = _(data.links).max(function (path) {
return path.value;
});
var ratio = 750 / longestPath.value;
this.force
.distance(function (d) {
return d.value * ratio;
});
this.force
.nodes(data.nodes)
.links(data.links)
.start();
this._createMarkers();
var link = this.svgChart.selectAll('.link')
.data(data.links)
.enter().append('svg:path')
.attr('class', function (d) {
return 'link ' + d.source.name + ' ' + d.target.name;
})
.attr('id', function (d) {
return d.source.name + '_' + d.target.name + '_' + d.value;
})
.style("stroke-width", function(d) {
return (d.value / 20);
})
.style('marker-start', function(d) {
return d.left ? 'url(#start-arrow)' : '';
})
.style('marker-end', function(d) {
return d.right ? 'url(#end-arrow)' : '';
});
var node = this.svgChart.selectAll('.node')
.data(data.nodes)
.enter().append('svg:g')
.attr('class', function (d) {
return 'node ' + d.name;
})
.call(this.force.drag)
.on( 'mouseover', _.bind(this._displayWeight, this) )
.on( 'mouseout', _.bind(this._removeDisplay, this) )
.on( 'mousedown', this._addSticky)
.on( 'dblclick', function (d) {
self.responseView.trigger('fetchDashboard', d.name);
});
node.append('circle')
.attr('r', this.r);
node.append('text')
.attr('dx', 16)
.attr('dy', '.95em')
.text(function(d) { return d.name; });
this.force.on( 'tick', _.bind(this._onTick, this) );
};
return forceGraph;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment