d3 树状图:替换根保留旧树
d3 Dendrogram: Replacing root retains old tree
我有一个使用 d3.hierarchy 的树状图/聚类图的根。我正在尝试用一个选定的节点更新根,该节点应该成为新的头部,并在顶部绘制一棵用该节点绘制的新树。这应该取代旧树。步骤如下:
- 读入平面数据
- 使用 d3.stratify
转换为层次结构
- 将其转换为集群(带坐标等)
- 使用新的 select.join 绘制(不再需要显式退出/删除)
- 用户点击节点圈子
- 更新层次结构,将所选节点作为新根并删除父节点
- 重新绘制,节点不再出现在数据(父级及以上)中,由 join
移除
然而,它重新绘制了新的、更小的根和从属,但所有旧的 SVG 仍然存在。我已经尝试明确添加退出/删除,但这没有帮助。
我做错了什么?
可以在此处查看一个简化的、可重现的示例。我还在 https://jsfiddle.net/colourblue/zp7ujra3/9/
创建了一个 fiddle
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<div id="vis"></div>
<script>
let treeData = []
let currentTreeData = []
var flatData = [
{ "ID" : 1000, "name" : "The Root", "parentID":null},
{ "ID" : 1100, "name" : "Child 1", "parentID":1000 },
{ "ID" : 1110, "name" : "G.Child 1.1", "parentID":1100 },
{ "ID" : 1120, "name" : "G.Child 1.2", "parentID":1100 },
{ "ID" : 1130, "name" : "G.Child 1.3", "parentID":1100 },
{ "ID" : 1200, "name" : "Child 2", "parentID":1000 },
{ "ID" : 1210, "name" : "G.Child 2.1", "parentID":1200 },
{ "ID" : 1211, "name" : "G.G.Child 2.1.1", "parentID":1210 },
{ "ID" : 1212, "name" : "G.G.Child 2.2.2", "parentID":1210 },
{ "ID" : 12111, "name" : "G.G.G.Child 2.1.1.1", "parentID":1211 },
{ "ID" : 1300, "name" : "Child 3", "parentID":1000 }
];
function chart(thisTreeData) {
let root = clusterise(thisTreeData)
// Add nodes (links)
svg.append("g")
.attr("class", "node")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.3)
.selectAll("path")
.data(root.links(), function(d) { return "Link" + ":" + d.target.data.id })
.join("path")
.attr("d", d3.linkRadial()
.angle(d => d.x)
.radius(d => d.y));
// Add circles
svg.append("g")
.attr("class", "node")
.selectAll("circle")
.data(root.descendants(), function(d) { return "Circle" + d.data.id; })
.join("circle")
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
`)
.attr("r", 3)
.on('click', click);
// Add text
svg.append("g")
.attr("class", "node")
.selectAll("text")
.data(root.descendants(), function(d) { return "Text" + d.data.id; })
.join("text")
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`)
.attr("text-anchor", d => d.x < Math.PI === !d.children ? "start" : "end")
.text(d => d.data.data.name);
}
// Switch tree on click so centre is now selected node
function click(event,d) {
currentTreeData = findNode(treeData, d.data.id)
chart(currentTreeData);
}
// HELPER FUNCTIONS
// ----------------
// Function to Strafify flat CSV data into a tree
function convertToHierarchy(data) {
var stratify = d3.stratify()
.parentId(function (d) {
return d.parentID;
})
.id(function (d) {
return d.ID;
});
let treeData = stratify(data);
return (treeData)
}
// Function to Create d3 cluster with coordinates etc from stratified data
function clusterise(treeData) {
tree = d3.cluster().size([2 * Math.PI, radius - 100])
let root = tree(d3.hierarchy(treeData)
.sort((a, b) => d3.ascending(a.name, b.name)));
return (root)
}
function findNode(root, id) {
console.log(root);
let selected = root.find(obj => obj.id === id);
selected.parent= null;
console.log(selected);
return(selected)
}
width = 800
height = 600
radius = width / 2
let svg = d3.select("#vis")
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
treeData = convertToHierarchy(flatData)
currentTreeData = treeData
chart(currentTreeData);
</script>
</body>
</html>
这是一个更复杂的示例,它使用新的 .join
方法正确处理 enter
、update
和 exit
模式。这确实允许您添加过渡。请注意,我删除了您的内部包装 g
节点。由于每次点击都会附加一个新的点击,这会弄乱您对可见节点(路径、圆圈和文本)的选择。
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<div id="vis"></div>
<script>
let treeData = [];
let currentTreeData = [];
var flatData = [
{ ID: 1000, name: 'The Root', parentID: null },
{ ID: 1100, name: 'Child 1', parentID: 1000 },
{ ID: 1110, name: 'G.Child 1.1', parentID: 1100 },
{ ID: 1120, name: 'G.Child 1.2', parentID: 1100 },
{ ID: 1130, name: 'G.Child 1.3', parentID: 1100 },
{ ID: 1200, name: 'Child 2', parentID: 1000 },
{ ID: 1210, name: 'G.Child 2.1', parentID: 1200 },
{ ID: 1211, name: 'G.G.Child 2.1.1', parentID: 1210 },
{ ID: 1212, name: 'G.G.Child 2.2.2', parentID: 1210 },
{ ID: 12111, name: 'G.G.G.Child 2.1.1.1', parentID: 1211 },
{ ID: 1300, name: 'Child 3', parentID: 1000 },
];
function chart(thisTreeData) {
let root = clusterise(thisTreeData);
// Add nodes (links)
svg
.selectAll('.line')
.data(root.links(), function (d) {
return 'Link' + ':' + d.target.data.id;
})
.join(
function (enter) {
return enter
.append('path')
.attr('class', 'line')
.attr(
'd',
d3
.linkRadial()
.angle((d) => d.x)
.radius((d) => d.y)
)
.attr('fill', 'none')
.attr('stroke', '#555')
.attr('stroke-opacity', 0.3);
},
function (update) {
update
.transition()
.duration(1000)
.attr(
'd',
d3
.linkRadial()
.angle((d) => d.x)
.radius((d) => d.y)
);
return update;
},
function (exit) {
return exit.remove();
}
);
// Add text
svg
.selectAll('.word')
.data(root.descendants(), function (d) {
return 'Text' + d.data.id;
})
.join(
function (enter) {
return enter
.append('text')
.attr('class', 'word')
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`
)
.attr('text-anchor', (d) =>
d.x < Math.PI === !d.children ? 'start' : 'end'
)
.text((d) => d.data.data.name);
},
function (update) {
update
.transition()
.duration(1000)
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`
);
return update;
},
function (exit) {
return exit.remove();
}
);
// Add circles
svg
.selectAll('.round')
.data(root.descendants(), function (d) {
return 'circle' + d.data.id;
})
.join(
function (enter) {
return enter
.append('circle')
.attr('class', 'round')
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
`
)
.attr('r', 5)
.on('click', click);
},
function (update) {
update
.transition()
.duration(1000)
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
`
);
return update;
},
function (exit) {
return exit.remove();
}
);
}
// Switch tree on click so centre is now selected node
function click(event, d) {
currentTreeData = findNode(treeData, d.data.id);
chart(currentTreeData);
}
// HELPER FUNCTIONS
// ----------------
// Function to Strafify flat CSV data into a tree
function convertToHierarchy(data) {
var stratify = d3
.stratify()
.parentId(function (d) {
return d.parentID;
})
.id(function (d) {
return d.ID;
});
let treeData = stratify(data);
return treeData;
}
// Function to Create d3 cluster with coordinates etc from stratified data
function clusterise(treeData) {
tree = d3.cluster().size([2 * Math.PI, radius - 100]);
let root = tree(
d3.hierarchy(treeData).sort((a, b) => d3.ascending(a.name, b.name))
);
return root;
}
function findNode(root, id) {
//console.log(root);
let selected = root.find((obj) => obj.id === id);
selected.parent = null;
//console.log(selected);
return selected;
}
width = 800;
height = 600;
radius = width / 2;
let svg = d3
.select('#vis')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
treeData = convertToHierarchy(flatData);
currentTreeData = treeData;
chart(currentTreeData);
</script>
</body>
</html>
我有一个使用 d3.hierarchy 的树状图/聚类图的根。我正在尝试用一个选定的节点更新根,该节点应该成为新的头部,并在顶部绘制一棵用该节点绘制的新树。这应该取代旧树。步骤如下:
- 读入平面数据
- 使用 d3.stratify 转换为层次结构
- 将其转换为集群(带坐标等)
- 使用新的 select.join 绘制(不再需要显式退出/删除)
- 用户点击节点圈子
- 更新层次结构,将所选节点作为新根并删除父节点
- 重新绘制,节点不再出现在数据(父级及以上)中,由 join 移除
然而,它重新绘制了新的、更小的根和从属,但所有旧的 SVG 仍然存在。我已经尝试明确添加退出/删除,但这没有帮助。
我做错了什么?
可以在此处查看一个简化的、可重现的示例。我还在 https://jsfiddle.net/colourblue/zp7ujra3/9/
创建了一个 fiddle<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<div id="vis"></div>
<script>
let treeData = []
let currentTreeData = []
var flatData = [
{ "ID" : 1000, "name" : "The Root", "parentID":null},
{ "ID" : 1100, "name" : "Child 1", "parentID":1000 },
{ "ID" : 1110, "name" : "G.Child 1.1", "parentID":1100 },
{ "ID" : 1120, "name" : "G.Child 1.2", "parentID":1100 },
{ "ID" : 1130, "name" : "G.Child 1.3", "parentID":1100 },
{ "ID" : 1200, "name" : "Child 2", "parentID":1000 },
{ "ID" : 1210, "name" : "G.Child 2.1", "parentID":1200 },
{ "ID" : 1211, "name" : "G.G.Child 2.1.1", "parentID":1210 },
{ "ID" : 1212, "name" : "G.G.Child 2.2.2", "parentID":1210 },
{ "ID" : 12111, "name" : "G.G.G.Child 2.1.1.1", "parentID":1211 },
{ "ID" : 1300, "name" : "Child 3", "parentID":1000 }
];
function chart(thisTreeData) {
let root = clusterise(thisTreeData)
// Add nodes (links)
svg.append("g")
.attr("class", "node")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.3)
.selectAll("path")
.data(root.links(), function(d) { return "Link" + ":" + d.target.data.id })
.join("path")
.attr("d", d3.linkRadial()
.angle(d => d.x)
.radius(d => d.y));
// Add circles
svg.append("g")
.attr("class", "node")
.selectAll("circle")
.data(root.descendants(), function(d) { return "Circle" + d.data.id; })
.join("circle")
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
`)
.attr("r", 3)
.on('click', click);
// Add text
svg.append("g")
.attr("class", "node")
.selectAll("text")
.data(root.descendants(), function(d) { return "Text" + d.data.id; })
.join("text")
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`)
.attr("text-anchor", d => d.x < Math.PI === !d.children ? "start" : "end")
.text(d => d.data.data.name);
}
// Switch tree on click so centre is now selected node
function click(event,d) {
currentTreeData = findNode(treeData, d.data.id)
chart(currentTreeData);
}
// HELPER FUNCTIONS
// ----------------
// Function to Strafify flat CSV data into a tree
function convertToHierarchy(data) {
var stratify = d3.stratify()
.parentId(function (d) {
return d.parentID;
})
.id(function (d) {
return d.ID;
});
let treeData = stratify(data);
return (treeData)
}
// Function to Create d3 cluster with coordinates etc from stratified data
function clusterise(treeData) {
tree = d3.cluster().size([2 * Math.PI, radius - 100])
let root = tree(d3.hierarchy(treeData)
.sort((a, b) => d3.ascending(a.name, b.name)));
return (root)
}
function findNode(root, id) {
console.log(root);
let selected = root.find(obj => obj.id === id);
selected.parent= null;
console.log(selected);
return(selected)
}
width = 800
height = 600
radius = width / 2
let svg = d3.select("#vis")
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
treeData = convertToHierarchy(flatData)
currentTreeData = treeData
chart(currentTreeData);
</script>
</body>
</html>
这是一个更复杂的示例,它使用新的 .join
方法正确处理 enter
、update
和 exit
模式。这确实允许您添加过渡。请注意,我删除了您的内部包装 g
节点。由于每次点击都会附加一个新的点击,这会弄乱您对可见节点(路径、圆圈和文本)的选择。
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<div id="vis"></div>
<script>
let treeData = [];
let currentTreeData = [];
var flatData = [
{ ID: 1000, name: 'The Root', parentID: null },
{ ID: 1100, name: 'Child 1', parentID: 1000 },
{ ID: 1110, name: 'G.Child 1.1', parentID: 1100 },
{ ID: 1120, name: 'G.Child 1.2', parentID: 1100 },
{ ID: 1130, name: 'G.Child 1.3', parentID: 1100 },
{ ID: 1200, name: 'Child 2', parentID: 1000 },
{ ID: 1210, name: 'G.Child 2.1', parentID: 1200 },
{ ID: 1211, name: 'G.G.Child 2.1.1', parentID: 1210 },
{ ID: 1212, name: 'G.G.Child 2.2.2', parentID: 1210 },
{ ID: 12111, name: 'G.G.G.Child 2.1.1.1', parentID: 1211 },
{ ID: 1300, name: 'Child 3', parentID: 1000 },
];
function chart(thisTreeData) {
let root = clusterise(thisTreeData);
// Add nodes (links)
svg
.selectAll('.line')
.data(root.links(), function (d) {
return 'Link' + ':' + d.target.data.id;
})
.join(
function (enter) {
return enter
.append('path')
.attr('class', 'line')
.attr(
'd',
d3
.linkRadial()
.angle((d) => d.x)
.radius((d) => d.y)
)
.attr('fill', 'none')
.attr('stroke', '#555')
.attr('stroke-opacity', 0.3);
},
function (update) {
update
.transition()
.duration(1000)
.attr(
'd',
d3
.linkRadial()
.angle((d) => d.x)
.radius((d) => d.y)
);
return update;
},
function (exit) {
return exit.remove();
}
);
// Add text
svg
.selectAll('.word')
.data(root.descendants(), function (d) {
return 'Text' + d.data.id;
})
.join(
function (enter) {
return enter
.append('text')
.attr('class', 'word')
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`
)
.attr('text-anchor', (d) =>
d.x < Math.PI === !d.children ? 'start' : 'end'
)
.text((d) => d.data.data.name);
},
function (update) {
update
.transition()
.duration(1000)
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
rotate(${d.x >= Math.PI ? 180 : 0})
`
);
return update;
},
function (exit) {
return exit.remove();
}
);
// Add circles
svg
.selectAll('.round')
.data(root.descendants(), function (d) {
return 'circle' + d.data.id;
})
.join(
function (enter) {
return enter
.append('circle')
.attr('class', 'round')
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
`
)
.attr('r', 5)
.on('click', click);
},
function (update) {
update
.transition()
.duration(1000)
.attr(
'transform',
(d) => `
rotate(${(d.x * 180) / Math.PI - 90})
translate(${d.y},0)
`
);
return update;
},
function (exit) {
return exit.remove();
}
);
}
// Switch tree on click so centre is now selected node
function click(event, d) {
currentTreeData = findNode(treeData, d.data.id);
chart(currentTreeData);
}
// HELPER FUNCTIONS
// ----------------
// Function to Strafify flat CSV data into a tree
function convertToHierarchy(data) {
var stratify = d3
.stratify()
.parentId(function (d) {
return d.parentID;
})
.id(function (d) {
return d.ID;
});
let treeData = stratify(data);
return treeData;
}
// Function to Create d3 cluster with coordinates etc from stratified data
function clusterise(treeData) {
tree = d3.cluster().size([2 * Math.PI, radius - 100]);
let root = tree(
d3.hierarchy(treeData).sort((a, b) => d3.ascending(a.name, b.name))
);
return root;
}
function findNode(root, id) {
//console.log(root);
let selected = root.find((obj) => obj.id === id);
selected.parent = null;
//console.log(selected);
return selected;
}
width = 800;
height = 600;
radius = width / 2;
let svg = d3
.select('#vis')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
treeData = convertToHierarchy(flatData);
currentTreeData = treeData;
chart(currentTreeData);
</script>
</body>
</html>