block by nitaku f86779216b57d41504adb78db0ca4992

Force layout node-link

Full Screen

A simple example of a node-link diagram drawn by using a force-directed layout in D3 v4.

index.js

// Generated by CoffeeScript 1.10.0
(function() {
  var enl, enn, graph, height, index, links, links_layer, nodes, nodes_layer, simulation, svg, width;

  svg = d3.select('svg');

  width = svg.node().getBoundingClientRect().width;

  height = svg.node().getBoundingClientRect().height;

  graph = {
    nodes: [
      {
        id: 'A'
      }, {
        id: 'B'
      }, {
        id: 'C'
      }, {
        id: 'D'
      }, {
        id: 'E'
      }
    ],
    links: [
      {
        source: 'A',
        target: 'B'
      }, {
        source: 'A',
        target: 'C'
      }, {
        source: 'A',
        target: 'D'
      }, {
        source: 'B',
        target: 'C'
      }, {
        source: 'C',
        target: 'E'
      }, {
        source: 'E',
        target: 'A'
      }
    ]
  };

  index = {};

  graph.nodes.forEach(function(d) {
    return index[d.id] = d;
  });

  graph.links.forEach(function(l) {
    l.source = index[l.source];
    return l.target = index[l.target];
  });

  simulation = d3.forceSimulation().force('link', d3.forceLink().id(function(d) {
    return d.id;
  }).strength(0.5)).force('charge', d3.forceManyBody()).force('center', d3.forceCenter(width / 2, height / 2));

  links_layer = svg.append('g');

  nodes_layer = svg.append('g');

  nodes = nodes_layer.selectAll('.node').data(graph.nodes);

  enn = nodes.enter().append('g').attrs({
    "class": 'node'
  });

  enn.append('circle').attrs({
    r: 3
  });

  enn.append('text').text(function(d) {
    return d.id;
  }).attrs({
    dy: '1.2em'
  });

  links = links_layer.selectAll('.link').data(graph.links);

  enl = links.enter().append('line').attrs({
    "class": 'link'
  });

  simulation.nodes(graph.nodes).on('tick', function() {
    enn.attrs({
      transform: function(d) {
        return "translate(" + d.x + ", " + d.y + ")";
      }
    });
    return enl.attrs({
      x1: function(d) {
        return d.source.x;
      },
      y1: function(d) {
        return d.source.y;
      },
      x2: function(d) {
        return d.target.x;
      },
      y2: function(d) {
        return d.target.y;
      }
    });
  });

  simulation.force('link').links(graph.links);

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Force layout node-link</title>
  <link type="text/css" href="index.css" rel="stylesheet"/>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
</head>
<body>
  <svg></svg>
  <script src="index.js"></script>
</body>
</html>

index.coffee

svg = d3.select 'svg'
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height

# Data
graph = {
  nodes: [
    {id: 'A'}
    {id: 'B'}
    {id: 'C'}
    {id: 'D'}
    {id: 'E'}
  ]
  links: [
    {source: 'A', target: 'B'}
    {source: 'A', target: 'C'}
    {source: 'A', target: 'D'}
    {source: 'B', target: 'C'}
    {source: 'C', target: 'E'}
    {source: 'E', target: 'A'}
  ]
}

# objectify the graph
index = {}
graph.nodes.forEach (d) -> index[d.id] = d
graph.links.forEach (l) ->
  l.source = index[l.source]
  l.target = index[l.target]

# Layout
simulation = d3.forceSimulation()
  .force('link', d3.forceLink()
    .id((d) -> d.id)
    .strength(0.5))
  .force('charge', d3.forceManyBody())
  .force('center', d3.forceCenter(width/2, height/2))
  
# Vis
links_layer = svg.append 'g'
nodes_layer = svg.append 'g'

nodes = nodes_layer.selectAll '.node'
  .data graph.nodes

enn = nodes.enter().append 'g'
  .attrs
    class: 'node'

enn.append 'circle'
  .attrs
    r: 3

enn.append 'text'
  .text (d) -> d.id
  .attrs
    dy: '1.2em'
    
    
links = links_layer.selectAll '.link'
  .data graph.links

enl = links.enter().append 'line'
  .attrs
    class: 'link'


# Simulation
simulation
  .nodes(graph.nodes)
  .on 'tick', () ->
    enn.attrs
      transform: (d) -> "translate(#{d.x}, #{d.y})"

    enl.attrs
      x1: (d) -> d.source.x
      y1: (d) -> d.source.y
      x2: (d) -> d.target.x
      y2: (d) -> d.target.y

simulation.force('link')
  .links(graph.links)
  

index.css

body, html {
  padding: 0;
  margin: 0;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
  background: white;
}

.node circle {
  fill: #333;
  stroke: white;
}
.node text {
  font-family: sans-serif;
  font-size: 12px;
  text-anchor: middle;
}

.link {
  stroke: #CCC;
}