"Pinning" of nodes in a D3 force plot

I've built a regular "grid" that places the nodes at evenly spaced points along the grid. Then, by randomizing the linkDistance, I can "fire up" the grid so that it is less regular.

I would like to "snap" all the endpoints so that they do not move, leaving only the interior points that are affected by the force layout.

My understanding was that since this is a regular square grid, any points with a weight less than 4 will be an "edge" point and therefore should be anchored.

I suppose the weight is only calculated after the nodes and links have been added to the force layout, so I go forEach

through the array nodes

by adding it to the force layout and conditionally setting the fixed

weight based property.

Then I re-use the property nodes

and start

for the simulation.

Not good. In the example, I attach ALL the move points.

force = d3.layout.force()
                .size( [w, h ] )
                .nodes( nodes )
                .links( links )
                .linkDistance( function( d ){ return Math.random() * GRID_SPACING; } )
                .linkStrength( 1 )
                .charge( 0 )
                .gravity( 0 )
                .friction( .5 )
                .on( "tick", function() {
                    d3links.attr("x1", function(d) { return d.source.x; })
                        .attr("y1", function(d) { return d.source.y; })
                        .attr("x2", function(d) { return d.target.x; })
                        .attr("y2", function(d) { return d.target.y; });

                    d3nodes.attr("cx", function(d) { return d.x; })
                        .attr("cy", function(d) { return d.y; });
                } );

            // "Pin" all the edge nodes.
            nodes.forEach( function( node ){
                if ( node.weight < 4 ){
                    node.fixed = true;
                }
            } );

            force.nodes( nodes ).start();

      

+3


source to share


1 answer


Your discernment is good! But timing is everything ... The "start" event is fired after the weights have been initialized, so this should work ...

force = d3.layout.force()
    .size([w, h])
    .nodes(nodes)
    .links(links)
    .linkDistance(function (d) { return Math.random() * GRID_SPACING; })
    .linkStrength(1)
    .charge(0)
    .gravity(0)
    .friction(.5)
    .on("tick", function () {
        d3links.attr("x1", function (d) { return d.source.x; })
                .attr("y1", function (d) { return d.source.y; })
                .attr("x2", function (d) { return d.target.x; })
                .attr("y2", function (d) { return d.target.y; });

        d3nodes.attr("cx", function (d) { return d.x; })
                .attr("cy", function (d) { return d.y; });
    })
    .on("start", function () {
        // "Pin" all the edge nodes.
        nodes.forEach(function (node) {
            if (node.weight < 4) {
                node.fixed = true;
            }
        });
    })


force.nodes(nodes).start();

      



(If you want to enable drag mode, you will also need to re-commit after dragend.)

+1


source







All Articles