本文目标借助d3-hierarchy在不写递归的情况下,完成树的常用操作:
d3-hierarchy文档:github.com/d3/d3-hiera…
常见数据格式
大多从后端返回的树数据常见的有两种格式:
第一种表格数据(tabular data):
const categoryTableData = [
{"id": 1, "name": "此电脑", "parentId": null},
{"id": 10, "name": "图片", "parentId": 1},
{"id": 11, "name": "风景", "parentId": 10},
{"id": 12, "name": "卡通", "parentId": 10},
{"id": 13, "name": "壁纸", "parentId": 10},
{"id": 20, "name": "音乐", "parentId": 1},
{"id": 21, "name": "摇滚", "parentId": 20},
{"id": 22, "name": "流行", "parentId": 20},
]
第二种已分层的数据(hierarchical format data):
const categoryTreeData = {
"id": 1,
"name": "此电脑",
"children": [
{
"id": 10,
"name": "图片",
"children": [
{
"id": 11,
"name": "风景"
},
{
"id": 12,
"name": "卡通"
},
{
"id": 13,
"name": "壁纸"
}
]
},
{
"id": 20,
"name": "音乐",
"children": [
{
"id": 21,
"name": "摇滚"
},
{
"id": 22,
"name": "流行"
}
]
}
]
}
开始前的准备:
# 安装d3-hierarchy
npm install d3-hierarchy@^2.0.0
npm install lodash@^4.17.21
yarn add d3-hierarchy@^2.0.0
yarn add lodash@^4.17.21
# 引入依赖
import * as d3 from 'd3-hierarchy'
import _ from 'lodash'
data.js
export const categoryTableData = [
{"id": 1, "name": "此电脑", "parentId": null},
{"id": 10, "name": "图片", "parentId": 1},
{"id": 11, "name": "风景", "parentId": 10},
{"id": 12, "name": "卡通", "parentId": 10},
{"id": 13, "name": "壁纸", "parentId": 10},
{"id": 20, "name": "音乐", "parentId": 1},
{"id": 21, "name": "摇滚", "parentId": 20},
{"id": 22, "name": "流行", "parentId": 20},
]
export const categoryTreeData = {
"id": 1,
"name": "此电脑",
"children": [
{
"id": 10,
"name": "图片",
"children": [
{
"id": 11,
"name": "风景"
},
{
"id": 12,
"name": "卡通"
},
{
"id": 13,
"name": "壁纸"
}
]
},
{
"id": 20,
"name": "音乐",
"children": [
{
"id": 21,
"name": "摇滚"
},
{
"id": 22,
"name": "流行"
}
]
}
]
}
1.树结构的生成,生成统一格式的树结构
使用d3.stratify方法处理平铺开的数据:
import * as d3 from 'd3-hierarchy';
import {categoryTableData} from './data';
const categoryTree = d3.stratify()
.id((d) => d.id) // 节点id
.parentId((d) => d.parentId) // 父节点id
(categoryTableData);
使用d3.hierarchy方法处理带层级的数据:
import * as d3 from 'd3-hierarchy';
import {categoryTreeData} from './data';
const categoryTree2 = d3.hierarchy(categoryTreeData, (item) => item.children);
至此,两种不同的数据结构通过d3.stratify、d3.hierarchy方法达到了统一。
返回的Node节点(根节点)以及所有层级节点都具有的属性:
- node.data 节点对应的原始数据
- node.depth 节点深度,从0开始,根节点为0、一级为1以此类推。(更多的用于数据可视化)
- node.height 节点高度,从0开始,所有的叶子节点为0,父节点加1以此类推。(更多的用于数据可视化)
- node.parent 指向父节点,根节点的此值为null
- node.children 子节点组成的数组,或者为undefined
- node.value 节点汇总结果,包含所有的子节点。node.sum自定义求和,node.value计算当前节点的所有子节点个数包含当前节点。此操作是impure的,会改变原数据。
2.节点的遍历
node.each(function[, that]) 采用广度优先遍历(breadth-first order)层次遍历距根近的先遍历。
import * as d3 from 'd3-hierarchy';
import _ from 'lodash';
import {categoryTableData} from './data';
const categoryTree = d3.stratify()
.id((d) => d.id) // 节点id
.parentId((d) => d.parentId) // 父节点id
(categoryTableData);
/*
打印如下:
此电脑
--图片
--音乐
----风景
----卡通
----壁纸
----摇滚
----流行
*/
categoryTree.each(function (item) {
const prefixSpace = _.repeat('--', item.depth);
const {name} = item.data;
console.log(`${prefixSpace}${name}`);
})
node.eachAfter(function[, that]) 采用深度优先的后序遍历(Post-Order Traversal),先访问子树,然后访问根的遍历方式。
import * as d3 from 'd3-hierarchy';
import _ from 'lodash';
import {categoryTableData} from './data';
const categoryTree = d3.stratify()
.id((d) => d.id) // 节点id
.parentId((d) => d.parentId) // 父节点id
(categoryTableData);
/*
----风景
----卡通
----壁纸
--图片
----摇滚
----流行
--音乐
此电脑
*/
categoryTree.eachAfter(function (item) {
const prefixSpace = _.repeat('--', item.depth);
const {name} = item.data;
console.log(`${prefixSpace}${name}`);
})
node.eachBefore(function[, that]) 采用深度优先的前序遍历(Pre-Order Traversal),先访问根,然后访问子树的遍历方式。
import * as d3 from 'd3-hierarchy';
import _ from 'lodash';
import {categoryTableData} from './data';
const categoryTree = d3.stratify()
.id((d) => d.id) // 节点id
.parentId((d) => d.parentId) // 父节点id
(categoryTableData);
/*
此电脑
--图片
----风景
----卡通
----壁纸
--音乐
----摇滚
----流行
*/
categoryTree.eachBefore(function (item) {
const prefixSpace = _.repeat('--', item.depth);
const {name} = item.data;
console.log(`${prefixSpace}${name}`);
})
3. 节点查找 node.find(filter)
假设节点视图被点击时,得到节点id,此时可通过node.find(filter)获取对应的Node。
import * as d3 from 'd3-hierarchy';
import {categoryTableData} from './data';
const categoryTree = d3.stratify()
.id((d) => d.id) // 节点id
.parentId((d) => d.parentId) // 父节点id
(categoryTableData);
function findNodeById(id) {
return categoryTree.find(function (item) {
return item.data.id === id
})
}
// 音乐的id为20
const musicNode = findNodeById(20);
// 打印: {id: 20, name: "音乐", parentId: 1}
console.log(musicNode.data);
// console.log(matchedNode.children);
4.节点距根节点的路径 node.ancestors()
从当前节点到父节点一直到根节点
常用示例显示当前位置:
import * as d3 from 'd3-hierarchy';
import _ from 'lodash';
import {categoryTableData, categoryTreeData} from './data';
const categoryTree = d3.stratify()
.id((d) => d.id) // 节点id
.parentId((d) => d.parentId) // 父节点id
(categoryTableData);
function findNodeById(id) {
return categoryTree.find(function (item) {
return item.data.id === id
})
}
// 音乐的id为20
const musicNode = findNodeById(20);
const nodes = musicNode.ancestors().reverse();
const res = _.map(nodes, node => node.data.name).join('->');
// 打印为:此电脑->音乐
console.log(res);
5.最短路径 node.path(target)
获取两个节点的最短路径:
import * as d3 from 'd3-hierarchy';
import _ from 'lodash';
import {categoryTableData} from './data';
const categoryTree = d3.stratify()
.id((d) => d.id) // 节点id
.parentId((d) => d.parentId) // 父节点id
(categoryTableData);
function findNodeById(id) {
return categoryTree.find(function (item) {
return item.data.id === id
})
}
// 音乐的id为20
const musicNode = findNodeById(20);
// 卡通的id为11
const cartoonNode = findNodeById(11);
const paths = musicNode.path(cartoonNode);
const res = _.map(paths, node => node.data.name).join('->');
// 音乐节点到卡通节点的最短路径为 音乐->此电脑->图片->风景
console.log(res);
更多操作请访问:github.com/d3/d3-hiera…
关于树操作数据层面的介绍就到这里,ui可视化部分待你实战,比如图表、以及vue和react相关树ui等。希望本文对你有帮助:)