cola.js's flowLayout gives us the ability to layout a node-link diagram with downward-pointing edges, which can be used to effectively draw a tangled tree, i.e., a tree with a small amount of nodes featuring multiple inheritance (see also this old example).
| graph = { | |
| nodes: [ | |
| {id: 'A'}, | |
| {id: 'B'}, | |
| {id: 'C'}, | |
| {id: 'D'}, | |
| {id: 'E'}, | |
| {id: 'F'}, | |
| {id: 'G'}, | |
| {id: 'H'}, | |
| {id: 'I'}, | |
| {id: 'J'} | |
| ], | |
| links: [ | |
| {id: 1, source: 'A', target: 'B'}, | |
| {id: 2, source: 'A', target: 'C'}, | |
| {id: 3, source: 'A', target: 'D'}, | |
| {id: 4, source: 'B', target: 'E'}, | |
| {id: 5, source: 'B', target: 'F'}, | |
| {id: 6, source: 'C', target: 'G'}, | |
| {id: 7, source: 'C', target: 'F'}, | |
| {id: 8, source: 'F', target: 'G'}, | |
| {id: 9, source: 'G', target: 'H'}, | |
| {id: 10, source: 'G', target: 'I'}, | |
| {id: 11, source: 'H', target: 'I'}, | |
| {id: 12, source: 'I', target: 'J'} | |
| ]} | |
| ### objectify the graph ### | |
| ### resolve node IDs (not optimized at all!) ### | |
| for l in graph.links | |
| for n in graph.nodes | |
| if l.source is n.id | |
| l.source = n | |
| if l.target is n.id | |
| l.target = n | |
| R = 18 | |
| svg = d3.select('svg') | |
| width = svg.node().getBoundingClientRect().width | |
| height = svg.node().getBoundingClientRect().height | |
| defs = svg.append('defs') | |
| ### define arrow markers for graph links ### | |
| defs.append('marker') | |
| .attr | |
| id: 'end-arrow' | |
| viewBox: '0 0 10 10' | |
| refX: 4+R | |
| refY: 5 | |
| orient: 'auto' | |
| .append('path') | |
| .attr | |
| d: 'M0,0 L0,10 L10,5 z' | |
| ### create nodes and links ### | |
| links = svg.selectAll('.link') | |
| .data(graph.links, (d) -> d.id) | |
| links | |
| .enter().append('line') | |
| .attr('class', 'link') | |
| nodes = svg.selectAll('.node') | |
| .data(graph.nodes, (d) -> d.id) | |
| enter_nodes = nodes.enter().append('g') | |
| .attr('class', 'node') | |
| enter_nodes.append('circle') | |
| .attr('r', R) | |
| ### draw the label ### | |
| enter_nodes.append('text') | |
| .text((d) -> d.id) | |
| .attr('dy', '0.35em') | |
| ### cola layout ### | |
| graph.nodes.forEach (v) -> | |
| v.width = 2.5*R | |
| v.height = 2.5*R | |
| d3cola = cola.d3adaptor() | |
| .size([width, height]) | |
| .linkDistance(70) | |
| .avoidOverlaps(true) | |
| .flowLayout('y', 30) | |
| .nodes(graph.nodes) | |
| .links(graph.links) | |
| .on 'tick', () -> | |
| ### update nodes and links ### | |
| nodes | |
| .attr('transform', (d) -> "translate(#{d.x},#{d.y})") | |
| links | |
| .attr('x1', (d) -> d.source.x) | |
| .attr('y1', (d) -> d.source.y) | |
| .attr('x2', (d) -> d.target.x) | |
| .attr('y2', (d) -> d.target.y) | |
| enter_nodes | |
| .call(d3cola.drag) | |
| d3cola.start(30,30,30) |
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Tangled tree</title> | |
| <link rel="stylesheet" href="index.css"> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script src="http://marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script> | |
| </head> | |
| <body> | |
| <svg width="960px" height="500px"></svg> | |
| <script src="index.js"></script> | |
| </body> | |
| </html> |
| // Generated by CoffeeScript 1.4.0 | |
| (function() { | |
| var R, d3cola, defs, enter_nodes, graph, height, l, links, n, nodes, svg, width, _i, _j, _len, _len1, _ref, _ref1; | |
| graph = { | |
| nodes: [ | |
| { | |
| id: 'A' | |
| }, { | |
| id: 'B' | |
| }, { | |
| id: 'C' | |
| }, { | |
| id: 'D' | |
| }, { | |
| id: 'E' | |
| }, { | |
| id: 'F' | |
| }, { | |
| id: 'G' | |
| }, { | |
| id: 'H' | |
| }, { | |
| id: 'I' | |
| }, { | |
| id: 'J' | |
| } | |
| ], | |
| links: [ | |
| { | |
| id: 1, | |
| source: 'A', | |
| target: 'B' | |
| }, { | |
| id: 2, | |
| source: 'A', | |
| target: 'C' | |
| }, { | |
| id: 3, | |
| source: 'A', | |
| target: 'D' | |
| }, { | |
| id: 4, | |
| source: 'B', | |
| target: 'E' | |
| }, { | |
| id: 5, | |
| source: 'B', | |
| target: 'F' | |
| }, { | |
| id: 6, | |
| source: 'C', | |
| target: 'G' | |
| }, { | |
| id: 7, | |
| source: 'C', | |
| target: 'F' | |
| }, { | |
| id: 8, | |
| source: 'F', | |
| target: 'G' | |
| }, { | |
| id: 9, | |
| source: 'G', | |
| target: 'H' | |
| }, { | |
| id: 10, | |
| source: 'G', | |
| target: 'I' | |
| }, { | |
| id: 11, | |
| source: 'H', | |
| target: 'I' | |
| }, { | |
| id: 12, | |
| source: 'I', | |
| target: 'J' | |
| } | |
| ] | |
| }; | |
| /* objectify the graph | |
| */ | |
| /* resolve node IDs (not optimized at all!) | |
| */ | |
| _ref = graph.links; | |
| for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
| l = _ref[_i]; | |
| _ref1 = graph.nodes; | |
| for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | |
| n = _ref1[_j]; | |
| if (l.source === n.id) { | |
| l.source = n; | |
| } | |
| if (l.target === n.id) { | |
| l.target = n; | |
| } | |
| } | |
| } | |
| R = 18; | |
| svg = d3.select('svg'); | |
| width = svg.node().getBoundingClientRect().width; | |
| height = svg.node().getBoundingClientRect().height; | |
| defs = svg.append('defs'); | |
| /* define arrow markers for graph links | |
| */ | |
| defs.append('marker').attr({ | |
| id: 'end-arrow', | |
| viewBox: '0 0 10 10', | |
| refX: 4 + R, | |
| refY: 5, | |
| orient: 'auto' | |
| }).append('path').attr({ | |
| d: 'M0,0 L0,10 L10,5 z' | |
| }); | |
| /* create nodes and links | |
| */ | |
| links = svg.selectAll('.link').data(graph.links, function(d) { | |
| return d.id; | |
| }); | |
| links.enter().append('line').attr('class', 'link'); | |
| nodes = svg.selectAll('.node').data(graph.nodes, function(d) { | |
| return d.id; | |
| }); | |
| enter_nodes = nodes.enter().append('g').attr('class', 'node'); | |
| enter_nodes.append('circle').attr('r', R); | |
| /* draw the label | |
| */ | |
| enter_nodes.append('text').text(function(d) { | |
| return d.id; | |
| }).attr('dy', '0.35em'); | |
| /* cola layout | |
| */ | |
| graph.nodes.forEach(function(v) { | |
| v.width = 2.5 * R; | |
| return v.height = 2.5 * R; | |
| }); | |
| d3cola = cola.d3adaptor().size([width, height]).linkDistance(70).avoidOverlaps(true).flowLayout('y', 30).nodes(graph.nodes).links(graph.links).on('tick', function() { | |
| /* update nodes and links | |
| */ | |
| nodes.attr('transform', function(d) { | |
| return "translate(" + d.x + "," + d.y + ")"; | |
| }); | |
| return links.attr('x1', function(d) { | |
| return d.source.x; | |
| }).attr('y1', function(d) { | |
| return d.source.y; | |
| }).attr('x2', function(d) { | |
| return d.target.x; | |
| }).attr('y2', function(d) { | |
| return d.target.y; | |
| }); | |
| }); | |
| enter_nodes.call(d3cola.drag); | |
| d3cola.start(30, 30, 30); | |
| }).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment