D3:如何在力组布局节点上绘制多个凸包?



我正在尝试在力布局中的所有组上绘制凸包。但我只设法画了一半的凸包。当 D3 尝试绘制其余的船体时,控制台返回错误:元素尚未创建。然而,当我在控制台中检查"组"变量时,所有组数据都在那里,x、y 数据都设置得很好。见下图:

我什至尝试在刻度函数中延迟船体的绘制,但它仍然不起作用,我得到了相同的结果(如下图所示(。

JSFiddle:只得到我想要的凸包的一半

这是代码:

<script>
var radius = 5.5;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var scale = d3.scaleLinear()
.domain([0.5, 1])
.range([1.8, 3.8]);
var svg2 = d3.select("#svg2");
var w = +svg2.attr("width"),
h = +svg2.attr("height");
var hull = svg2.append("path")
.attr("class", "hull");
var groupPath = function(d) { return "M" + d3.polygonHull(d.values.map(function(i) { return [i.x, i.y]; }))
.join("L") + "Z"; };
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;
});
fnode
.attr("cx", function (d) {
return d.x = Math.max(radius, Math.min(w - radius, d.x));
})
.attr("cy", function (d) {
return d.y = Math.max(radius, Math.min(h - radius, d.y));
})
.attr("r", radius);
delayHull(6000);
}
function delayHull(delay) {
setTimeout(function() {
svg2.selectAll("path")
.data(groups)
.attr("d", groupPath)
.enter()
.append("path")
.attr("class", "hull")
.attr("d", groupPath);
}, delay);
}
var simulation, link, fnode, groups;
var fnodeg = svg2.append("g")
.attr("class", "fnode");
var linkg = svg2.append("g")
.attr("class", "links")
.attr("id", "linkg");
d3.json("..//vizData//forceLayout//forceLayout_15000.json", function(error, graph) {
if (error) throw error;
simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(30).strength(1))
.force("charge", d3.forceManyBody().strength(-2).distanceMin(15).distanceMax(180))
.force("center", d3.forceCenter(w / 2, h / 2))
.force("collide", d3.forceCollide().strength(1).iterations(2));
link = linkg.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function (d) {
return scale(d.value);
});
fnode = fnodeg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", radius)
.attr("fill", function (d) {
return color(d.truth);
});
simulation
.nodes(graph.nodes);
simulation.force("link")
.links(graph.links);
groups = d3.nest().key(function(d) { return d.group; }).entries(graph.nodes);
simulation.on("tick", ticked);
fnode.append("title")
.text(function (d) { return d.id; });
link.append("title")
.text(function (d) { return d.value; });
})
</script>

我引用了这个 http://bl.ocks.org/donaldh/2920551 凸包的例子;他在tick函数之外设置了他的"groups"变量,这没问题。

我做错了什么???

基于 Andrew 的回答,当您的集群只有两个点时,您可以简单地推送另一个内部数组:

if (d.values.length === 2) {
var arr = d.values.map(function(i) {
return [i.x, i.y];
})
arr.push([arr[0][0], arr[0][1]]);
return "M" + d3.polygonHull(arr).join("L") + "Z";

以下是仅包含该更改的代码:

var radius = 5.5;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var scale = d3.scaleLinear()
.domain([0.5, 1])
.range([1.8, 3.8]);
var svg2 = d3.select("#svg2");
var w = +svg2.attr("width"),
h = +svg2.attr("height");
var hull = svg2.append("path")
.attr("class", "hull");
var groupPath = function(d) {
if (d.values.length === 2) {
var arr = d.values.map(function(i) {
return [i.x, i.y];
})
arr.push([arr[0][0], arr[0][1]]);
return "M" + d3.polygonHull(arr).join("L") + "Z";
} else {
return "M" + d3.polygonHull(d.values.map(function(i) {
return [i.x, i.y];
}))
.join("L") + "Z";
}
};
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;
});
fnode
.attr("cx", function(d) {
return d.x = Math.max(radius, Math.min(w - radius, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(radius, Math.min(h - radius, d.y));
})
.attr("r", radius);
delayHull(1000);
}
function delayHull(delay) {
setTimeout(function() {
svg2.selectAll("path")
.data(groups)
.attr("d", groupPath)
.enter()
.append("path")
.attr("class", "hull")
.attr("d", groupPath);
}, delay);
}
var simulation, link, fnode, groups;
var fnodeg = svg2.append("g")
.attr("class", "fnode");
var linkg = svg2.append("g")
.attr("class", "links")
.attr("id", "linkg");
d3.json('https://api.myjson.com/bins/bkzxh', function(error, graph) {
if (error) throw error;
simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(30).strength(1))
.force("charge", d3.forceManyBody().strength(-2).distanceMin(15).distanceMax(180))
.force("center", d3.forceCenter(w / 2, h / 2))
.force("collide", d3.forceCollide().strength(1).iterations(2));
link = linkg.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) {
return scale(d.value);
});
fnode = fnodeg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", radius)
.attr("fill", function(d) {
return color(d.truth);
});
simulation
.nodes(graph.nodes);
simulation.force("link")
.links(graph.links);
groups = d3.nest().key(function(d) {
return d.group;
}).entries(graph.nodes);
simulation.on("tick", ticked);
fnode.append("title")
.text(function(d) {
return d.id;
});
link.append("title")
.text(function(d) {
return d.value;
});
});
.links line {
stroke: #999;
stroke-opacity: 0.8;
}
.fnode circle {
stroke: #fff;
stroke-width: 1.5px;
fill-opacity: 1;
}
.hull {
fill: steelblue;
stroke: steelblue;
fill-opacity: 0.3;
stroke-opacity: 0.3;
stroke-width: 10px;
stroke-linejoin: round;
}
<script src="https://d3js.org/d3.v4.js"></script>
<svg id="svg2" width="600" height="600" style="margin-left: -5px"></svg>

我相信您的问题是某些组只有 2 个节点。在 v4 中,这会生成一个类型错误,因为如果使用两个点,d3.polygonHull(( 将返回 null - 凸包需要三个点(大概不在一行中。更正 - 他们可以排成一行,请参阅杰拉尔多对此答案的评论以及他的答案(。以下片段几乎没有从 Mike 的规范示例中修改:

此代码段演示了该问题:

var width = 960,
height = 500;
var randomX = d3.randomNormal(width / 2, 60),
randomY = d3.randomNormal(height / 2, 60),
vertices = d3.range(2).map(function() { return [randomX(), randomY()]; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousemove", function() { vertices[0] = d3.mouse(this); redraw(); })
.on("click", function() { vertices.push(d3.mouse(this)); redraw(); });
svg.append("rect")
.attr("width", width)
.attr("height", height);
var hull = svg.append("path")
.attr("class", "hull");
var circle = svg.selectAll("circle");
redraw();
function redraw() {
hull.datum(d3.polygonHull(vertices)).attr("d", function(d) { return "M" + d.join("L") + "Z"; });

circle = circle.data(vertices);
circle.enter().append("circle").attr("r", 3);
circle.attr("transform", function(d) { return "translate(" + d + ")"; });
}
rect {
fill: none;
pointer-events: all;
}
.hull {
fill: steelblue;
stroke: steelblue;
stroke-width: 32px;
stroke-linejoin: round;
}
circle {
fill: white;
stroke: black;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

这似乎没有在 v3 中产生错误:

var width = 960,
height = 500;
var randomX = d3.random.normal(width / 2, 60),
randomY = d3.random.normal(height / 2, 60),
vertices = d3.range(2).map(function() { return [randomX(), randomY()]; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousemove", function() { vertices[0] = d3.mouse(this); redraw(); })
.on("click", function() { vertices.push(d3.mouse(this)); redraw(); });
svg.append("rect")
.attr("width", width)
.attr("height", height);
var hull = svg.append("path")
.attr("class", "hull");
var circle = svg.selectAll("circle");
redraw();
function redraw() {
hull.datum(d3.geom.hull(vertices)).attr("d", function(d) { return "M" + d.join("L") + "Z"; });

console.log(d3.geom.hull(vertices));

circle = circle.data(vertices);
circle.enter().append("circle").attr("r", 3);
circle.attr("transform", function(d) { return "translate(" + d + ")"; });
}
rect {
fill: none;
pointer-events: all;
}
.hull {
fill: steelblue;
stroke: steelblue;
stroke-width: 32px;
stroke-linejoin: round;
}
circle {
fill: white;
stroke: black;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

最新更新