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
能够看到上面的图片则为新构建树形结构。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;
}
);
从上图能够看出,新结构的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>