block by micahstubbs bb8a7dd10eb6b2a05db14a52ff228bdf

clustered force layout, static data

Full Screen

a force layout where like-nodes cluster together, drawn from static data

an iteration on the block Clustered Force Layout 4.0 from @shancarter

while es-linting code from this block, I read up on operator precendence in Javascript. It turns out that multiplication * is evaluated before division /.

check out the companion repo for a nice commit history and related experiments

index.html

<!doctype html>
<meta charset='utf-8'>
<body>
<script src='//d3js.org/d3.v4.min.js'></script>
<script src='vis.js'></script>

graph.json

{
    "nodes": [
        {
            "cluster": 9,
            "r": 9.259670255973901
        },
        {
            "cluster": 7,
            "r": 8.909499905215805
        },
        {
            "cluster": 6,
            "r": 13.111974353489249
        },
        {
            "cluster": 8,
            "r": 11.373085883848557
        },
        {
            "cluster": 2,
            "r": 0.813474563335458
        },
        {
            "cluster": 3,
            "r": 4.341946600458814
        },
        {
            "cluster": 2,
            "r": 6.667333434466238
        },
        {
            "cluster": 9,
            "r": 7.911498350320349
        },
        {
            "cluster": 0,
            "r": 4.597604124486907
        },
        {
            "cluster": 6,
            "r": 4.340571868290901
        },
        {
            "cluster": 5,
            "r": 3.5878606980553065
        },
        {
            "cluster": 0,
            "r": 0.3818599744977188
        },
        {
            "cluster": 6,
            "r": 6.874201213903172
        },
        {
            "cluster": 7,
            "r": 11.988881995045055
        },
        {
            "cluster": 0,
            "r": 0.7375641615588722
        },
        {
            "cluster": 1,
            "r": 3.83142426518969
        },
        {
            "cluster": 5,
            "r": 2.7302570653623466
        },
        {
            "cluster": 7,
            "r": 2.1650055752224318
        },
        {
            "cluster": 7,
            "r": 3.7860658129035656
        },
        {
            "cluster": 4,
            "r": 16.962609275339474
        },
        {
            "cluster": 2,
            "r": 7.397340156592428
        },
        {
            "cluster": 2,
            "r": 6.999531169611245
        },
        {
            "cluster": 6,
            "r": 13.019894090019307
        },
        {
            "cluster": 0,
            "r": 4.046616197852555
        },
        {
            "cluster": 8,
            "r": 3.225328780272923
        },
        {
            "cluster": 8,
            "r": 6.087740584733343
        },
        {
            "cluster": 8,
            "r": 14.425967477661167
        },
        {
            "cluster": 8,
            "r": 3.34470377759449
        },
        {
            "cluster": 1,
            "r": 4.293510114124356
        },
        {
            "cluster": 7,
            "r": 11.914055313583855
        },
        {
            "cluster": 2,
            "r": 3.6810638302844323
        },
        {
            "cluster": 9,
            "r": 13.045300117867052
        },
        {
            "cluster": 0,
            "r": 1.9679969981361989
        },
        {
            "cluster": 4,
            "r": 6.002836962375587
        },
        {
            "cluster": 5,
            "r": 5.44559457861006
        },
        {
            "cluster": 3,
            "r": 7.112859827105364
        },
        {
            "cluster": 2,
            "r": 3.8631945337911615
        },
        {
            "cluster": 5,
            "r": 9.808344270855102
        },
        {
            "cluster": 8,
            "r": 8.786242195775312
        },
        {
            "cluster": 2,
            "r": 2.4304680551459286
        },
        {
            "cluster": 8,
            "r": 7.690584140956325
        },
        {
            "cluster": 1,
            "r": 3.8063210378920243
        },
        {
            "cluster": 5,
            "r": 7.604382514568928
        },
        {
            "cluster": 9,
            "r": 17.434628390107957
        },
        {
            "cluster": 0,
            "r": 1.6252513672061384
        },
        {
            "cluster": 2,
            "r": 7.331808927318587
        },
        {
            "cluster": 4,
            "r": 8.116763401587676
        },
        {
            "cluster": 6,
            "r": 6.903091426770327
        },
        {
            "cluster": 3,
            "r": 8.962587526121954
        },
        {
            "cluster": 4,
            "r": 5.087408184352244
        },
        {
            "cluster": 7,
            "r": 11.451560642415068
        },
        {
            "cluster": 3,
            "r": 11.261865276144224
        },
        {
            "cluster": 8,
            "r": 3.7619998884007604
        },
        {
            "cluster": 4,
            "r": 5.852465067502188
        },
        {
            "cluster": 3,
            "r": 2.2988242179838516
        },
        {
            "cluster": 4,
            "r": 19.169514948665487
        },
        {
            "cluster": 9,
            "r": 5.862770330322363
        },
        {
            "cluster": 2,
            "r": 9.351640223353849
        },
        {
            "cluster": 9,
            "r": 10.430131136161929
        },
        {
            "cluster": 7,
            "r": 5.289130100573111
        },
        {
            "cluster": 0,
            "r": 2.1931576543536737
        },
        {
            "cluster": 8,
            "r": 7.344952622197862
        },
        {
            "cluster": 3,
            "r": 6.362549353826214
        },
        {
            "cluster": 0,
            "r": 5.912921938589746
        },
        {
            "cluster": 5,
            "r": 5.84701314380495
        },
        {
            "cluster": 5,
            "r": 17.53188903716625
        },
        {
            "cluster": 8,
            "r": 16.454719754871196
        },
        {
            "cluster": 1,
            "r": 2.0967313521660165
        },
        {
            "cluster": 8,
            "r": 22.665073351721833
        },
        {
            "cluster": 0,
            "r": 2.3953220941716147
        },
        {
            "cluster": 8,
            "r": 17.398089063400704
        },
        {
            "cluster": 2,
            "r": 4.489631238988284
        },
        {
            "cluster": 1,
            "r": 1.5550861469241708
        },
        {
            "cluster": 7,
            "r": 6.828176179631646
        },
        {
            "cluster": 5,
            "r": 1.3949983557808792
        },
        {
            "cluster": 7,
            "r": 18.15519976143089
        },
        {
            "cluster": 4,
            "r": 10.777944013818828
        },
        {
            "cluster": 4,
            "r": 5.727158717707772
        },
        {
            "cluster": 7,
            "r": 15.263980953990075
        },
        {
            "cluster": 0,
            "r": 4.140056876451165
        },
        {
            "cluster": 3,
            "r": 11.809815703272445
        },
        {
            "cluster": 3,
            "r": 7.625769247350361
        },
        {
            "cluster": 8,
            "r": 11.670369407548918
        },
        {
            "cluster": 3,
            "r": 2.864976753122453
        },
        {
            "cluster": 1,
            "r": 5.782808182124143
        },
        {
            "cluster": 9,
            "r": 20.27296671509774
        },
        {
            "cluster": 8,
            "r": 7.424973928831013
        },
        {
            "cluster": 4,
            "r": 11.477056172978916
        },
        {
            "cluster": 7,
            "r": 13.072068416142244
        },
        {
            "cluster": 1,
            "r": 1.312838094842473
        },
        {
            "cluster": 3,
            "r": 8.5523149371797
        },
        {
            "cluster": 2,
            "r": 5.241904021515078
        },
        {
            "cluster": 3,
            "r": 11.448925815920003
        },
        {
            "cluster": 0,
            "r": 4.609895492082293
        },
        {
            "cluster": 9,
            "r": 8.588913842173824
        },
        {
            "cluster": 1,
            "r": 5.496249454373348
        },
        {
            "cluster": 4,
            "r": 4.9553056846580485
        },
        {
            "cluster": 2,
            "r": 6.951273479651984
        },
        {
            "cluster": 9,
            "r": 5.759588486541276
        },
        {
            "cluster": 5,
            "r": 0.8585655746100416
        },
        {
            "cluster": 8,
            "r": 5.4857925910781855
        },
        {
            "cluster": 4,
            "r": 9.036566271337456
        },
        {
            "cluster": 5,
            "r": 2.9200903345628006
        },
        {
            "cluster": 6,
            "r": 9.381396697639099
        },
        {
            "cluster": 3,
            "r": 6.095422235806083
        },
        {
            "cluster": 7,
            "r": 9.690503428977397
        },
        {
            "cluster": 3,
            "r": 6.006485216173571
        },
        {
            "cluster": 3,
            "r": 8.76405182817216
        },
        {
            "cluster": 5,
            "r": 0.4808168518632294
        },
        {
            "cluster": 2,
            "r": 11.450886171521642
        },
        {
            "cluster": 6,
            "r": 6.071099103644298
        },
        {
            "cluster": 1,
            "r": 6.034605011868475
        },
        {
            "cluster": 7,
            "r": 3.640626110465396
        },
        {
            "cluster": 5,
            "r": 6.129561103598028
        },
        {
            "cluster": 9,
            "r": 5.4690797797471316
        },
        {
            "cluster": 1,
            "r": 1.288352602570954
        },
        {
            "cluster": 1,
            "r": 4.503481956678496
        },
        {
            "cluster": 0,
            "r": 4.968285461344784
        },
        {
            "cluster": 5,
            "r": 4.869913292532537
        },
        {
            "cluster": 8,
            "r": 19.035513800218055
        },
        {
            "cluster": 2,
            "r": 5.1820315591317065
        },
        {
            "cluster": 7,
            "r": 18.328291456381145
        },
        {
            "cluster": 8,
            "r": 6.4765003613544705
        },
        {
            "cluster": 6,
            "r": 9.75904707313678
        },
        {
            "cluster": 3,
            "r": 4.071578675034565
        },
        {
            "cluster": 4,
            "r": 13.194936050984108
        },
        {
            "cluster": 0,
            "r": 4.155648062659441
        },
        {
            "cluster": 0,
            "r": 1.0802805219045832
        },
        {
            "cluster": 9,
            "r": 23.053981988652996
        },
        {
            "cluster": 5,
            "r": 2.463089780679736
        },
        {
            "cluster": 1,
            "r": 5.132590626539599
        },
        {
            "cluster": 7,
            "r": 6.93572766107949
        },
        {
            "cluster": 0,
            "r": 3.4720879272197287
        },
        {
            "cluster": 9,
            "r": 15.877291057948877
        },
        {
            "cluster": 9,
            "r": 14.134502338886765
        },
        {
            "cluster": 1,
            "r": 4.545784293988218
        },
        {
            "cluster": 2,
            "r": 9.317274725878152
        },
        {
            "cluster": 8,
            "r": 12.123802063260968
        },
        {
            "cluster": 0,
            "r": 5.321990904712712
        },
        {
            "cluster": 3,
            "r": 1.4041552057405893
        },
        {
            "cluster": 4,
            "r": 9.920924102977988
        },
        {
            "cluster": 5,
            "r": 5.478774195361606
        },
        {
            "cluster": 9,
            "r": 9.526789295392865
        },
        {
            "cluster": 4,
            "r": 6.518980383115273
        },
        {
            "cluster": 9,
            "r": 14.995010382404313
        },
        {
            "cluster": 9,
            "r": 16.642003223956607
        },
        {
            "cluster": 5,
            "r": 4.114844420766859
        },
        {
            "cluster": 7,
            "r": 0.49892292359509965
        },
        {
            "cluster": 3,
            "r": 6.275627467950188
        },
        {
            "cluster": 3,
            "r": 9.81168599309813
        },
        {
            "cluster": 6,
            "r": 12.64377750017487
        },
        {
            "cluster": 3,
            "r": 12.797889781218368
        },
        {
            "cluster": 4,
            "r": 7.618020914702662
        },
        {
            "cluster": 5,
            "r": 10.731774476576987
        },
        {
            "cluster": 5,
            "r": 9.716585571655193
        },
        {
            "cluster": 6,
            "r": 18.565683428845595
        },
        {
            "cluster": 6,
            "r": 3.8217862145573287
        },
        {
            "cluster": 3,
            "r": 3.8588917727479473
        },
        {
            "cluster": 8,
            "r": 4.4774999074563855
        },
        {
            "cluster": 3,
            "r": 4.318195408619502
        },
        {
            "cluster": 2,
            "r": 6.328501633198946
        },
        {
            "cluster": 1,
            "r": 4.094078339502131
        },
        {
            "cluster": 3,
            "r": 3.8588240066751505
        },
        {
            "cluster": 1,
            "r": 4.004632383005384
        },
        {
            "cluster": 6,
            "r": 13.881526898543534
        },
        {
            "cluster": 4,
            "r": 12.506719650183388
        },
        {
            "cluster": 4,
            "r": 0.487449443958749
        },
        {
            "cluster": 3,
            "r": 2.993864029861696
        },
        {
            "cluster": 1,
            "r": 3.4358535790337443
        },
        {
            "cluster": 9,
            "r": 11.61416030506196
        },
        {
            "cluster": 6,
            "r": 13.775399779244715
        },
        {
            "cluster": 8,
            "r": 7.873977390020491
        },
        {
            "cluster": 1,
            "r": 3.097979808541668
        },
        {
            "cluster": 8,
            "r": 9.31232564478648
        },
        {
            "cluster": 6,
            "r": 11.940072218061433
        },
        {
            "cluster": 0,
            "r": 4.10117521077354
        },
        {
            "cluster": 7,
            "r": 10.51899397149345
        },
        {
            "cluster": 8,
            "r": 8.865336255450714
        },
        {
            "cluster": 6,
            "r": 6.963356353488244
        },
        {
            "cluster": 5,
            "r": 2.4384432695021627
        },
        {
            "cluster": 4,
            "r": 7.6272270137507245
        },
        {
            "cluster": 1,
            "r": 1.1509093147707856
        },
        {
            "cluster": 3,
            "r": 11.79628492531506
        },
        {
            "cluster": 1,
            "r": 9.39586561627845
        },
        {
            "cluster": 8,
            "r": 5.451120564138922
        },
        {
            "cluster": 9,
            "r": 25.661014262625976
        },
        {
            "cluster": 5,
            "r": 16.70431528730784
        },
        {
            "cluster": 8,
            "r": 5.789847734049817
        },
        {
            "cluster": 3,
            "r": 7.967538691580401
        },
        {
            "cluster": 6,
            "r": 16.50366405247469
        },
        {
            "cluster": 2,
            "r": 8.28219564420553
        },
        {
            "cluster": 3,
            "r": 8.152748149145362
        },
        {
            "cluster": 8,
            "r": 5.220993365370065
        },
        {
            "cluster": 5,
            "r": 7.2993278654242815
        },
        {
            "cluster": 4,
            "r": 7.2263395643139
        },
        {
            "cluster": 2,
            "r": 12.25015185685546
        },
        {
            "cluster": 4,
            "r": 1.6578362894228034
        },
        {
            "cluster": 1,
            "r": 4.094982685322045
        },
        {
            "cluster": 6,
            "r": 13.592577795980258
        },
        {
            "cluster": 6,
            "r": 7.6784326664344835
        }
    ],
    "links": []
}

vis.js

/* global d3 */

d3.json('graph.json', (error, graph) => {
  const nodes = graph.nodes;
  console.log('nodes', nodes);

  const margin = { top: 100, right: 100, bottom: 100, left: 100 };

  const width = 960;
  const height = 500;

  // separation between same-color circles
  const padding = 1.5;

  // separation between different-color circles
  const clusterPadding = 6;

  const maxRadius = 12;

  const z = d3.scaleOrdinal(d3.schemeCategory20);

  // total number of nodes
  const n = nodes.length;

  // collect clusters from nodes
  const clusters = {};
  nodes.forEach((node) => {
    const radius = node.r;
    const clusterID = node.cluster;
    if (!clusters[clusterID] || (radius > clusters[clusterID].r)) { 
      clusters[clusterID] = node;
    }
  });
  console.log('clusters', clusters);

  const svg = d3.select('body')
    .append('svg')
    .attr('height', height)
    .attr('width', width)
    .append('g')
      .attr('transform', `translate(${width / 2},${height / 2})`);

  const circles = svg.append('g')
    .datum(nodes)
    .selectAll('.circle')
    .data(d => d)
    .enter().append('circle')
      .attr('r', d => d.r)
      .attr('fill', d => z(d.cluster))
      .attr('stroke', 'black')
      .attr('stroke-width', 1);

  const simulation = d3.forceSimulation(nodes)
    .velocityDecay(0.2)
    .force('x', d3.forceX().strength(0.0005))
    .force('y', d3.forceY().strength(0.0005))
    .force('collide', collide)
    .force('cluster', clustering)
    .on('tick', ticked);

  function ticked() {
    circles
      .attr('cx', d => d.x)
      .attr('cy', d => d.y);
  }

  // These are implementations of the custom forces
  function clustering(alpha) {
    nodes.forEach((d) => {
      const cluster = clusters[d.cluster];
      if (cluster === d) return;
      let x = d.x - cluster.x;
      let y = d.y - cluster.y;
      let l = Math.sqrt((x * x) + (y * y));
      const r = d.r + cluster.r;
      if (l !== r) {
        l = ((l - r) / l) * alpha;
        d.x -= x *= l;
        d.y -= y *= l;
        cluster.x += x;
        cluster.y += y;
      }
    });
  }

  function collide(alpha) {
    const quadtree = d3.quadtree()
      .x(d => d.x)
      .y(d => d.y)
      .addAll(nodes);

    nodes.forEach((d) => {
      const r = d.r + maxRadius + Math.max(padding, clusterPadding);
      const nx1 = d.x - r;
      const nx2 = d.x + r;
      const ny1 = d.y - r;
      const ny2 = d.y + r;
      quadtree.visit((quad, x1, y1, x2, y2) => {
        if (quad.data && (quad.data !== d)) {
          let x = d.x - quad.data.x;
          let y = d.y - quad.data.y;
          let l = Math.sqrt((x * x) + (y * y));
          const r = d.r + quad.data.r + (d.cluster === quad.data.cluster ? padding : clusterPadding);
          if (l < r) {
            l = ((l - r) / l) * alpha;
            d.x -= x *= l;
            d.y -= y *= l;
            quad.data.x += x;
            quad.data.y += y;
          }
        }
        return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
      });
    });
  }
});