我编写了一个小应用程序,如果用户单击屏幕,将出现一个节点(圆圈)。如果用户然后从一个节点拖动到另一个节点,则它们之间的边缘也会出现。这两个都在内部更新“ nodeData”和“ edgeData”数据结构。
节点似乎工作正常。如果用户单击屏幕,则新节点将添加到数据结构中,并且将调用函数“ restart()”以更新可视化效果。但是,边缘无法正常工作。而不是更新当前边缘并将任何新边缘添加到可视化中,“ restart()”函数再次将边缘添加到可视化中,以便在多次调用“ restart()”之后,图形的边缘过多
以下是小提琴的链接: https://jsfiddle.net/AlexMarshaall/srL3huk9/4/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
svg {
background-color: #FFF;
cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
svg:not(.active):not(.ctrl) {
cursor: crosshair;
}
path.link {
fill: none;
stroke: #000;
stroke-width: 4px;
cursor: default;
}
svg:not(.active):not(.ctrl) path.link {
cursor: pointer;
}
path.link.selected {
stroke-dasharray: 10, 2;
}
path.link.dragline {
pointer-events: none;
}
path.link.hidden {
stroke-width: 0;
}
circle.node {
stroke-width: 1.5px;
cursor: pointer;
}
text {
font: 12px sans-serif;
pointer-events: none;
}
text.id {
text-anchor: middle;
font-weight: bold;
}
</style>
</head>
<body>
<script src="/static/d3/d3.min.js"></script>
<script>
const svg = d3.select('body')
.append('svg')
.attr('width', 900)
.attr('height', 500);
let hoverOverNode = null; // the node the mouse is currently hovering over
let mousedownNode = null; // the node that the mouse went down over
function resetMouseVars() {
mousedownNode = null;
}
var lastNodeId = 1;
var node1 = {
id: "n" + lastNodeId++,
xVal: 50,
yVal: 50
};
var node2 = {
id: "n" + lastNodeId++,
xVal: 100,
yVal: 100
}
const nodesData = [node1,node2];
const edgeData = [{
source: node1,
target: node2
}];
let paths = svg.append('svg:g').selectAll('path');
let nodes = svg.append('svg:g').selectAll('g');
const dragLine = svg.append('svg:path')
.attr('class', 'link dragline hidden')
.attr('d', 'M0,0L0,0');
function restart() {
nodes = nodes.data(nodesData, (d) => d.id); // Nodes is just the nodes to update
nodes.selectAll('g')
.style('fill', "DarkGreen");
nodes.exit().remove();
var newNodesToBeAdded = nodes.enter().append('svg:g');
newNodesToBeAdded.attr('transform', (d) => `translate(${d.xVal},${d.yVal})`)
.attr('id', (d) => d.id);
newNodesToBeAdded.append('svg:circle')
.attr('class', 'node')
.attr('r', 12)
.attr('stroke-width', 3)
.attr('stroke', 'black')
.style('fill', "DarkGreen")
.on('mouseover', function(d) {
hoverOverNode = d;
d3.select(this).attr('transform', 'scale(1.1)');
})
.on('mouseout', function(d) {
hoverOverNode = null;
d3.select(this).attr('transform', '');
})
.on('mousedown', (d) => {
if (d3.event.ctrlKey) return;
mousedownNode = d;
dragLine
.classed('hidden', false)
.attr('d', `M${mousedownNode.xVal},${mousedownNode.yVal}L${mousedownNode.xVal},${mousedownNode.yVal}`);
restart();
})
.on('mouseup', (d) => {
dragLine.classed('hidden', true);
});
newNodesToBeAdded.append('svg:text')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id')
.text((d) => d.id.substring(1));
nodes = newNodesToBeAdded.merge(nodes);
paths = paths.data(edgeData);
paths.append('svg:path')
.attr("class", "link")
.attr("d", (d) => {
const sourceX = d.source.xVal;
const sourceY = d.source.yVal;
const targetX = d.target.xVal;
const targetY = d.target.yVal;
return `M${sourceX},${sourceY}L${targetX},${targetY}`;
});
paths.exit().remove();
var newPathsToBeAdded = paths.enter().append('svg:path');
newPathsToBeAdded.attr('class','link')
.attr("d", (d) => {
const sourceX = d.source.xVal;
const sourceY = d.source.yVal;
const targetX = d.target.xVal;
const targetY = d.target.yVal;
return `M${sourceX},${sourceY}L${targetX},${targetY}`;
});
}
function mousedown() {
if (d3.event.ctrlKey || hoverOverNode != null) {
return;
}
var coords = d3.mouse(this);
var newNode = {
id: "n" + lastNodeId++,
xVal: coords[0],
yVal: coords[1]
};
nodesData.push(newNode);
restart();
}
function mousemove() {
if (!mousedownNode) return; // if there's no mousedownNode then there's no need to do anything
// update dragline
dragLine.attr('d', `M${mousedownNode.xVal},${mousedownNode.yVal}L${d3.mouse(this)[0]},${d3.mouse(this)[1]}`);
restart();
}
function mouseup() {
// if there is a mousedown node, hide the dragline that's been drawn
if (mousedownNode){
dragLine
.classed('hidden', true);
if (hoverOverNode != null){
edgeData.push({source:mousedownNode, target:hoverOverNode});
resetMouseVars();
restart();
}
}
}
// App starts here
svg.on('mousedown', mousedown)
.on('mousemove', mousemove)
.on('mouseup', mouseup);
restart()
</script>
</body>
</html>
答案 0 :(得分:0)
您遇到了这些问题,因为您没有遵循正确的做法。
paths = paths.data(edgeData);
.data方法返回更新选择,并且确实不应保存在两次调用之间。您应该创建一个局部变量,而不是重用路径。您有一个奇怪的行为,因为您没有将更新选择与输入选择合并-结果您创建的路径没有被捕获到更新选择中,并且每次相同的路径数据对于d3来说都是新的。好吧,至少这是我的想法。添加paths = newPathsToBeAdded.merge(paths);
应该可以解决。
如果遵循规范循环,将来您不太可能遇到此类奇怪的问题。这就是我的方法:
var paths = svg.select('g.pathcnt').selectAll('path.edge'); // add classes for selection reference
paths.exit().remove();
paths.enter()
.append('path')
.classed('edge', true)
//do not duplicate things; put it after the merge to set for both new and existing nodes
.merge(paths)
.attr('d', function(d) { ... });
我不建议在调用之间保留选择。所有示例都根据需要重新选择,因此我不确定重新使用选择是否有效。