Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nitaku/5fb51700f991e4101773 to your computer and use it in GitHub Desktop.
Save nitaku/5fb51700f991e4101773 to your computer and use it in GitHub Desktop.
Tangled tree

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)
.node > circle {
fill: #dddddd;
stroke: #777777;
stroke-width: 2px;
}
.node > text {
font-family: sans-serif;
text-anchor: middle;
pointer-events: none;
}
.link {
stroke: #DDD;
stroke-width: 4px;
marker-end: url(#end-arrow);
}
#end-arrow {
fill: #DDD;
}
<!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