|
// Generated by CoffeeScript 1.10.0 |
|
(function() { |
|
var ANIMATION_DELAY, ANIMATION_DURATION, C, R; |
|
|
|
R = 20; |
|
|
|
C = 0.4; |
|
|
|
ANIMATION_DURATION = 1400; |
|
|
|
ANIMATION_DELAY = 400; |
|
|
|
window.NodeLink = Backbone.D3View.extend({ |
|
tagName: 'svg', |
|
initialize: function() { |
|
var throttled_render, zoom, zoomable_layer; |
|
this.d3el.classed('node_link', true); |
|
this.link_color = d3.scale.ordinal().range(['#ff7f0e', '#9467bd', '#8c564b', '#1f77b4', '#2ca02c', '#d62728', '#e377c2', '#bcbd22', '#17becf']); |
|
this.node_color = d3.scale.ordinal().domain(['class', 'instance', 'term']).range(['#ff7f0e', '#9467bd', '#8c564b', '#7f7f7f']); |
|
this.defs = this.d3el.append('defs'); |
|
zoomable_layer = this.d3el.append('g'); |
|
zoom = d3.behavior.zoom().scaleExtent([-Infinity, Infinity]).on('zoom', function() { |
|
return zoomable_layer.attr({ |
|
transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")" |
|
}); |
|
}); |
|
this.d3el.call(zoom); |
|
this.graph_layer = zoomable_layer.append('g'); |
|
this.links_layer = this.graph_layer.append('g'); |
|
this.nodes_layer = this.graph_layer.append('g'); |
|
this.legend = this.d3el.append('g').attr({ |
|
"class": 'legend' |
|
}); |
|
this.legend.append('rect').attr({ |
|
"class": 'legend_box' |
|
}); |
|
throttled_render = _.debounce(((function(_this) { |
|
return function() { |
|
return _this.render(); |
|
}; |
|
})(this)), 800, { |
|
trailing: true |
|
}); |
|
return this.listenTo(this.model, 'change:graph', throttled_render); |
|
}, |
|
render: function() { |
|
var PAD_MULTIPLIER, arrowheads, d3cola, dag, dag_links, dag_nodes_index, drag, enter_arrowheads, enter_legend_arrowheads, enter_legend_items, enter_legend_links, enter_links, enter_nodes, graph, height, i, j, l, legend_arrowheads, legend_height, legend_items, legend_width, len, len1, len2, len3, len4, len5, levels, link_types, links, m, n, nn, nodes, o, p, q, r, ref, ref1, ref2, ref3, ref4, topological_order, width; |
|
width = this.el.getBoundingClientRect().width; |
|
height = this.el.getBoundingClientRect().height; |
|
this.graph_layer.attr({ |
|
transform: "translate(0," + (-2 * R) + ") rotate(-10," + (width / 2) + "," + (height / 2) + ")" |
|
}); |
|
graph = this.model.get('graph'); |
|
this.graph = graph; |
|
|
|
/* extract the Directed Acyclic Graph composed by isA and instanceOf links */ |
|
dag_links = []; |
|
dag_nodes_index = {}; |
|
graph.links.forEach(function(d) { |
|
if (d.source.dummy || d.full_name === 'isA' || d.full_name === 'instanceOf') { |
|
dag_links.push(d); |
|
dag_nodes_index[d.source.id] = d.source; |
|
return dag_nodes_index[d.target.id] = d.target; |
|
} |
|
}); |
|
dag = { |
|
nodes: Object.keys(dag_nodes_index).map(function(k) { |
|
return dag_nodes_index[k]; |
|
}), |
|
links: dag_links |
|
}; |
|
|
|
/* store the node index into the node itself */ |
|
ref = dag.nodes; |
|
for (i = j = 0, len = ref.length; j < len; i = ++j) { |
|
n = ref[i]; |
|
n.i = i; |
|
} |
|
|
|
/* store neighbor nodes into each node */ |
|
ref1 = dag.nodes; |
|
for (i = m = 0, len1 = ref1.length; m < len1; i = ++m) { |
|
n = ref1[i]; |
|
n.in_neighbors = []; |
|
n.out_neighbors = []; |
|
} |
|
ref2 = dag.links; |
|
for (o = 0, len2 = ref2.length; o < len2; o++) { |
|
l = ref2[o]; |
|
l.source.out_neighbors.push(l.target); |
|
l.target.in_neighbors.push(l.source); |
|
} |
|
|
|
/* compute longest distances */ |
|
topological_order = tsort(dag.links.map(function(l) { |
|
return [l.source.i, l.target.i]; |
|
})); |
|
ref3 = dag.nodes; |
|
for (p = 0, len3 = ref3.length; p < len3; p++) { |
|
n = ref3[p]; |
|
n.longest_dist = -Infinity; |
|
} |
|
graph.dummy_root.longest_dist = 0; |
|
for (q = 0, len4 = topological_order.length; q < len4; q++) { |
|
i = topological_order[q]; |
|
n = dag.nodes[i]; |
|
ref4 = n.out_neighbors; |
|
for (r = 0, len5 = ref4.length; r < len5; r++) { |
|
nn = ref4[r]; |
|
if (nn.longest_dist < n.longest_dist + 1) { |
|
nn.longest_dist = n.longest_dist + 1; |
|
} |
|
} |
|
} |
|
|
|
/* CONSTRAINTS */ |
|
graph.constraints = []; |
|
graph.nodes.sort(function(a, b) { |
|
if ((a.i != null) && (b.i != null)) { |
|
return a.i - b.i; |
|
} else if (a.i != null) { |
|
return -1; |
|
} else { |
|
return 1; |
|
} |
|
}); |
|
|
|
/* create the alignment contraints */ |
|
levels = _.uniq(dag.nodes.map(function(n) { |
|
return n.longest_dist; |
|
})); |
|
levels.forEach(function(depth) { |
|
return graph.constraints.push({ |
|
type: 'alignment', |
|
axis: 'y', |
|
offsets: dag.nodes.filter(function(n) { |
|
return n.longest_dist === depth; |
|
}).map(function(n) { |
|
return { |
|
node: n.i, |
|
offset: 0 |
|
}; |
|
}) |
|
}); |
|
}); |
|
PAD_MULTIPLIER = 3.5; |
|
|
|
/* create the position contraints */ |
|
levels.forEach(function(depth, i) { |
|
var n1, n2; |
|
if (i < levels.length - 1) { |
|
n1 = _.find(dag.nodes, function(n) { |
|
return n.longest_dist === depth; |
|
}); |
|
n2 = _.find(dag.nodes, function(n) { |
|
return n.longest_dist === depth + 1; |
|
}); |
|
return graph.constraints.push({ |
|
axis: 'y', |
|
left: n1.i, |
|
right: n2.i, |
|
gap: 2 * R |
|
}); |
|
} |
|
}); |
|
link_types = d3.map(graph.links, function(d) { |
|
return d.full_name; |
|
}); |
|
link_types.remove('undefined'); |
|
link_types.remove('isA'); |
|
link_types.remove('instanceOf'); |
|
link_types.remove('denotes'); |
|
link_types = link_types.keys(); |
|
link_types = ['isA', 'instanceOf', 'denotes'].concat(link_types); |
|
this.link_color.domain(link_types); |
|
arrowheads = this.defs.selectAll('.arrowhead').data(link_types, function(d) { |
|
return d; |
|
}); |
|
enter_arrowheads = arrowheads.enter().append('marker').attr({ |
|
"class": 'arrowhead', |
|
id: function(d) { |
|
return d + '_arrow'; |
|
}, |
|
viewBox: '0 0 10 10', |
|
refX: 6.5, |
|
refY: 5, |
|
orient: 'auto' |
|
}); |
|
enter_arrowheads.append('path').attr({ |
|
d: 'M0,0 L0,10 L10,5 z' |
|
}); |
|
arrowheads.select('path').attr({ |
|
fill: (function(_this) { |
|
return function(d) { |
|
return _this.link_color(d); |
|
}; |
|
})(this) |
|
}); |
|
arrowheads.exit().remove(); |
|
legend_arrowheads = this.defs.selectAll('.legend_arrowhead').data(link_types, function(d) { |
|
return d; |
|
}); |
|
enter_legend_arrowheads = legend_arrowheads.enter().append('marker').attr({ |
|
"class": 'legend_arrowhead', |
|
id: function(d) { |
|
return d + '_legend_arrow'; |
|
}, |
|
viewBox: '0 0 10 10', |
|
refX: 4, |
|
refY: 5, |
|
orient: 'auto' |
|
}); |
|
enter_legend_arrowheads.append('path').attr({ |
|
d: 'M0,0 L0,10 L10,5 z' |
|
}); |
|
legend_arrowheads.select('path').attr({ |
|
fill: (function(_this) { |
|
return function(d) { |
|
return _this.link_color(d); |
|
}; |
|
})(this) |
|
}); |
|
legend_arrowheads.exit().remove(); |
|
legend_width = 200; |
|
legend_height = 16 * link_types.length + 10; |
|
this.legend.attr({ |
|
transform: "translate(" + (width - legend_width - 10) + "," + (height - legend_height - 10) + ")" |
|
}); |
|
this.legend.select('.legend_box').attr({ |
|
width: legend_width, |
|
height: legend_height |
|
}); |
|
legend_items = this.legend.selectAll('.item').data(link_types, function(d) { |
|
return d; |
|
}); |
|
enter_legend_items = legend_items.enter().append('g').attr({ |
|
"class": 'item' |
|
}); |
|
enter_legend_items.append('text').text(function(d) { |
|
return d; |
|
}).attr({ |
|
x: 54, |
|
dy: '0.35em' |
|
}); |
|
enter_legend_links = enter_legend_items.append('g').attr({ |
|
"class": function(d) { |
|
return "link " + d; |
|
} |
|
}); |
|
enter_legend_links.append('line').attr({ |
|
x1: 2, |
|
x2: 42, |
|
stroke: (function(_this) { |
|
return function(d) { |
|
return _this.link_color(d); |
|
}; |
|
})(this), |
|
'marker-end': function(d) { |
|
return "url(#" + d + "_legend_arrow)"; |
|
} |
|
}); |
|
legend_items.attr({ |
|
transform: function(d, i) { |
|
return "translate(10," + (13 + 16 * i) + ")"; |
|
} |
|
}); |
|
legend_items.exit().remove(); |
|
nodes = this.nodes_layer.selectAll('.node').data(graph.nodes, function(d) { |
|
return d.id; |
|
}); |
|
enter_nodes = nodes.enter().append('g').attr({ |
|
display: function(d) { |
|
if (d.dummy) { |
|
return 'none'; |
|
} else { |
|
return null; |
|
} |
|
} |
|
}); |
|
enter_nodes.append('circle').attr({ |
|
"class": 'bg', |
|
r: R |
|
}); |
|
enter_nodes.append('circle').attr({ |
|
"class": 'fg', |
|
r: R |
|
}); |
|
enter_nodes.append('text').text(function(d) { |
|
if (d["class"] === 'term') { |
|
return d.id.replace(/_/g, ' '); |
|
} else { |
|
return d.id; |
|
} |
|
}).attr({ |
|
dy: '0.35em', |
|
transform: 'rotate(10)' |
|
}); |
|
nodes.attr({ |
|
"class": function(d) { |
|
return "node " + d["class"]; |
|
} |
|
}); |
|
nodes.select('.fg').attr({ |
|
fill: (function(_this) { |
|
return function(d) { |
|
return _this.node_color(d["class"]); |
|
}; |
|
})(this) |
|
}); |
|
nodes.exit().remove(); |
|
links = this.links_layer.selectAll('.link').data(graph.links, function(d) { |
|
return d.id; |
|
}); |
|
enter_links = links.enter().append('g').attr({ |
|
"class": function(d) { |
|
return "link " + d.full_name; |
|
}, |
|
display: function(d) { |
|
if (d.source.dummy || d.target.dummy) { |
|
return 'none'; |
|
} else { |
|
return null; |
|
} |
|
} |
|
}); |
|
enter_links.append('path'); |
|
links.select('path').classed('directed', function(d) { |
|
return d.directed; |
|
}).attr({ |
|
stroke: (function(_this) { |
|
return function(d) { |
|
return _this.link_color(d.full_name); |
|
}; |
|
})(this), |
|
'marker-end': function(d) { |
|
return "url(#" + d.full_name + "_arrow)"; |
|
} |
|
}); |
|
links.exit().remove(); |
|
|
|
/* cola layout */ |
|
graph.nodes.forEach(function(v) { |
|
v.width = 5 * R; |
|
return v.height = 3 * R; |
|
}); |
|
d3cola = cola.d3adaptor().size([width, height]).avoidOverlaps(true).linkDistance(70).flowLayout('y', 80).nodes(graph.nodes).links(dag.links).on('tick', function() { |
|
nodes.attr('transform', function(d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
}); |
|
return links.select('path').attr({ |
|
d: function(d) { |
|
var a, alpha, b, h, x1, x2, xr, y1, y2, yr; |
|
if (!d.inverse) { |
|
x1 = d.source.x; |
|
x2 = d.target.x; |
|
y1 = d.source.y; |
|
y2 = d.target.y; |
|
} else { |
|
x1 = d.target.x; |
|
x2 = d.source.x; |
|
y1 = d.target.y; |
|
y2 = d.source.y; |
|
} |
|
alpha = Math.atan2(y2 - y1, x2 - x1); |
|
xr = R * Math.sin(alpha); |
|
yr = R * Math.cos(alpha); |
|
if (d.full_name === 'isA' || d.full_name === 'instanceOf') { |
|
return "M" + (x1 + yr) + " " + (y1 + xr) + " L" + (x2 - yr) + " " + (y2 - xr); |
|
} else { |
|
h = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); |
|
a = C * h * Math.sin(alpha); |
|
b = C * h * Math.cos(alpha); |
|
return "M" + (x1 + xr) + " " + (y1 - yr) + " C" + (x1 + xr + a) + " " + (y1 - yr - b) + " " + (x2 + xr + a) + " " + (y2 - yr - b) + " " + (x2 + xr) + " " + (y2 - yr); |
|
} |
|
} |
|
}); |
|
}); |
|
drag = d3cola.drag(); |
|
drag.on('dragstart', function() { |
|
return d3.event.sourceEvent.stopPropagation(); |
|
}); |
|
nodes.call(drag); |
|
return d3cola.start(200, 30, 30); |
|
} |
|
}); |
|
|
|
}).call(this); |