我有一段时间难以根据用户输入添加和删除节点,这是通过每次用户从复选框列表中选择要查看的内容时更新推送到force.nodes()的整个对象集来解决的。
从滑块更新但我认为需要更精细的触摸 - 我不想每次移动滑块时更新整个设置。我想在force.nodes()中推送和弹出节点。
使用我当前的代码,节点出来就好了 - 他们只是不再回来了。这里是jsfiddle - https://jsfiddle.net/hiwilson1/ancmtxux/3/
这是造成问题的部分;
function brushed() {
var exists;
//var newd = new Date(2013, 05, 01)
data.forEach(function(d, i) {
//if data point in range (between extent 0 and 1)
if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) {
exists = force.nodes().some(function(node, i) {
//check if data point already exists in force.nodes()
return (node.mIndex == d.mIndex)
})
console.log(exists)
if (!exists) {
force.nodes().push(d)
}
}
else {
force.nodes().splice(i, 1)
}
})
d3.select("#nodeCount").text(force.nodes().length)
}
对于每个数据点,我都要检查点是否位于extent()[0]和[1]之间。如果是,请检查force.nodes()以查看它当前是否是其中的成员。如果不是,则将其推入force.nodes()。
如果数据点不在范围之间,则将其与force.nodes()拼接。最后一点工作正常。
更新:已修复。我实际上也研究了如何过滤连接到节点的链接。 jsfiddle here - https://jsfiddle.net/hiwilson1/7oumeat5/2/。切勿尝试使用索引/硬编码索引执行此操作,将节点/链接作为对象进行比较。
顺便说一下,我看到了节点顶部的链接。如果有某种方法可以解决这个问题,我很乐意听到它。
进一步更新:为确保节点后面的链接使用.insert(&#34; line&#34;,&#34;:first-child&#34;)代替.append(&#34; line&#34; )
答案 0 :(得分:2)
您当前的代码存在一些问题。根本问题在于您将data
赋予force.nodes()
,这意味着两个数据结构实际上是相同的。也就是说,当您从force.nodes()
中删除元素时,您也会修改基础data
。因此你无法将它们添加回来 - 它们已经消失了。
通过将data
的副本传递给force.nodes()
:
var force = d3.layout.force()
.nodes(JSON.parse(JSON.stringify(data)))
然后您从force.nodes()
删除了错误的节点 - 您使用的索引是data
,而不是force.nodes()
。您可以在data
中计算force.nodes()
元素的索引,并像这样使用它:
data.forEach(function(d, i) {
var idx = -1;
force.nodes().forEach(function(node, j) {
if(node.mIndex == d.mIndex) {
idx = j;
}
});
//if data point in range (between extent 0 and 1)
if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) {
if (idx == -1) {
force.nodes().push(d)
}
}
else if(idx > -1) {
force.nodes().splice(idx, 1)
}
最后,您需要在force.start()
结束时致电brushed
,以便在布局确定后更改可见。
完整示例here。
答案 1 :(得分:1)
在强大的@Lars Kotthoff解决问题的基础上,我将重点关注架构。这是一个更简单,更符合d3习语的架构。
主要原则是:
Array.prototype.filter()
管理数据brushed
事件是数据事件,tick
例程是动画事件。在不需要的情况下管理动画事件的数据并不是最佳选择。第1点的好处是过滤后的数组实际上是对原始data
元素的引用数组,因此当在复制的数组上添加额外状态时,它实际上会添加到原始data
元素上。 1}}数组。因此,先前的状态在被过滤回来时是可用的,因此平滑的退出和进入行为。同时,当画笔过滤时,原始data
中不会删除任何元素:只有对它们的引用才会在克隆数组中删除。我不得不承认我没想到,但这是一个很好的发现,即使是偶然的!
当然这只能起作用,因为数组元素是对象。
这里的工作示例......
var width = 700,
height = 600,
padding = 20;
var start = new Date(2013, 0, 1),
end = new Date(2013, 11, 31)
var data = []
for (i = 0; i < 100; i++) {
var point = {}
var year = 2013;
var month = Math.floor(Math.random() * 12)
var day = Math.floor(Math.random() * 28)
point.date = new Date(year, month, day)
point.mIndex = i
data.push(point)
}
var force = d3.layout.force()
.size([width - padding, height - 100])
.on("tick", tick)
.start()
var svg = d3.select("body").append("svg")
.attr({
"width": width,
"height": height
})
//build stuff
var x = d3.time.scale()
.domain([start, end])
.range([padding, width - 6 * padding])
.clamp(true)
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(0)
.tickPadding(20)
//.tickFormat(d3.time.format("%x"))
var brush = d3.svg.brush()
.x(x)
.extent([start, end])
.on("brush", brushed1)
//append stuff
var slidercontainer = svg.append("g")
.attr("transform", "translate(100, 500)")
var axis = slidercontainer.append("g")
.call(xAxis)
var slider = slidercontainer.append("g")
.call(brush)
.classed("slider", true)
//manipulate stuff
d3.selectAll(".resize").append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 10)
.attr("fill", "Red")
.classed("handle", true)
d3.select(".domain")
.select(function () { return this.parentNode.appendChild(this.cloneNode(true)) })
.classed("halo", true)
function brushed1(e) {
var nodes = includedNodes(data, brush);
nodes.enter().append("circle")
.attr("r", 10)
.attr("fill", "red")
.call(force.drag)
.attr("class", "node")
.attr("cx", function (d) { return d.x })
.attr("cy", function (d) { return d.y })
nodes
.exit()
.remove()
force
.nodes(includedData(data, brush))
.start()
}
function includedData(data, brush) {
return data.filter(function (d, i, a) {
return d.date >= brush.extent()[0] && d.date <= brush.extent()[1]
})
}
function includedNodes(data, brush) {
return svg.selectAll(".node")
.data(includedData(data, brush), function (d, i) {
return d.mIndex
})
}
function tick() {
includedNodes(data, brush)
.attr("cx", function (d) { return d.x })
.attr("cy", function (d) { return d.y })
}
brushed1()
.domain {
fill: none;
stroke: #000;
stroke-opacity: .3;
stroke-width: 10px;
stroke-linecap: round;
}
.halo {
fill: none;
stroke: #ddd;
stroke-width: 8px;
stroke-linecap: round;
}
.tick {
font-size: 10px;
}
.selecting circle {
fill-opacity: .2;
}
.selecting circle.selected {
stroke: #f00;
}
.handle {
fill: #fff;
stroke: #000;
stroke-opacity: .5;
stroke-width: 1.25px;
cursor: crosshair;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<p id="nodeCount"></p>