我得到了一个带有 3 个主节点的小型 D3 强制图。这些节点包含一个属性shoes,它保存一个整数。图表顶部有两个按钮,用于添加或删除鞋子。一旦单击其中一个按钮,我就想更新 D3 强制图形数据。基本上蓝色节点中的整数值应该增加或减少。
我搜索并找到了几篇 stackoverflow 文章,其中解释了实现我的需求的步骤。不幸的是,我还无法成功地将这些文章映射到我的原型(prototype)上。
问题是:它向最后一个数据节点添加了一个元素,但在视觉上并没有改变蓝色圆圈中的数量。相反,控制台输出显示正确的值并正确增加或减少鞋子的数量。
我错过了什么?
var width = window.innerWidth,
height = window.innerHeight;
var buttons = d3.select("body").selectAll("button")
.data(["add Shoes", "remove Shoes"])
.enter()
.append("button")
.text(function(d) {
return d;
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
////////////////////////
// outer force layout
var data = {
"nodes":[
{ "id": "A", "shoes": 1},
{ "id": "B", "shoes": 1},
{ "id": "C", "shoes": 0},
],
"links": [
{ "source": "A", "target": "B"},
{ "source": "B", "target": "C"},
{ "source": "C", "target": "A"}
]
};
var simulation = d3.forceSimulation()
.force("size", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
linksContainer = svg.append("g").attr("class", "linkscontainer")
nodesContainer = svg.append("g").attr("class", "nodesContainer")
var links = linksContainer.selectAll("g")
.data(data.links)
.join("g")
.attr("fill", "transparent")
var linkLine = linksContainer.selectAll(".linkPath")
.data(data.links)
.join("path")
.attr("stroke", "red")
.attr("fill", "transparent")
.attr("stroke-width", 3)
nodes = nodesContainer.selectAll(".nodes")
.data(data.nodes, function (d) { return d.id; })
.join("g")
.attr("class", "nodes")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
nodes.selectAll("circle")
.data(d => [d])
.join("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
var smallCircle = nodes.selectAll("g")
//.data(d => d.shoes)
.data(d => [d])
.enter()
.filter(function(d) {
return d.shoes !== 0;
})
.append("g")
.attr("cursor", "pointer")
.attr("transform", function(d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
smallCircle.append('circle')
.attr("class", "circle-small")
.attr('r', 15)
.attr("fill", "blue")
smallCircle.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.attr("pointer-events", "cursor")
.text(function(d) {
return d.shoes;
})
simulation
.nodes(data.nodes)
.on("tick", tick)
simulation
.force("link")
.links(data.links)
function tick() {
linkLine.attr("d", function(d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy)
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
})
nodes
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
buttons.on("click", function(d) {
if (d.srcElement.__data__ == "add Shoes") {
data.nodes.forEach(function(item) {
item.shoes = item.shoes + 1
})
} else if (d.srcElement.__data__ == "remove Shoes") {
data.nodes.forEach(function(item) {
if (!item.shoes == 0) {
item.shoes = item.shoes - 1
}
})
}
restart()
})
function restart() {
// Apply the general update pattern to the nodes.
smallCircle = nodes.selectAll("g")
.data(d => [d])
.enter()
.filter(function(d) {
return d.shoes !== 0;
})
.append("g")
.attr("cursor", "pointer")
.attr("transform", function(d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
smallCircle.append("circle")
.attr("class", "circle-small")
.attr('r', 15)
.attr("fill", "blue")
smallCircle.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.text(function(d) {
return d.shoes;
})
smallCircle.exit().remove();
// Update and restart the simulation.
simulation.nodes(data.nodes);
simulation.restart()
}
body {
background: whitesmoke,´;
overflow: hidden;
margin: 0px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v7</title>
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
</body>
</html>
进一步的 d3 选择行为。
我接受了答案,这是完全正确的。此外,我尝试在同一级别添加另一个 g 元素,似乎 d3 选择器无法按组处理选择。至少 selectAll("gblue") 的行为与 selectAll("g") 不同
var width = window.innerWidth,
height = window.innerHeight;
var buttons = d3.select("body").selectAll("button")
.data(["add blue", "remove blue", "add red", "remove red"])
.enter()
.append("button")
.text(function (d) {
return d;
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
////////////////////////
// outer force layout
var data = {
"nodes": [{
"id": "A",
"blue": 1,
"red": 0,
},
{
"id": "B",
"blue": 1,
"red": 1
},
{
"id": "C",
"blue": 0,
"red": 1
},
],
"links": [{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
}
]
};
var simulation = d3.forceSimulation()
.force("size", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink().id(function (d) {
return d.id
}).distance(250))
linksContainer = svg.append("g").attr("class", "linkscontainer")
nodesContainer = svg.append("g").attr("class", "nodesContainer")
var links = linksContainer.selectAll("g")
.data(data.links)
.join("g")
.attr("fill", "transparent")
var linkLine = linksContainer.selectAll(".linkPath")
.data(data.links)
.join("path")
.attr("stroke", "red")
.attr("fill", "transparent")
.attr("stroke-width", 3)
nodes = nodesContainer.selectAll(".nodes")
.data(data.nodes, function (d) {
return d.id;
})
.join("g")
.attr("class", "nodes")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
nodes.selectAll("circle")
.data(d => [d])
.join("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
var blueNode = nodes.selectAll("gblue")
.data(d => d.blue ? [d] : [])
.enter()
.append("g")
.attr("class", "gblue")
.attr("cursor", "pointer")
.attr("transform", function (d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
blueNode.append('circle')
.attr('r', 15)
.attr("fill", "blue")
blueNode.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.attr("pointer-events", "cursor")
.text(function (d) {
return d.blue;
})
var redNode = nodes.selectAll("gred")
.data(d => d.red ? [d] : [])
.enter()
.append("g")
.attr("class", "gred")
.attr("cursor", "pointer")
.attr("transform", function (d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.3)},${40 * Math.sin(factor - Math.PI * 0.3)})`;
});
redNode.append('circle')
.attr('r', 15)
.attr("fill", "red")
redNode.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.attr("pointer-events", "cursor")
.text(function (d) {
return d.red;
})
simulation
.nodes(data.nodes)
.on("tick", tick)
simulation
.force("link")
.links(data.links)
function tick() {
linkLine.attr("d", function (d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy)
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
})
nodes
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
buttons.on("click", function (_, d) {
if (d === "add blue") {
data.nodes.forEach(function (item) {
item.blue = item.blue + 1
})
} else if (d === "remove blue") {
data.nodes.forEach(function (item) {
if (!item.blue == 0) {
item.blue = item.blue - 1
}
})
} else if (d === "add red") {
data.nodes.forEach(function (item) {
item.red = item.red + 1
})
} else if (d === "remove red") {
data.nodes.forEach(function (item) {
if (!item.red == 0) {
item.red = item.red - 1
}
})
}
restart()
})
function restart() {
// Apply the general update pattern to the nodes.
let blueNode = nodes.selectAll("g")
.data(d => d.blue ? [d] : []);
blueNode.exit().remove();
const blueNodeEnter = blueNode.enter()
.append("g")
.attr("class", "blue")
.attr("cursor", "pointer")
.attr("transform", function (d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
blueNodeEnter.append("circle")
.attr('r', 15)
.attr("fill", "blue")
blueNodeEnter.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.text(function (d) {
return d.blue;
});
blueNode = blueNodeEnter.merge(blueNode);
blueNode.select("text")
.text(function (d) {
return d.blue;
});
let redNode = nodes.selectAll("g")
.data(d => d.red ? [d] : []);
redNode.exit().remove();
const redNodeEnter = redNode.enter()
.append("g")
.attr("class", "red")
.attr("cursor", "pointer")
.attr("transform", function (d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
redNodeEnter.append("circle")
.attr('r', 15)
.attr("fill", "blue")
redNodeEnter.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.text(function (d) {
return d.red;
});
redNode = redNodeEnter.merge(redNode);
redNode.select("text")
.text(function (d) {
return d.red;
});
// Update and restart the simulation.
simulation.nodes(data.nodes);
simulation.restart()
}
.link {
stroke: #000;
stroke-width: 1.5px;
}
.nodes {
fill: whitesmoke;
}
<!DOCTYPE html>
<html lang="de">
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
<meta charset="utf-8">
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.3.js"></script>
<!-- D3 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- fontawesome stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/98a5e27706.js" crossorigin="anonymous"></script>
</head>
<body>
</body>
</html>
最佳答案
首先,不要弄乱私有(private)变量,通常分配为__foo__
。所以,而不是...
buttons.on("click", function(d) {
if (d.srcElement.__data__ == "add Shoes") { etc...
...只需执行:
buttons.on("click", function(_, d) {
if (d == "add Shoes") {
回到问题:这里的问题是不正确的进入-更新-退出模式。应该是这样:
//the update selection:
let smallCircle = nodes.selectAll("g")
.data(d => d.shoes ? [d] : []);
//the exit selection:
smallCircle.exit().remove();
//the enter selection:
const smallCircleEnter = smallCircle.enter()
.append("g")
//etc...
//appending elements in the enter selection only:
smallCircleEnter.append("circle")
//etc...
smallCircleEnter.append("text")
//etc...
//merging the enter and the update selections:
smallCircle = smallCircleEnter.merge(smallCircle);
//modifying the update selection
smallCircle.select("text")
.text(function(d) {
return d.shoes;
});
另外,如果鞋子的数量为零,我将删除该过滤器并传递一个空数组。
这是经过这些更改的代码:
var width = window.innerWidth,
height = window.innerHeight;
var buttons = d3.select("body").selectAll("button")
.data(["add Shoes", "remove Shoes"])
.enter()
.append("button")
.text(function(d) {
return d;
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
////////////////////////
// outer force layout
var data = {
"nodes": [{
"id": "A",
"shoes": 1
},
{
"id": "B",
"shoes": 1
},
{
"id": "C",
"shoes": 0
},
],
"links": [{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
}
]
};
var simulation = d3.forceSimulation()
.force("size", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink().id(function(d) {
return d.id
}).distance(250))
linksContainer = svg.append("g").attr("class", "linkscontainer")
nodesContainer = svg.append("g").attr("class", "nodesContainer")
var links = linksContainer.selectAll("g")
.data(data.links)
.join("g")
.attr("fill", "transparent")
var linkLine = linksContainer.selectAll(".linkPath")
.data(data.links)
.join("path")
.attr("stroke", "red")
.attr("fill", "transparent")
.attr("stroke-width", 3)
nodes = nodesContainer.selectAll(".nodes")
.data(data.nodes, function(d) {
return d.id;
})
.join("g")
.attr("class", "nodes")
.attr("id", function(d) {
return d.id;
})
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
nodes.selectAll("circle")
.data(d => [d])
.join("circle")
.style("fill", "lightgrey")
.style("stroke", "blue")
.attr("r", 40)
var smallCircle = nodes.selectAll("g")
//.data(d => d.shoes)
.data(d => [d])
.enter()
.filter(function(d) {
return d.shoes !== 0;
})
.append("g")
.attr("cursor", "pointer")
.attr("transform", function(d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
smallCircle.append('circle')
.attr("class", "circle-small")
.attr('r', 15)
.attr("fill", "blue")
smallCircle.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.attr("pointer-events", "cursor")
.text(function(d) {
return d.shoes;
})
simulation
.nodes(data.nodes)
.on("tick", tick)
simulation
.force("link")
.links(data.links)
function tick() {
linkLine.attr("d", function(d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy)
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
})
nodes
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
buttons.on("click", function(_, d) {
if (d === "add Shoes") {
data.nodes.forEach(function(item) {
item.shoes = item.shoes + 1
})
} else if (d === "remove Shoes") {
data.nodes.forEach(function(item) {
if (!item.shoes == 0) {
item.shoes = item.shoes - 1
}
})
}
restart()
})
function restart() {
// Apply the general update pattern to the nodes.
let smallCircle = nodes.selectAll("g")
.data(d => d.shoes ? [d] : []);
smallCircle.exit().remove();
const smallCircleEnter = smallCircle.enter()
.append("g")
.attr("cursor", "pointer")
.attr("transform", function(d, i) {
const factor = (i / 40) * (15 / 2) * 5;
return `translate(${40 * Math.cos(factor - Math.PI * 0.5)},${40 * Math.sin(factor - Math.PI * 0.5)})`;
});
smallCircleEnter.append("circle")
.attr("class", "circle-small")
.attr('r', 15)
.attr("fill", "blue")
smallCircleEnter.append("text")
.attr("font-size", 15)
.attr("fill", "white")
.attr("dominant-baseline", "central")
.style("text-anchor", "middle")
.text(function(d) {
return d.shoes;
});
smallCircle = smallCircleEnter.merge(smallCircle);
smallCircle.select("text")
.text(function(d) {
return d.shoes;
});
// Update and restart the simulation.
simulation.nodes(data.nodes);
simulation.restart()
}
body {
background: whitesmoke, ´;
overflow: hidden;
margin: 0px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v7</title>
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
</body>
</html>
关于javascript - 更改数据后强制图表不更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75028754/