这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战
渲染的示例数据
const root = {
val: 1,
children: [
{ val: 2, children: [] },
{
val: 3,
children: [
{ val: 5, children: [] },
{ val: 6, children: [] },
{
val: 7,
children: [
{ val: 11, children: [] },
{ val: 12, children: [] },
],
},
{ val: 8, children: [] },
],
},
{
val: 4,
children: [
{ val: 9, children: [] },
{ val: 10, children: [] },
],
},
],
}
结点不重叠
前几天一个朋友问我怎么样能把一棵树渲染出来,保证结点之间不重叠.学了两个多月的算法这还是难不倒我的.用 BFS 遍历树,一层层渲染即可,这样保证每层之间不会重叠,然后每一层之间相互隔开.
interface DataNode {
val: number
children: DataNode[]
}
// 渲染结点
interface drawNode {
(x: number, y: number, val: number, radius?: number): void
}
function renderTree(root: DataNode) {
const gap = 50
let children = [root],
top = 50
while (children.length) {
let left = 50
const tmp: DataNode[] = []
for (const node of children) {
drawNode(left, top, node.val)
tmp.push(...node.children)
left += gap
}
top += gap
children = tmp
}
}
查看源码 renderTree1.ts
实际渲染的样子,每个结点之间不会重叠
优化渲染效果
只是这样看起来有点丑,果然朋友又加了个要求,想要渲染出来能好看一些,结点的位置能平衡一些,类似 XMind 中的样子:
要渲染成这样,就没办法像上面那样一步到位的完成了,在不知道子孙结点的情况下,无法确定当前结点横坐标的位置.这时候我们可以使用预处理的思想,先将整棵树完整的搜索一遍,获取我们想要的信息,之后再去根据已有信息进行布局.
代码如下:
function renderTree(root: DataNode) {
const nodes = new Map<DataNode, { val: number; x: number; y: number; wide: number }>()
const gap = 50
let top = 50
const queue: (DataNode | null)[][] = [[root]]
// 用 BFS 遍历树,按层将结点放入 queue 中
while (true) {
const tmp: (DataNode | null)[] = []
let foundChild = false
for (const node of queue[queue.length - 1]) {
if (node && node.children.length) {
foundChild = true
tmp.push(...node.children)
} else {
// 当结点没有子结点时,填充 null,预留宽度
tmp.push(null)
}
}
if (!foundChild) break
queue.push(tmp)
}
// 自底向上遍历 queue,先计算子结点的位置以及宽度,然后父结点的宽度等于子结点宽度之和
for (let i = queue.length - 1; i >= 0; i--) {
let left = 0
for (const node of queue[i]) {
let wide = gap
if (node) {
for (const child of node.children) {
wide += nodes.get(child)?.wide!
}
if (wide > gap) wide -= gap
const x = left + wide / 2,
y = top * (i + 1)
nodes.set(node, { x: x, y: y, val: node.val, wide })
drawNode(x, y, node.val)
}
left += wide
}
}
}
查看源码 renderTree.ts
最终渲染出来的样子