// 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);