在多焦点d3力布局中重新放置节点 [英] Reposition nodes in a multi-foci d3 force layout

查看:139
本文介绍了在多焦点d3力布局中重新放置节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在多焦点强制布局中有三组节点.每个节点已经以HTML呈现.

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.

我的意思是,如果新数据只需要左侧10个蓝色,右侧90个红色和中间2个紫色,则左侧的蓝色将变为红色并过渡到右侧群集.

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.

根据我到目前为止所读的内容,我认为它可能正在改变引力.但是,我不认为这是D3 V4中的一个选项.

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) + ')';
            });
        });

到目前为止,我已经能够完成的工作是根据下拉菜单中的更改来重新绘制布局,这会重新初始化d3.forceSimulation()并使群集重新回到页面上,如下面的gif所示.

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.

更新:通过不重新初始化d3.forceSimulation(),我可以将新数据绑定到节点并更改其颜色.现在只是将它们移至适当的群集.

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.

推荐答案

您可以使用

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

重新启动模拟的内部计时器并返回模拟.结合simulation.alphaTarget或simulation.alpha,此方法可用于在交互过程中(例如,拖动节点时)重新加热"仿真,或在使用simulation.stop暂时将其暂停后恢复仿真.

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:我将 reheat 放在setTimeout内,因此您可以首先看到圆圈改变颜色,然后移动到焦点位置.

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

这篇关于在多焦点d3力布局中重新放置节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆