block by nitaku 7733be2b2e595cd17189

Cola.js layout

Full Screen

A simple exercise with cola.js. A node-link diagram is displayed, based on positions computed by cola’s fast-converging constraints-based layout algorithm. Handling of more than one connected component, node dragging and non-overlapping constraints are also shown.

Compare with a similar example using native d3.js force-directed layout.

index.js

// Generated by CoffeeScript 1.4.0
(function() {
  var R, d3cola, 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'
      }, {
        id: 'K'
      }, {
        id: 'L'
      }, {
        id: 'M'
      }, {
        id: 'N'
      }, {
        id: 'O'
      }
    ],
    links: [
      {
        id: 1,
        source: 'A',
        target: 'B'
      }, {
        id: 2,
        source: 'B',
        target: 'C'
      }, {
        id: 3,
        source: 'C',
        target: 'A'
      }, {
        id: 4,
        source: 'B',
        target: 'D'
      }, {
        id: 5,
        source: 'D',
        target: 'C'
      }, {
        id: 6,
        source: 'D',
        target: 'E'
      }, {
        id: 7,
        source: 'E',
        target: 'F'
      }, {
        id: 8,
        source: 'F',
        target: 'G'
      }, {
        id: 9,
        source: 'F',
        target: 'H'
      }, {
        id: 10,
        source: 'G',
        target: 'H'
      }, {
        id: 11,
        source: 'G',
        target: 'I'
      }, {
        id: 12,
        source: 'H',
        target: 'I'
      }, {
        id: 13,
        source: 'J',
        target: 'E'
      }, {
        id: 14,
        source: 'J',
        target: 'L'
      }, {
        id: 15,
        source: 'J',
        target: 'K'
      }, {
        id: 16,
        source: 'K',
        target: 'L'
      }, {
        id: 17,
        source: 'L',
        target: 'M'
      }, {
        id: 18,
        source: 'M',
        target: 'K'
      }, {
        id: 19,
        source: 'N',
        target: 'O'
      }
    ]
  };

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

  /* 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(50).avoidOverlaps(true).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);

index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Cola.js layout</title>
    <link rel="stylesheet" href="index.css">
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="//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>

index.coffee

graph = {
  nodes: [
    {id: 'A'},
    {id: 'B'},
    {id: 'C'},
    {id: 'D'},
    {id: 'E'},
    {id: 'F'},
    {id: 'G'},
    {id: 'H'},
    {id: 'I'},
    {id: 'J'},
    {id: 'K'},
    {id: 'L'},
    {id: 'M'},
    {id: 'N'},
    {id: 'O'}
  ],
  links: [
    {id:  1, source: 'A', target: 'B'},
    {id:  2, source: 'B', target: 'C'},
    {id:  3, source: 'C', target: 'A'},
    {id:  4, source: 'B', target: 'D'},
    {id:  5, source: 'D', target: 'C'},
    {id:  6, source: 'D', target: 'E'},
    {id:  7, source: 'E', target: 'F'},
    {id:  8, source: 'F', target: 'G'},
    {id:  9, source: 'F', target: 'H'},
    {id: 10, source: 'G', target: 'H'},
    {id: 11, source: 'G', target: 'I'},
    {id: 12, source: 'H', target: 'I'},
    {id: 13, source: 'J', target: 'E'},
    {id: 14, source: 'J', target: 'L'},
    {id: 15, source: 'J', target: 'K'},
    {id: 16, source: 'K', target: 'L'},
    {id: 17, source: 'L', target: 'M'},
    {id: 18, source: 'M', target: 'K'},
    {id: 19, source: 'N', target: 'O'}
  ]}


### 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

### 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(50)
  .avoidOverlaps(true)
  .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)

index.css

.node > circle {
  fill: #dddddd;
  stroke: #777777;
  stroke-width: 2px;
}

.node > text {
  font-family: sans-serif;
  text-anchor: middle;
  pointer-events: none;
}

.link {
  stroke: #dddddd;
  stroke-width: 4px;
}