Reposition nodes in a multi-foci d3 force layout

╄→гoц情女王★ 提交于 2019-12-07 08:58:17

问题


I have three sets of nodes in a multi-foci force layout. Each node is already rendered in HTML.

The only thing that changes is the data bound to each little person.

What I'm trying to do is when an event happens (in this situation a dropdown), the nodes reposition themselves to their new cluster.

What I mean by that is, if new data calls for only 10 blues on the left and 90 reds on the right and 2 purples in the middle, the blues on the left will change to red and transition to the right cluster.

From what I've read thus far, I think it might have be changing the gravity. However, I'm not seeing this as an option in D3 V4.

The closest example I can find to what I want to accomplish is here.

Here's what my force layout code looks like:

var node = this.svg.selectAll('path')
    .data(data);

// foci is a dictionary that assigns the x and y value based
// on what group a node belongs to.
var foci = {
    "Blue" : {
         "x" : xScale(0),
         "y": height / 2
    },
    "Red": {
         "x" : xScale(1),
         "y": height / 2
    },
    "Purple": {
         "x" : xScale(2),
         "y": height / 2
    },
};

// This helped me position the nodes to their assigned clusters.
var forceX = d3.forceX((d) => foci[d.group].x);
var forceY = d3.forceY((d) => foci[d.group].y);

var force = d3.forceSimulation(data)
    .force('x', forceX)
    .force('y', forceY)
    .force("collide", d3.forceCollide(8))
    .on('tick', function() {
         node
            .attr('transform', (d) => {
                return 'translate(' + (d.x - 100) + ',' + (-d.y + 25) + ')';
            });
        });

What I have been able to accomplish so far is redraw the layouts based on a change in the dropdown, which reinitilizes d3.forceSimulation() and makes the clusters snap back on the page, as you can see in the gif below.

That is not what I want. I'm trying to make the rearranging as seamless as possible.

UPDATE: By not reinitializing the d3.forceSimulation(), I can bind the new data to the nodes and change their colors. Now is just moving them to their appropriate clusters.


回答1:


Instead of reinitialising d3.forceSimulation() you can simply reheat the simulation, using restart():

Restarts the simulation’s internal timer and returns the simulation. In conjunction with simulation.alphaTarget or simulation.alpha, this method can be used to “reheat” the simulation during interaction, such as when dragging a node, or to resume the simulation after temporarily pausing it with simulation.stop.

I created a demo to show you, using parts of your code. In this demo, the button randomizes the color of each data point. After that, we reheat the simulation:

force.alpha(0.8).restart();

Check it, clicking "Randomize":

var width = 500, height = 200;

var svg = d3.select("#svgdiv")
	.append("svg")
	.attr("width", width)
	.attr("height", height);
	
var data = d3.range(100).map(function(d, i){
	return {
	group: Math.random()*2 > 1 ? "blue" : "red",
	id: i
	}
});

var xScale = d3.scaleOrdinal()
	.domain([0, 1])
	.range([100, width-100]);

var foci = {
    "blue" : {
         "x" : xScale(0),
         "y": height / 2
    },
    "red": {
         "x" : xScale(1),
         "y": height / 2
    }
};

var forceX = d3.forceX((d) => foci[d.group].x);
var forceY = d3.forceY((d) => foci[d.group].y);

var node = svg.append("g")
            .attr("class", "nodes")
            .selectAll("circle")
            .data(data)
            .enter().append("circle")
            .attr("r", 5)
						.attr("fill", (d)=>d.group);


var force = d3.forceSimulation(data)
    .velocityDecay(0.65)
    .force('x', forceX)
    .force('y', forceY)
    .force("collide", d3.forceCollide(8));
		
force.nodes(data)
    .on('tick', function() {
         node
            .attr('transform', (d) => {
                return 'translate(' + (d.x) + ',' + (d.y) + ')';
            });
        });
				
d3.select("#btn").on("click", function(){
    data.forEach(function(d){
		d.group = Math.random()*2 > 1 ? "blue" : "red"
		})
		node.transition().duration(500).attr("fill", (d)=>d.group);
		setTimeout(function(){
		force.nodes(data);
		force.alpha(0.8).restart();
		}, 1500)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<button id="btn">Randomize</button>
<div id="svgdiv"><div>

PS: I put the reheat inside a setTimeout, so you can first see the circles changing colours and, then, moving to the foci positions.



来源:https://stackoverflow.com/questions/40413970/reposition-nodes-in-a-multi-foci-d3-force-layout

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!