block by fil 56c02237d9951c4bb12ed41cf64d6e4c

Urquhart Force Mesh

Full Screen

Some force-directed points, with the Urquhart graph overlaid. Thanks @enjalot for the idea and time wasted :)

In another variant I toss in some Brownian motion.

Forked from erlenstar‘s block: Delaunay Force Mesh II

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.js"></script>
<script>

var width = 960,
    height = 500,
    τ = 2 * Math.PI,
    maxLength = 10000,
    maxLength2 = maxLength * maxLength;

var nodes = d3.range(200).map(function() {
  return {
    x: Math.random() * width,
    y: Math.random() * height
  };
});

var force = d3.forceSimulation()
    .nodes(nodes.slice())
    .force("link", d3.forceLink().id(function(d) { return d.id; }))
    .force("charge", d3.forceManyBody().strength(function(d,i){
      return i ? -25 : -1000
    }))
    .force("x", d3.forceX())
    .force("y", d3.forceY())
    .force("center", d3.forceCenter(width / 2, height / 2))
    .on("tick", ticked);

  
  
var voronoi = d3.voronoi()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; });

var canvas = d3.select("body").append("canvas")
    .attr("width", width)
    .attr("height", height)
    .on("ontouchstart" in document ? "touchmove" : "mousemove", moved);

var context = canvas.node().getContext("2d");

function moved() {
  var p1 = d3.mouse(this);
  nodes[0].fx = p1[0];
  nodes[0].fy = p1[1];
  force.alpha(0.1).restart();
}

  
function urquhart(diagram){
    var urquhart = d3.map();
    diagram.links()
    .forEach(function(link) {
      var v = d3.extent([link.source.index, link.target.index]);
      urquhart.set(v, link);
    });
    urquhart._remove = [];
    diagram.triangles()
    .forEach(function(t) {

      var l = 0, length = 0, i="bleh", v;
      for (var j=0; j<3; j++) {
        var a = t[j], b = t[(j+1)%3];
        v = d3.extent([a.index, b.index]);
        length = (a.x-b.x) * (a.x-b.x) + (a.y-b.y) * (a.y-b.y);
        if (length > l) {
          l = length;
          i = v;
        }
      }
      urquhart._remove.push(i);
    });
     //console.log(JSON.stringify(urquhart._remove))
    urquhart._remove.forEach(function(i) {
      if (urquhart.has(i)) urquhart.remove(i);
    });
    return urquhart.values();
  }

  
function ticked() {
  var diagram = voronoi(nodes);

  //var links = diagram.links();
  var links = urquhart(diagram);
  
  context.clearRect(0, 0, width, height);

  context.beginPath();
  for (var i = 0, n = links.length; i < n; ++i) {
    var link = links[i],
        dx = link.source.x - link.target.x,
        dy = link.source.y - link.target.y;
    if (dx * dx + dy * dy < maxLength2) {
      context.moveTo(link.source.x, link.source.y);
      context.lineTo(link.target.x, link.target.y);
    }
  }
  context.lineWidth = 1;
  context.strokeStyle = "#bbb";
  context.stroke();

  context.beginPath();
  for (var i = 0, n = nodes.length; i < n; ++i) {
    var node = nodes[i];
    context.moveTo(node.x, node.y);
    context.arc(node.x, node.y, 2, 0, τ);
  }
  context.lineWidth = 3;
  context.strokeStyle = "#fff";
  context.stroke();
  context.fillStyle = "#000";
  context.fill();
}

</script>