数据处理实战:不写递归实现树操作

363 阅读4分钟

本文目标借助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方法达到了统一。

605ad1c946df2736b1a5497b.png

返回的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。

605ad25c46df2736b1a5497d.png

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()

从当前节点到父节点一直到根节点

605ad32246df2736b1a54980.png

常用示例显示当前位置:

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等。希望本文对你有帮助:)

原文链接

www.hijs.ren/article/blo…