javascriptd3.jsforce-layout

d3 force layout nodes in predictable order


Whenever you refresh this example, the nodes are in a different order. How could you make it so the order is the same upon each refresh? Perhaps "sub node 1" at the top, then adjacent to the right is "sub node 2", etc, all the way around. Other than that, this example works for what I need to achieve.

The solutions below seem to require fixing the nodes to x,y points. But doing that seems to eliminate the drag functionality (the nodes need to be able to be dragged into different locations to change the order). Also, I don't always know how many nodes there will be initially.

https://groups.google.com/forum/#!topic/d3-js/NsHlubbv3pc

Calm down initial tick of a force layout

Configure fixed-layout static graph in d3.js

While the drag is a requirement, the animation is not. I tried seeing if stoping the animation had any effect, but it didn't.

var n = 50;

for (var i = 0; i < n; ++i) force.tick();

force.stop();

Also, tried adding a new property to the data giving each child a rank to manipulate somehow. And tried assigning id of the rank and sorting. Also tried using the index number of the array of objects. No luck. Thanks for any ideas.


Solution

  • Per Lars' comment, it seems this is not possible in a force layout. Switched to tree layout. JSFIDDLE Still need to add the drag piece. Will update if I can get that working.

    Here is the JS code:

    var diameter = 800;
    
    var tree = d3.layout.tree()
        .size([360, diameter / 2 - 120])
        .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
    
    var svg = d3.select("body").append("svg")
        .attr("width", diameter)
        .attr("height", diameter)
        .append("g")
        .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
          
    var data = {
     "name": "root",
     "children": [
        {"name": "circle 1"},
        {"name": "circle 2"},
        {"name": "circle 3"},
        {"name": "circle 4"},
        {"name": "circle 5"},
        {"name": "circle 6"},
        {"name": "circle 7"},
        {"name": "circle 8"},
        {"name": "circle 9"},
        {"name": "circle 10"},
        {"name": "circle 11"},
        {"name": "circle 12"},
        {"name": "circle 13"}
     ]
    }
    
        var nodes = tree.nodes(data),
            links = tree.links(nodes);
    
        var lines = svg.selectAll('line')
            .data(links)
            .enter()
            .append('line')
            .attr('stroke','#ccc');
    
        lines.attr('x1',function(d){ return d.source.y })
            .attr('y1',function(d){ return d.source.x/ 180 * Math.PI })
            .attr('x2',function(d){ return d.target.y })
            .attr('y2',function(d){ return d.target.x / 180 * Math.PI });
    
        lines.attr("transform", function(d) {      
            return "rotate(" + (d.target.x - 90 ) + ")"; });
    
        var link = svg.selectAll(".link")
            .data(links)
            .enter().append("path")
            .attr("class", "link");
    
        var node = svg.selectAll(".node")
            .data(nodes)
            .enter().append("g")
            .attr("class", "node")
            .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")rotate(" + (-d.x + 90) + ")"; });
    
        node.append("circle")
            .attr("r", 48)
            .style("fill", "#ccc")
            .on("dblclick", function(d){
                d3.select(this)
                    .attr('r', function(d) {
                        if (d.group === "Hub") {
                            return 80;
                        } else {
                            return 30;
                        }
                    })
                }
            );
    
        node.append("text")
            .attr("dy", ".31em")
            .attr("text-anchor", "middle")
            .text(function(d) { return d.name; });
    
    d3.select(self.frameElement).style("height", diameter - 150 + "px");