block by micahstubbs 76d8a78648be617fe7aef649c312b580

538 Twitter Graph, Annotated

Full Screen

a mashup of the 538 twitter network from @xaranke with the Network Annotation with Collision Detection block from @elijah_meeks


Using collision detection with network visualization labels

This demonstrates how to use d3-annotation() with bboxCollide to procedurally place node labels. After using the nodes data to create a network visualization of the Les Miserables play, we filter the nodes to leave out the side characters and pass that array to d3-annotation. We then create a second forceSimulation, this time using the size of the notes as the property in our bounding box collision detection, to move the labels out of each others’ way.

d3-annotation by Susie Lu.

index.html

<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <link href='https://fonts.googleapis.com/css?family=Lato:300,900' rel='stylesheet' type='text/css'>

    <style>

    :root {
      --annotation-color: #e91e56;
    }
     body{
        background-color: white;
     }

     svg {
        background-color: white;
        font-family: 'Lato';
        overflow: visible;
     }

     line {
       stroke:#e3e3e3;
     }

     .editable .annotation-subject, .editable .annotation-textbox {
       cursor: move;
     }

     .line {
        fill: none;
        stroke: black;
        stroke-width: 1px;
      }

      .annotation path {
        stroke: var(--annotation-color);
        fill: rgba(0,0,0,0);
      }

      .annotation path.connector-arrow{
        fill: var(--annotation-color);
      }

      .annotation text {
        fill: var(--annotation-color);
      }

      .annotation-title {
        font-weight: bold;
      }

      .annotation .annotation-subject circle.handle {
        display: none;
      }

      .annotation-note-bg {
        fill: rgba(255, 255, 255, 0);
      }

       circle.handle {
        stroke-dasharray: 5;
        stroke: grey;
        fill: rgba(255, 255, 255, 0);
        cursor: move;

        stroke-opacity: .4;
      }

      circle.handle.highlight {
        stroke-opacity: 1;
      }

      .annotation.major {
        font-weight: 900;
        font-size: 1em;
      }

      .annotation-note-label tspan {
        text-anchor: middle;
      }

    </style>
</head>
<body>
<svg width=960 height=500></svg>
<script src='https://d3js.org/d3.v4.js'></script>
<script src='bboxCollide.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.23.1/babel.min.js'></script>
<script src='https://cdn.rawgit.com/susielu/d3-annotation/master/d3-annotation.js'></script> 
<script src='vis.js'></script>
</body>
</html>

bboxCollide.js

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-quadtree')) :
  typeof define === 'function' && define.amd ? define(['exports', 'd3-quadtree'], factory) :
  (factory((global.d3 = global.d3 || {}),global.d3));
}(this, function (exports,d3Quadtree) { 'use strict';

  function bboxCollide (bbox) {

    function x (d) {
      return d.x + d.vx;
    }

    function y (d) {
      return d.y + d.vy;
    }

    function constant (x) {
      return function () {
        return x;
      };
    }

    var nodes,
        boundingBoxes,
        strength = 1,
        iterations = 1;

        if (typeof bbox !== "function") {
          bbox = constant(bbox === null ? [[0,0][1,1]] : bbox)
        }

        function force () {
          var i,
              tree,
              node,
              xi,
              yi,
              bbi,
              nx1,
              ny1,
              nx2,
              ny2

              var cornerNodes = []
              nodes.forEach(function (d, i) {
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + (boundingBoxes[i][1][0] + boundingBoxes[i][0][0]) / 2, y: d.y + (boundingBoxes[i][0][1] + boundingBoxes[i][1][1]) / 2})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][0][0], y: d.y + boundingBoxes[i][0][1]})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][0][0], y: d.y + boundingBoxes[i][1][1]})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][1][0], y: d.y + boundingBoxes[i][0][1]})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][1][0], y: d.y + boundingBoxes[i][1][1]})
              })
              var cn = cornerNodes.length

          for (var k = 0; k < iterations; ++k) {
            tree = d3Quadtree.quadtree(cornerNodes, x, y).visitAfter(prepareCorners);

            for (i = 0; i < cn; ++i) {
              var nodeI = ~~(i / 5);
              node = nodes[nodeI]
              bbi = boundingBoxes[nodeI]
              xi = node.x + node.vx
              yi = node.y + node.vy
              nx1 = xi + bbi[0][0]
              ny1 = yi + bbi[0][1]
              nx2 = xi + bbi[1][0]
              ny2 = yi + bbi[1][1]
              tree.visit(apply);
            }
          }

          function apply (quad, x0, y0, x1, y1) {
              var data = quad.data
              if (data) {
                var bWidth = bbLength(bbi, 0),
                bHeight = bbLength(bbi, 1);

                if (data.node.index !== nodeI) {
                  var dataNode = data.node
                  var bbj = boundingBoxes[dataNode.index],
                    dnx1 = dataNode.x + dataNode.vx + bbj[0][0],
                    dny1 = dataNode.y + dataNode.vy + bbj[0][1],
                    dnx2 = dataNode.x + dataNode.vx + bbj[1][0],
                    dny2 = dataNode.y + dataNode.vy + bbj[1][1],
                    dWidth = bbLength(bbj, 0),
                    dHeight = bbLength(bbj, 1)

                  if (nx1 <= dnx2 && dnx1 <= nx2 && ny1 <= dny2 && dny1 <= ny2) {

                    var xSize = [Math.min.apply(null, [dnx1, dnx2, nx1, nx2]), Math.max.apply(null, [dnx1, dnx2, nx1, nx2])]
                    var ySize = [Math.min.apply(null, [dny1, dny2, ny1, ny2]), Math.max.apply(null, [dny1, dny2, ny1, ny2])]

                    var xOverlap = bWidth + dWidth - (xSize[1] - xSize[0])
                    var yOverlap = bHeight + dHeight - (ySize[1] - ySize[0])

                    var xBPush = xOverlap * strength * (yOverlap / bHeight)
                    var yBPush = yOverlap * strength * (xOverlap / bWidth)

                    var xDPush = xOverlap * strength * (yOverlap / dHeight)
                    var yDPush = yOverlap * strength * (xOverlap / dWidth)

                    if ((nx1 + nx2) / 2 < (dnx1 + dnx2) / 2) {
                      node.vx -= xBPush
                      dataNode.vx += xDPush
                    }
                    else {
                      node.vx += xBPush
                      dataNode.vx -= xDPush
                    }
                    if ((ny1 + ny2) / 2 < (dny1 + dny2) / 2) {
                      node.vy -= yBPush
                      dataNode.vy += yDPush
                    }
                    else {
                      node.vy += yBPush
                      dataNode.vy -= yDPush
                    }
                  }

                }
                return;
              }

              return x0 > nx2 || x1 < nx1 || y0 > ny2 || y1 < ny1;
          }

        }

        function prepareCorners (quad) {

          if (quad.data) {
            return quad.bb = boundingBoxes[quad.data.node.index]
          }
            quad.bb = [[0,0],[0,0]]
            for (var i = 0; i < 4; ++i) {
              if (quad[i] && quad[i].bb[0][0] < quad.bb[0][0]) {
                quad.bb[0][0] = quad[i].bb[0][0]
              }
              if (quad[i] && quad[i].bb[0][1] < quad.bb[0][1]) {
                quad.bb[0][1] = quad[i].bb[0][1]
              }
              if (quad[i] && quad[i].bb[1][0] > quad.bb[1][0]) {
                quad.bb[1][0] = quad[i].bb[1][0]
              }
              if (quad[i] && quad[i].bb[1][1] > quad.bb[1][1]) {
                quad.bb[1][1] = quad[i].bb[1][1]
              }
          }
        }

        function bbLength (bbox, heightWidth) {
          return bbox[1][heightWidth] - bbox[0][heightWidth]
        }

        force.initialize = function (_) {
          var i, n = (nodes = _).length; boundingBoxes = new Array(n);
          for (i = 0; i < n; ++i) boundingBoxes[i] = bbox(nodes[i], i, nodes);
        };

        force.iterations = function (_) {
          return arguments.length ? (iterations = +_, force) : iterations;
        };

        force.strength = function (_) {
          return arguments.length ? (strength = +_, force) : strength;
        };

        force.bbox = function (_) {
          return arguments.length ? (bbox = typeof _ === "function" ? _ : constant(+_), force) : bbox;
        };

        return force;
  }

  exports.bboxCollide = bboxCollide;

  Object.defineProperty(exports, '__esModule', { value: true });

}));

graph.json

{  
  "links":[  
    {  
      "source":46,
      "target":6,
      "value":5
    },
    {  
      "source":20,
      "target":47,
      "value":30
    },
    {  
      "source":15,
      "target":25,
      "value":8
    },
    {  
      "source":40,
      "target":26,
      "value":7
    },
    {  
      "source":46,
      "target":42,
      "value":15
    },
    {  
      "source":44,
      "target":24,
      "value":19
    },
    {  
      "source":44,
      "target":20,
      "value":17
    },
    {  
      "source":46,
      "target":47,
      "value":27
    },
    {  
      "source":47,
      "target":38,
      "value":20
    },
    {  
      "source":33,
      "target":24,
      "value":32
    },
    {  
      "source":40,
      "target":48,
      "value":27
    },
    {  
      "source":12,
      "target":25,
      "value":10
    },
    {  
      "source":36,
      "target":21,
      "value":17
    },
    {  
      "source":46,
      "target":43,
      "value":11
    },
    {  
      "source":24,
      "target":25,
      "value":8
    },
    {  
      "source":15,
      "target":20,
      "value":7
    },
    {  
      "source":50,
      "target":25,
      "value":13
    },
    {  
      "source":18,
      "target":35,
      "value":5
    },
    {  
      "source":36,
      "target":0,
      "value":5
    },
    {  
      "source":12,
      "target":27,
      "value":6
    },
    {  
      "source":14,
      "target":6,
      "value":21
    },
    {  
      "source":17,
      "target":39,
      "value":5
    },
    {  
      "source":35,
      "target":23,
      "value":28
    },
    {  
      "source":33,
      "target":43,
      "value":5
    },
    {  
      "source":47,
      "target":48,
      "value":12
    },
    {  
      "source":40,
      "target":39,
      "value":58
    },
    {  
      "source":35,
      "target":20,
      "value":49
    },
    {  
      "source":40,
      "target":31,
      "value":46
    },
    {  
      "source":40,
      "target":27,
      "value":29
    },
    {  
      "source":35,
      "target":15,
      "value":15
    },
    {  
      "source":35,
      "target":24,
      "value":30
    },
    {  
      "source":44,
      "target":35,
      "value":23
    },
    {  
      "source":27,
      "target":50,
      "value":13
    },
    {  
      "source":34,
      "target":42,
      "value":5
    },
    {  
      "source":35,
      "target":47,
      "value":52
    },
    {  
      "source":47,
      "target":43,
      "value":37
    },
    {  
      "source":18,
      "target":20,
      "value":5
    },
    {  
      "source":35,
      "target":36,
      "value":42
    },
    {  
      "source":40,
      "target":37,
      "value":36
    },
    {  
      "source":13,
      "target":47,
      "value":6
    },
    {  
      "source":27,
      "target":38,
      "value":12
    },
    {  
      "source":25,
      "target":39,
      "value":31
    },
    {  
      "source":26,
      "target":43,
      "value":10
    },
    {  
      "source":31,
      "target":15,
      "value":5
    },
    {  
      "source":49,
      "target":48,
      "value":21
    },
    {  
      "source":25,
      "target":48,
      "value":42
    },
    {  
      "source":37,
      "target":39,
      "value":62
    },
    {  
      "source":40,
      "target":10,
      "value":7
    },
    {  
      "source":47,
      "target":42,
      "value":8
    },
    {  
      "source":35,
      "target":5,
      "value":22
    },
    {  
      "source":36,
      "target":5,
      "value":5
    },
    {  
      "source":43,
      "target":48,
      "value":6
    },
    {  
      "source":36,
      "target":20,
      "value":62
    },
    {  
      "source":40,
      "target":20,
      "value":63
    },
    {  
      "source":40,
      "target":33,
      "value":16
    },
    {  
      "source":24,
      "target":48,
      "value":8
    },
    {  
      "source":40,
      "target":34,
      "value":11
    },
    {  
      "source":18,
      "target":25,
      "value":7
    },
    {  
      "source":40,
      "target":46,
      "value":12
    },
    {  
      "source":44,
      "target":41,
      "value":6
    },
    {  
      "source":40,
      "target":44,
      "value":45
    },
    {  
      "source":45,
      "target":49,
      "value":8
    },
    {  
      "source":18,
      "target":30,
      "value":7
    },
    {  
      "source":35,
      "target":37,
      "value":26
    },
    {  
      "source":33,
      "target":20,
      "value":8
    },
    {  
      "source":44,
      "target":23,
      "value":12
    },
    {  
      "source":45,
      "target":25,
      "value":11
    },
    {  
      "source":5,
      "target":20,
      "value":7
    },
    {  
      "source":20,
      "target":48,
      "value":30
    },
    {  
      "source":38,
      "target":43,
      "value":59
    },
    {  
      "source":13,
      "target":25,
      "value":23
    },
    {  
      "source":35,
      "target":27,
      "value":37
    },
    {  
      "source":44,
      "target":36,
      "value":15
    },
    {  
      "source":36,
      "target":39,
      "value":67
    },
    {  
      "source":45,
      "target":39,
      "value":17
    },
    {  
      "source":22,
      "target":47,
      "value":5
    },
    {  
      "source":36,
      "target":25,
      "value":25
    },
    {  
      "source":44,
      "target":37,
      "value":15
    },
    {  
      "source":31,
      "target":47,
      "value":12
    },
    {  
      "source":26,
      "target":39,
      "value":20
    },
    {  
      "source":31,
      "target":27,
      "value":15
    },
    {  
      "source":44,
      "target":38,
      "value":108
    },
    {  
      "source":35,
      "target":42,
      "value":5
    },
    {  
      "source":24,
      "target":23,
      "value":7
    },
    {  
      "source":46,
      "target":44,
      "value":37
    },
    {  
      "source":40,
      "target":21,
      "value":9
    },
    {  
      "source":33,
      "target":36,
      "value":7
    },
    {  
      "source":20,
      "target":23,
      "value":30
    },
    {  
      "source":18,
      "target":34,
      "value":9
    },
    {  
      "source":39,
      "target":48,
      "value":10
    },
    {  
      "source":13,
      "target":20,
      "value":12
    },
    {  
      "source":45,
      "target":19,
      "value":5
    },
    {  
      "source":44,
      "target":34,
      "value":17
    },
    {  
      "source":33,
      "target":50,
      "value":13
    },
    {  
      "source":25,
      "target":23,
      "value":10
    },
    {  
      "source":35,
      "target":38,
      "value":42
    },
    {  
      "source":40,
      "target":19,
      "value":16
    },
    {  
      "source":20,
      "target":11,
      "value":15
    },
    {  
      "source":37,
      "target":25,
      "value":6
    },
    {  
      "source":18,
      "target":38,
      "value":5
    },
    {  
      "source":44,
      "target":48,
      "value":9
    },
    {  
      "source":40,
      "target":35,
      "value":248
    },
    {  
      "source":30,
      "target":20,
      "value":5
    },
    {  
      "source":24,
      "target":43,
      "value":7
    },
    {  
      "source":44,
      "target":39,
      "value":25
    },
    {  
      "source":36,
      "target":47,
      "value":12
    },
    {  
      "source":50,
      "target":43,
      "value":10
    },
    {  
      "source":35,
      "target":19,
      "value":21
    },
    {  
      "source":33,
      "target":27,
      "value":14
    },
    {  
      "source":13,
      "target":36,
      "value":8
    },
    {  
      "source":12,
      "target":36,
      "value":5
    },
    {  
      "source":40,
      "target":24,
      "value":44
    },
    {  
      "source":44,
      "target":45,
      "value":15
    },
    {  
      "source":35,
      "target":13,
      "value":24
    },
    {  
      "source":40,
      "target":29,
      "value":9
    },
    {  
      "source":21,
      "target":50,
      "value":5
    },
    {  
      "source":46,
      "target":30,
      "value":10
    },
    {  
      "source":12,
      "target":10,
      "value":7
    },
    {  
      "source":47,
      "target":25,
      "value":5
    },
    {  
      "source":25,
      "target":43,
      "value":5
    },
    {  
      "source":45,
      "target":47,
      "value":13
    },
    {  
      "source":40,
      "target":13,
      "value":17
    },
    {  
      "source":20,
      "target":3,
      "value":8
    },
    {  
      "source":47,
      "target":39,
      "value":19
    },
    {  
      "source":46,
      "target":34,
      "value":16
    },
    {  
      "source":12,
      "target":24,
      "value":5
    },
    {  
      "source":13,
      "target":19,
      "value":14
    },
    {  
      "source":44,
      "target":0,
      "value":36
    },
    {  
      "source":35,
      "target":4,
      "value":8
    },
    {  
      "source":24,
      "target":39,
      "value":7
    },
    {  
      "source":18,
      "target":40,
      "value":5
    },
    {  
      "source":40,
      "target":43,
      "value":27
    },
    {  
      "source":35,
      "target":14,
      "value":7
    },
    {  
      "source":27,
      "target":37,
      "value":5
    },
    {  
      "source":5,
      "target":15,
      "value":7
    },
    {  
      "source":44,
      "target":30,
      "value":12
    },
    {  
      "source":24,
      "target":5,
      "value":7
    },
    {  
      "source":46,
      "target":35,
      "value":13
    },
    {  
      "source":35,
      "target":9,
      "value":9
    },
    {  
      "source":18,
      "target":44,
      "value":23
    },
    {  
      "source":9,
      "target":25,
      "value":10
    },
    {  
      "source":20,
      "target":37,
      "value":17
    },
    {  
      "source":43,
      "target":39,
      "value":14
    },
    {  
      "source":27,
      "target":47,
      "value":5
    },
    {  
      "source":40,
      "target":23,
      "value":9
    },
    {  
      "source":40,
      "target":30,
      "value":12
    },
    {  
      "source":10,
      "target":25,
      "value":5
    },
    {  
      "source":40,
      "target":36,
      "value":50
    },
    {  
      "source":35,
      "target":43,
      "value":23
    },
    {  
      "source":12,
      "target":20,
      "value":7
    },
    {  
      "source":35,
      "target":10,
      "value":15
    },
    {  
      "source":20,
      "target":50,
      "value":12
    },
    {  
      "source":45,
      "target":43,
      "value":15
    },
    {  
      "source":40,
      "target":38,
      "value":34
    },
    {  
      "source":35,
      "target":22,
      "value":5
    },
    {  
      "source":30,
      "target":47,
      "value":5
    },
    {  
      "source":44,
      "target":25,
      "value":17
    },
    {  
      "source":27,
      "target":10,
      "value":10
    },
    {  
      "source":44,
      "target":31,
      "value":23
    },
    {  
      "source":46,
      "target":31,
      "value":5
    },
    {  
      "source":35,
      "target":30,
      "value":34
    },
    {  
      "source":20,
      "target":39,
      "value":12
    },
    {  
      "source":46,
      "target":45,
      "value":5
    },
    {  
      "source":35,
      "target":16,
      "value":13
    },
    {  
      "source":44,
      "target":26,
      "value":46
    },
    {  
      "source":46,
      "target":14,
      "value":63
    },
    {  
      "source":9,
      "target":39,
      "value":6
    },
    {  
      "source":33,
      "target":28,
      "value":18
    },
    {  
      "source":36,
      "target":23,
      "value":6
    },
    {  
      "source":35,
      "target":31,
      "value":77
    },
    {  
      "source":35,
      "target":39,
      "value":26
    },
    {  
      "source":45,
      "target":20,
      "value":25
    },
    {  
      "source":33,
      "target":38,
      "value":6
    },
    {  
      "source":20,
      "target":16,
      "value":8
    },
    {  
      "source":5,
      "target":23,
      "value":5
    },
    {  
      "source":47,
      "target":50,
      "value":29
    },
    {  
      "source":17,
      "target":48,
      "value":27
    },
    {  
      "source":12,
      "target":40,
      "value":5
    },
    {  
      "source":46,
      "target":25,
      "value":9
    },
    {  
      "source":40,
      "target":16,
      "value":6
    },
    {  
      "source":36,
      "target":49,
      "value":12
    },
    {  
      "source":36,
      "target":15,
      "value":11
    },
    {  
      "source":31,
      "target":43,
      "value":13
    },
    {  
      "source":35,
      "target":26,
      "value":15
    },
    {  
      "source":12,
      "target":35,
      "value":47
    },
    {  
      "source":47,
      "target":37,
      "value":10
    },
    {  
      "source":15,
      "target":50,
      "value":8
    },
    {  
      "source":35,
      "target":25,
      "value":45
    },
    {  
      "source":21,
      "target":39,
      "value":9
    },
    {  
      "source":49,
      "target":37,
      "value":18
    },
    {  
      "source":27,
      "target":20,
      "value":7
    },
    {  
      "source":44,
      "target":43,
      "value":89
    },
    {  
      "source":15,
      "target":47,
      "value":9
    },
    {  
      "source":35,
      "target":49,
      "value":6
    },
    {  
      "source":20,
      "target":25,
      "value":37
    },
    {  
      "source":44,
      "target":50,
      "value":29
    },
    {  
      "source":24,
      "target":27,
      "value":12
    },
    {  
      "source":36,
      "target":27,
      "value":6
    },
    {  
      "source":44,
      "target":4,
      "value":8
    },
    {  
      "source":49,
      "target":50,
      "value":53
    },
    {  
      "source":40,
      "target":14,
      "value":9
    },
    {  
      "source":40,
      "target":25,
      "value":66
    },
    {  
      "source":13,
      "target":24,
      "value":5
    },
    {  
      "source":23,
      "target":39,
      "value":22
    },
    {  
      "source":31,
      "target":39,
      "value":16
    },
    {  
      "source":49,
      "target":47,
      "value":15
    },
    {  
      "source":40,
      "target":50,
      "value":26
    },
    {  
      "source":35,
      "target":50,
      "value":92
    },
    {  
      "source":40,
      "target":45,
      "value":9
    },
    {  
      "source":50,
      "target":39,
      "value":30
    },
    {  
      "source":27,
      "target":43,
      "value":21
    },
    {  
      "source":35,
      "target":45,
      "value":16
    },
    {  
      "source":20,
      "target":9,
      "value":6
    },
    {  
      "source":24,
      "target":20,
      "value":29
    },
    {  
      "source":46,
      "target":50,
      "value":9
    },
    {  
      "source":24,
      "target":50,
      "value":9
    },
    {  
      "source":33,
      "target":35,
      "value":21
    },
    {  
      "source":44,
      "target":27,
      "value":22
    },
    {  
      "source":44,
      "target":47,
      "value":30
    },
    {  
      "source":35,
      "target":48,
      "value":29
    },
    {  
      "source":40,
      "target":9,
      "value":5
    },
    {  
      "source":44,
      "target":42,
      "value":37
    },
    {  
      "source":40,
      "target":15,
      "value":24
    },
    {  
      "source":15,
      "target":9,
      "value":8
    },
    {  
      "source":44,
      "target":22,
      "value":5
    },
    {  
      "source":36,
      "target":24,
      "value":10
    },
    {  
      "source":20,
      "target":43,
      "value":5
    },
    {  
      "source":33,
      "target":25,
      "value":6
    },
    {  
      "source":40,
      "target":47,
      "value":29
    },
    {  
      "source":0,
      "target":26,
      "value":22
    },
    {  
      "source":31,
      "target":20,
      "value":17
    }
  ],
  "nodes":[  
    {  
      "group":1,
      "name":"jayboice"
    },
    {  
      "group":1,
      "name":"hrfingerhut"
    },
    {  
      "group":1,
      "name":"mathisonian"
    },
    {  
      "group":1,
      "name":"walthickey"
    },
    {  
      "group":1,
      "name":"538viz"
    },
    {  
      "group":1,
      "name":"farai"
    },
    {  
      "group":1,
      "name":"cragcrest"
    },
    {  
      "group":1,
      "name":"reubenfb"
    },
    {  
      "group":1,
      "name":"carlbialik"
    },
    {  
      "group":1,
      "name":"bencasselman"
    },
    {  
      "group":1,
      "name":"burritobracket"
    },
    {  
      "group":1,
      "name":"leahlibresco"
    },
    {  
      "group":1,
      "name":"538bot"
    },
    {  
      "group":1,
      "name":"blytheterrell"
    },
    {  
      "group":1,
      "name":"natesilver538"
    },
    {  
      "group":1,
      "name":"kateelazegui"
    },
    {  
      "group":1,
      "name":"fstonenyc"
    },
    {  
      "group":1,
      "name":"atmccann"
    },
    {  
      "group":1,
      "name":"sarapatt"
    },
    {  
      "group":1,
      "name":"ollie"
    },
    {  
      "group":1,
      "name":"no_little_plans"
    },
    {  
      "group":1,
      "name":"bycoffe"
    },
    {  
      "group":1,
      "name":"neil_paine"
    },
    {  
      "group":1,
      "name":"forecasterenten"
    },
    {  
      "group":1,
      "name":"hemdash"
    },
    {  
      "group":1,
      "name":"kylenw"
    },
    {  
      "group":1,
      "name":"jodyavirgan"
    },
    {  
      "group":1,
      "name":"skepticalsports"
    },
    {  
      "group":1,
      "name":"datalab538"
    },
    {  
      "group":1,
      "name":"micahcohen"
    },
    {  
      "group":1,
      "name":"lisaechow"
    },
    {  
      "group":1,
      "name":"claremalone"
    },
    {  
      "group":1,
      "name":"simonelandon"
    },
    {  
      "group":1,
      "name":"hickoryhigh"
    },
    {  
      "group":1,
      "name":"annabarryjester"
    },
    {  
      "group":1,
      "name":"datadhrumil"
    },
    {  
      "group":1,
      "name":"chadwickmatlin"
    },
    {  
      "group":1,
      "name":"abbyabrams"
    },
    {  
      "group":1,
      "name":"paulschreiber"
    },
    {  
      "group":1,
      "name":"ellawinthrop"
    },
    {  
      "group":1,
      "name":"ritchiesking"
    },
    {  
      "group":1,
      "name":"mashfordg"
    },
    {  
      "group":1,
      "name":"andrewflowers"
    },
    {  
      "group":1,
      "name":"censusamericans"
    },
    {  
      "group":1,
      "name":"stephrooster"
    },
    {  
      "group":1,
      "name":"mattlanza"
    },
    {  
      "group":1,
      "name":"ascheink"
    },
    {  
      "group":1,
      "name":"guswez"
    },
    {  
      "group":1,
      "name":"pasthaaa"
    },
    {  
      "group":1,
      "name":"dgoldenberg"
    },
    {  
      "group":1,
      "name":"monachalabi"
    }
  ]
}

vis.js

/* global d3 window makeAnnotations */

const svg = d3.select('svg');
const width = +svg.attr('width');
const height = +svg.attr('height');

const color = d3.scaleOrdinal(d3.schemeCategory20)
  .range([
    '#e91e56', 
    '#00965f', 
    '#00bcd4', 
    '#3f51b5', 
    '#9c27b0', 
    '#ff5722', 
    '#cddc39', 
    '#607d8b', 
    '#8bc34a'
    ]);
const simulation = d3.forceSimulation()
    .force('link', d3.forceLink().id((d, i) => i))
    .force('charge', d3.forceManyBody().strength(-80))
    .force('center', d3.forceCenter(width / 2, height / 2));

d3.json('graph.json', (error, graph) => {
  if (error) throw error;
  const link = svg.append('g')
    .attr('class', 'links')
  .selectAll('line')
  .data(graph.links)
  .enter().append('line')
    .attr('stroke-width', d => Math.sqrt(d.value));

  const node = svg.append('g')
    .attr('class', 'nodes')
  .selectAll('circle')
  .data(graph.nodes)
  .enter().append('circle')
    .attr('r', d => (d.type === 'major' ? 9 : 3))
    .style('fill', d => d3.hsl(color(d.group)).darker())
    .style('fill-opacity', d => (d.type === 'other' ? 0.5 : 1));

  node.append('title')
      .text(d => d.name);

  window.collide = d3.bboxCollide(a => [[a.offsetCornerX - 5, a.offsetCornerY - 10], [a.offsetCornerX + a.width + 5, a.offsetCornerY + a.height + 5]])
 .strength(0.5)
 .iterations(1);

  window.yScale = d3.scaleLinear();

  simulation
      .nodes(graph.nodes)
      .on('tick', ticked)
      .on('end', () => {
        const noteBoxes = makeAnnotations.collection().noteNodes;

        window.labelForce = d3.forceSimulation(noteBoxes)
          .force('x', d3.forceX(a => a.positionX).strength(a => Math.max(0.25, Math.min(3, Math.abs(a.x - a.positionX) / 20))))
          .force('y', d3.forceY(a => a.positionY).strength(a => Math.max(0.25, Math.min(3, Math.abs(a.x - a.positionX) / 20))))
         .force('collision', window.collide)
          .alpha(0.5)
          .on('tick', (d) => {
            makeAnnotations.annotations()
              .forEach((d, i) => {
                const match = noteBoxes[i];
                d.dx = match.x - match.positionX;
                d.dy = match.y - match.positionY;
              });

            makeAnnotations.update();
          });
      });
  const nonOtherNodes = graph.nodes
    .filter(d => d.type !== 'other');

  simulation.force('link')
      .links(graph.links);
  function ticked() {
    link
        .attr('x1', d => d.source.x)
        .attr('y1', d => d.source.y)
        .attr('x2', d => d.target.x)
        .attr('y2', d => d.target.y);
    node
        .attr('cx', d => d.x)
        .attr('cy', d => d.y);

    makeAnnotations.annotations()
      .forEach((d, i) => {
        d.position = nonOtherNodes[i];
      });
  }

  window.makeAnnotations = d3.annotation()
    .type(d3.annotationLabel)
    .annotations(nonOtherNodes
    .map((d, i) => ({
      data: { x: d.x, y: d.y, group: d.group },
      note: { label: d.name,
        align: 'middle',
        orientation: 'fixed' },
      connector: { type: 'elbow' },
      className: d.type,
    })))
    .accessors({ x: d => d.x, y: d => d.y });

  svg.append('g')
    .attr('class', 'annotation-test')
    .call(makeAnnotations);

  svg.selectAll('.annotation-note text')
    .style('fill', d => color(d.data.group));

  svg.selectAll('.annotation-connector > path')
    .style('stroke', (d, i) => color(d.data.group));
});