javascript - D3 selection.join() 实例
问题描述
我试图了解join()
D3v6 中的功能。显然我误解了官方的 D3 join() 文档。https://github.com/d3/d3-selection/blob/v2.0.0/README.md#selection_join,因为我认为我可以使用 join() 而不是典型的 append()、enter()、remove() , exit() 模式并节省大量代码。
下面的代码在开始时创建了 3 个节点,并带有一个上下文菜单。我可以添加和删除数据initialize()
再次调用该函数来重绘节点。问题是,“旧节点”仍然存在。不是只添加 1 个节点,而是重新绘制整个节点组。
我错过了什么?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3 JOIN Test</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<script>
var nodes = [
{
"id": 1,
},
{
"id": 2,
},
{
"id": 3,
}
]
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
d = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
initialze()
function initialze() {
node = nodesContainer.selectAll(".node")
.data(nodes, d => d.id)
.join("circle")
.attr("r", 30)
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
.on("contextmenu", contextMenu)
simulation
.nodes(nodes)
.on("tick", ticked);
}
function mouseEnter(event, d) {
thisNode = d
d3.select(this).style("fill", "lightblue")
}
function mouseLeave(d) {
d3.select(this).style("fill", "whitesmoke")
}
function contextMenu(event, d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function () {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNodeClicked)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function addNodeClicked() {
addNode(thisNode)
}
function addNode(d) {
var newID = Math.floor(Math.random() * 100000)
nodes.push({ "id": newID, })
initialze()
}
function removeNodeClicked() {
removeNode(thisNode)
}
function removeNode(d) {
var indexOfNodes = nodes.indexOf(d)
nodes.splice(indexOfNodes, 1)
initialze()
}
function ticked() {
node
.attr("transform", function (d) {
return "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 = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
解决方案
您正在选择一个您从未设置过的班级。因此,只需执行以下操作:
.attr("class", "node")
这是您进行更改的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3 JOIN Test</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<script>
var nodes = [
{
"id": 1,
},
{
"id": 2,
},
{
"id": 3,
}
]
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
d = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
initialze()
function initialze() {
node = nodesContainer.selectAll(".node")
.data(nodes, d => d.id)
.join("circle")
.attr("r", 30)
.attr("class", "node")
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
.on("contextmenu", contextMenu)
simulation
.nodes(nodes)
.on("tick", ticked);
}
function mouseEnter(event, d) {
thisNode = d
d3.select(this).style("fill", "lightblue")
}
function mouseLeave(d) {
d3.select(this).style("fill", "whitesmoke")
}
function contextMenu(event, d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function () {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNodeClicked)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function addNodeClicked() {
addNode(thisNode)
}
function addNode(d) {
var newID = Math.floor(Math.random() * 100000)
nodes.push({ "id": newID, })
initialze()
}
function removeNodeClicked() {
removeNode(thisNode)
}
function removeNode(d) {
var indexOfNodes = nodes.indexOf(d)
nodes.splice(indexOfNodes, 1)
initialze()
}
function ticked() {
node
.attr("transform", function (d) {
return "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 = undefined;
d.fy = undefined;
}
</script>
</body>
</html>