block by nsonnad 5993342

Circle-bound D3 force layout

Full Screen

Circle-bound nodes in d3.layout.force(), using a variation of Mike Bostock’s rectangular Bounded Force Layout. Here we calculate the maximum and minimum x for a given y in the circle, and vice versa.

index.html

<html>
<head>
    <title>Circular bounds</title>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="chart"></div>

<script>
var margin = 30,
    w = 500 - margin * 2,
    h = w,
    radius = w / 2,
    strokeWidth = 4,
    hyp2 = Math.pow(radius, 2),
    nodeBaseRad = 5;

var svg = d3.select('#chart').append('svg')
    .attr('width', w + margin * 2)
    .attr('height', h + margin * 2)
.append('g')
    .attr('transform', 'translate(' + margin + ',' + margin + ')');

var pool = svg.append('circle')
    .style('stroke-width', strokeWidth * 2)
    .attr({
        class: 'pool',
        r: radius,
        cy: 0,
        cx: 0,
        transform: 'translate(' + w / 2 + ',' + h / 2 + ')'
    });

function randomNodes(n) {
    var data = [],
        range = d3.range(n);

    for (var i = range.length - 1; i >= 0; i--) {
        data.push({
            rad: Math.floor(Math.random() * 12)
        });
    }
    return data;
}

var nodeData = randomNodes(80);

var force = d3.layout.force()
    .charge(-50)
    .gravity(0.05)
    .nodes(nodeData)
    .size([w, h]);

var nodes = svg.selectAll('.nodes')
    .data(nodeData)
.enter().append('circle')
    .attr({
        class: 'nodes',
        r: function (d) { return d.rad + nodeBaseRad; },
        fill: "#fff8d1"
    })

function pythag(r, b, coord) {
    r += nodeBaseRad;

    // force use of b coord that exists in circle to avoid sqrt(x<0)
    b = Math.min(w - r - strokeWidth, Math.max(r + strokeWidth, b));

    var b2 = Math.pow((b - radius), 2),
        a = Math.sqrt(hyp2 - b2);

    // radius - sqrt(hyp^2 - b^2) < coord < sqrt(hyp^2 - b^2) + radius
    coord = Math.max(radius - a + r + strokeWidth,
                Math.min(a + radius - r - strokeWidth, coord));

    return coord;
}

function tick() {
    nodes.attr('cx', function (d) { return d.x = pythag(d.rad, d.y, d.x); })
        .attr('cy', function (d) { return d.y = pythag(d.rad, d.x, d.y); });
}

nodes.call(force.drag);

force.on('tick', tick)
    .start();
</script>
</body>
</html>

style.css

#chart {
    width: 600px;
    margin: 0 auto;
}

.pool {
    fill: #3db1ff;
    stroke: #4f4f4f;
}