d3相关api

本文为一个最简单的树形结构过渡作用打开的核心功能,没有增加连接线等逻辑

hierarchy

family = d3.hierarchy({
  name: "root",
  children: [
    {name: "child #1"},
    {
      name: "child #2",
      children: [
        {name: "grandchild #1"},
        {name: "grandchild #2"},
        {name: "grandchild #3"}
      ]
    }
  ]
})

hierarchy接纳一个树形结构的方针,d3会依据这个树形结构生成一个新的树形结构,这个新的树形结构中有几个主要的特点

  • data: 原始树形结构对应方位的数据
  • parent:父级节点的引证
  • depth:当时节点的层级,根节点为0
  • height:当时节点到当时路径叶子节点的节点数,叶子节点的height为0

d3.js完成树形结构过渡打开、折叠
能够看到上面的图片则为新构建树形结构。hierarchy的第二个参数是一个函数,函数接纳原始数据为参数,回来一个数组类型,默认为children,也就是使用children特点构建层级结构,假如我们回来其他字段,就会使用相应字段构建层级结构。

  const family = hierarchy(
    {
      name: "root",
      parents: [
        { name: "parent #1" },
        {
          name: "parent #2",
          children: [
            { name: "grandparent#1" },
            { name: "grandparent#2" },
            { name: "grandparent#3" },
          ],
        },
      ],
    },
    (d) => {
      return d.parents;
    }
  );

d3.js完成树形结构过渡打开、折叠
从上图能够看出,新结构的children是使用原始数据的parents构建的

d3.tree()

依据装备给树形结构增加坐标

tree.nodeSize() 参数为一个数组,分别是每个节点的水平间隔和垂直间隔

transition

    selection.transition().duration(毫秒过渡时间)

为当时selection节点设置特点时增加过渡作用

开始制作

定义一个插件类DrawGraphPlugin,参数为数据data和el挂载方针方针

class DrawGraphPlugin {
    constructor(config) {
        this.el = config.el;
        this.daddta = config.data;
    }
}

在onMounted生命周期函数中调用

<template>
  <div class="svgContainer"></div>
</template>
<script setup>
import { onMounted, ref } from "vue";
    const data = {
      name: "根节点",
      children: [
        {
          name: "一级节点 1",
          children: [{ name: "二级节点 1-1" }, { name: "二级节点 1-2" }],
        },
        {
          name: "一级节点 2",
          children: [{ name: "二级节点 2-1" }, { name: "二级节点 2-2" }],
        },
      ],
    };
    onMounted(() => {
      new DrawGraphPlugin({
        el: ".svgContainer",
        data,
      });
    });
</script>

开始编写制作插件DrawGraphPlugin

初始化dom结构

  initDomStructure() {
    // 生成svg
    const svg = create("svg")
      .attr("xmlns", "http://www.w3.org/2000/svg")
      .attr("height", this.gInfo.height)
      .attr("width", this.gInfo.width)
      .attr("viewBox", () => {
        return [
          -this.gInfo.width / 2,
          -200,
          this.gInfo.width,
          this.gInfo.height,
        ];
      })
      .style("user-select", "none")
      .style("cursor", "move");
    this.rootContainer = svg.append("g").attr("class", "containerG");
    // 增加zoom,扩大、伸缩的作用
    svg
      .call(
        zoom()
          .scaleExtent([0.2, 5])
          .on("zoom", () => {
            const { x, y, k } = event.transform;
            this.rootContainer.attr("transform", () => {
              return `translate(${x},${y}) scale(${k})`;
            });
          })
      )
      // 取消双击扩大的事情
      .on("dblclick.zoom", null);
    // 将新生成的svg增加到el节点中
    select(this.el).node().appendChild(svg.node());
    this.svg = svg;
  }

初始化树结构数据

class DrawGraphPlugin {
  constructor(config) {
      ...
  }
  // 初始化树结构数据
  initTreeStructure(data) {
     // 增加层级关系
    const hierarchyData = hierarchy(data);
    // 增加方位坐标
    const descendantsData = tree().nodeSize([100, 200])(hierarchyData);
    return descendantsData;
  }
}

更新办法

  // 制作、更新 办法
  // source为点击时记载的方位,用于从点击的方位打开或许折叠到点击的方位
  draw(source) {
    // 给rootContainer绑定数据
    const nodesSelection = this.rootContainer
      .selectAll(".itemG")
      .data(this.treeData.descendants(), (d) => {
        return d.data.name;
      });
    const that = this;
    const itemG = nodesSelection.enter().append("g");
    // 先移动到点击点方位
    itemG.attr("class", "itemG").attr("transform", (d) => {
      // 先直接移动到点击点的方位
      return `translate(${source.x},${source.y})`;
    });
    // 再从点击点开始 动画移动到方针点
    itemG
      .transition()
      .duration(200)
      .attr("transform", function (d) {
        return `translate(${d.x},${d.y})`;
      });
    // 增加点击事情 折叠、打开
    itemG.on("click", function (d) {
      // 假如存在children则需求折叠,将children设置为null,一起用_children保存
      if (d.children) {
        d._children = d.children;
        d.children = null;
      }
      // 不存在children需求打开,将children康复为_children
      else {
        d.children = d._children;
        d._children = null;
      }
      // 重新制作,将当时节点信息作为参数传入(需求当时节点的x、y坐标)
      that.draw(d);
    });
    // 制作节点中的内容 方块、文本
    itemG
      .append("rect")
      .attr("width", 80)
      .attr("height", 50)
      .attr("stroke", "rgb(64, 137, 230)")
      .attr("fill", "#fff");
    itemG
      .append("text")
      .text((d) => d.data.name)
      .attr("font-size", 12)
      .attr("text-anchor", "middle")
      .attr("transform", (d) => {
        return `translate(${40},${30})`;
      });
    // 退出状况动画,将点击节点的子节点移动到点击的节点,隐藏
    nodesSelection
      .exit()
      .transition()
      .duration(200)
      .attr("transform", function () {
        return `translate(${source.x},${source.y})`;
      })
      .style("opacity", 0)
      .remove();
  }

完好代码

<template>
  <div class="svgContainer"></div>
</template>
<script setup>
import { onMounted } from "vue";
import { create, select, tree, hierarchy, zoom, event } from "d3";
const data = {
  name: "根节点",
  children: [
    {
      name: "一级节点 1",
      children: [{ name: "二级节点 1-1" }, { name: "二级节点 1-2" }],
    },
    {
      name: "一级节点 2",
      children: [{ name: "二级节点 2-1" }, { name: "二级节点 2-2" }],
    },
  ],
};
onMounted(() => {
  new DrawGraphPlugin({
    el: ".svgContainer",
    data,
  });
});
class DrawGraphPlugin {
  constructor(config) {
    this.el = config.el;
    this.data = config.data;
    this.svg = null;
    this.gInfo = {
      height: document.documentElement.clientHeight,
      width: document.documentElement.clientWidth,
    };
    this.treeData = this.initTreeStructure(this.data);
    this.initDomStructure();
    this.draw({ x: 0, y: 0 });
  }
  initDomStructure() {
    // 生成svg
    const svg = create("svg")
      .attr("xmlns", " = svg;
  }
  initTreeStructure(data) {
    const hierarchyData = hierarchy(data);
    const descendantsData = tree().nodeSize([100, 200])(hierarchyData);
    return descendantsData;
  }
  // 制作、更新 办法
  // source为点击时记载的方位,用于从点击的方位打开或许折叠到点击的方位
  draw(source) {
    // 给rootContainer绑定数据
    const nodesSelection = this.rootContainer
      .selectAll(".itemG")
      .data(this.treeData.descendants(), (d) => {
        return d.data.name;
      });
    const that = this;
    const itemG = nodesSelection.enter().append("g");
    // 先移动到点击点方位
    itemG.attr("class", "itemG").attr("transform", (d) => {
      // 先直接移动到点击点的方位
      return `translate(${source.x},${source.y})`;
    });
    // 再从点击点开始 动画移动到方针点
    itemG
      .transition()
      .duration(200)
      .attr("transform", function (d) {
        return `translate(${d.x},${d.y})`;
      });
    // 增加点击事情 折叠、打开
    itemG.on("click", function (d) {
      // 假如存在children则需求折叠,将children设置为null,一起用_children保存
      if (d.children) {
        d._children = d.children;
        d.children = null;
      }
      // 不存在children需求打开,将children康复为_children
      else {
        d.children = d._children;
        d._children = null;
      }
      // 重新制作,将当时节点信息作为参数传入(需求当时节点的x、y坐标)
      that.draw(d);
    });
    // 制作节点中的内容 方块、文本
    itemG
      .append("rect")
      .attr("width", 80)
      .attr("height", 50)
      .attr("stroke", "rgb(64, 137, 230)")
      .attr("fill", "#fff");
    itemG
      .append("text")
      .text((d) => d.data.name)
      .attr("font-size", 12)
      .attr("text-anchor", "middle")
      .attr("transform", (d) => {
        return `translate(${40},${30})`;
      });
    // 退出状况动画,将点击节点的子节点移动到点击的节点,隐藏
    nodesSelection
      .exit()
      .transition()
      .duration(200)
      .attr("transform", function () {
        return `translate(${source.x},${source.y})`;
      })
      .style("opacity", 0)
      .remove();
  }
}
</script>