我正在建造一个春天"使用d3-force布局。我想操纵它的属性,比如" strength"和"距离"通过用户输入。为此,我目前正在使用"输入范围滑块"。为了更好地理解,我在codepen上设置了一个工作草案,该问题涉及:http://codepen.io/bitHugger/pen/XNqGNE?editors=1010
HTML:
<input id="strengthElem" step="0.1" type="range" min="0" max="2"/>
我想像这样处理事件:
let strengthElem = window.document.getElementById('strengthElem');
let strength;
strengthElem.addEventListener('click', function(evt) {
strength = strengthElem.value;
console.log('strength', strength);
}, false);
现在我想在范围滑块发生某些交互时重新启动或重新计算d3.simulation对象。这是我目前的模拟:
let simulation = d3.forceSimulation().nodes(nodes)
.force("link", d3.forceLink()
.id(function(d) { return d.index; })
.strength(function(d) { return 2; })
.distance(function(d) { return 2; }))
.force("charge", d3.forceManyBody());
对于强度和距离,当前值是硬编码的。我想将其更改为例如:
.strength(function(d) { return strength; })
.distance(function(d) { return distance; })
我尝试设置d3.call()。on()函数但无法使其正常工作。我想知道如何基于unser输入操作模拟,这种输入发生在svg容器外的force()函数之外。
可悲的是,我无法正常工作,我不知道如何设置一个正确的d3事件监听器,它对输入按钮做出反应,然后根据更改的值重新计算力布局。有什么想法吗?
答案 0 :(得分:4)
不应在不保持对力的引用的情况下就地创建链接力,而应首先创建力并将引用传递给模拟。这样,你以后就可以根据你的滑块来操纵力量了。值:
// Create as before, but keep a reference for later manipulations.
let linkForce = d3.forceLink()
.id(function(d) { return d.index; })
.strength(2)
.distance(2);
let simulation = d3.forceSimulation().nodes(nodes)
.force("link", linkForce)
.force("charge", d3.forceManyBody());
在滑块上注册事件处理程序时,您可能还希望使用d3.select()
以便于使用,并使用selection.on()
分配函数。
d3.select('#strengthElem')
.on('click', function() {
// Set the slider's value. This will re-initialize the force's strenghts.
linkForce.strength(this.value);
simulation.alpha(0.5).restart(); // Re-heat the simulation
}, false);
d3.select('#distanceElem')
.on('click', function(evt) {
// Set the slider's value. This will re-initialize the force's strenghts
linkForce.distance(this.value);
simulation.alpha(0.5).restart(); // Re-heat the simulation
}, false);
在处理程序函数this
中指向实际的DOM元素,从而允许轻松访问滑块的值。现在可以使用先前保持的参考来更新链接力的参数。剩下要做的就是重新加热模拟以继续计算。
查看此片段以获取有效的演示:
'use strict';
var route = [[30, 30],[192, 172],[194, 170],[197, 167],[199, 164],[199, 161],[199, 157],[199, 154],[199, 150],[199, 147],[199, 143],[199, 140],[200, 137],[202, 134],[204, 132],[207, 129],[207, 126],[200, 200]];
let distance = 1;
let createNode = function(id, coords) {
return {
radius: 4,
x: coords[0],
y: coords[1],
};
};
let getNodes = (route) => {
let d = [];
let i = 0;
route.forEach(function(coord) {
if(i === 0 || i === route.length-1) {
d.push(createNode(i, coord));
d[i].fx = coord[0];
d[i].fy = coord[1];
}
else {
d.push(createNode(i, coord));
}
++i;
});
return d;
};
let getLinks = (nodes) => {
let next = 1;
let prev = 0;
let obj = [];
while(next < nodes.length) {
obj.push({source: prev, target: next, value: 1});
prev = next;
++next;
}
return obj;
};
let force = function(route) {
let width = 900;
let height = 700;
let nodes = getNodes(route);
let links = getLinks(nodes);
d3.select('#strengthElem')
.on('click', function() {
linkForce.strength(this.value); // Set the slider's value. This will re-initialize the force's strenghts
simulation.alpha(0.5).restart(); // Re-heat the simulation
}, false);
d3.select('#distanceElem')
.on('click', function(evt) {
linkForce.distance(this.value); // Set the slider's value. This will re-initialize the force's strenghts
simulation.alpha(0.5).restart(); // Re-heat the simulation
}, false);
let linkForce = d3.forceLink()
.id(function(d) { return d.index; })
.strength(2)
.distance(2);
let simulation = d3.forceSimulation().nodes(nodes)
.force("link", linkForce)
.force("charge", d3.forceManyBody());
let svg = d3.select('svg').append('svg')
.attr('width', width)
.attr('height', height);
let link = svg.append("g")
.attr('class', 'link')
.selectAll('.link')
.data(links)
.enter().append('line')
.attr("stroke-width", 1);
let node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", function(d) { return d.radius; })
.attr("fill", function(d) { return '#fabfab'; });
simulation.nodes(nodes).on("tick", ticked);
simulation.force("link").links(links);
function ticked() {
link
.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; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
};
force(route);
&#13;
.link {
stroke: #777;
stroke-width: 2px;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
&#13;
<script src="https://d3js.org/d3.v4.js"></script>
<div>Strength <input id="strengthElem" step="0.1" type="range" min="0" max="2"/></div>
<div>Distance <input id="distanceElem" step="1" type="range" min="0" max="50"/></div>
<svg style="width: 900; height: 700;"></svg>
&#13;
我也相应地更新了codepen。
答案 1 :(得分:1)
一种方法是删除svg的内容并使用所需的常量重新绘制它。
我不明白你被困在哪里,因为我只改变了你在问题中所说的几行。
在你的点击处理程序中,我清除了svg的内容并调用了“draw”函数:
strengthElem.addEventListener('click', function(evt) {
strength = strengthElem.value;
console.log('strength', strength);
d3.select('svg').selectAll("*").remove();
force(route);
}, false);
将配置变量移至全局范围:
var distance = 1;
let distElem = window.document.getElementById('distanceElem');
let strengthElem = window.document.getElementById('strengthElem');
var strength = strengthElem.value;
distance = distElem.value;
就像你说我已经改变为返回参数:
.strength(function(d) { return strength; })
.distance(function(d) { return distance; }))