默认节点

默认样式

组件默认提供了一个样式比较简单的树节点,来满足一些简单的场景, 通过指定default-node-key来表示要显示数据中哪个属性值。

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [
    {name: "水果"},
    {name: "蔬菜"},
  ],
});
</script>

<template>
  <div class="canvas-container">
    <VueTree :data="treeData" default-node-key="name" />
  </div>
</template>

<style scoped>
.canvas-container {
  width: 100%;
  height: 200px;
  border-radius: 5px;
  border: 1px solid gray;
  margin-bottom: 10px;
}
</style>

default-node-key属性可以指定成函数,来获取层级比较复杂的属性

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: {en: "Food"},
  children: [
    {name: { en: "Fruit" }},
    {name: { en: "Vegetable"}},
  ],
});

const getNodeKey = (data) => data.name.en
</script>

<template>
  <div class="canvas-container">
    <VueTree :data="treeData" :default-node-key="getNodeKey" />
  </div>
</template>

修改默认节点样式

可以通过default-node-style属性覆盖默认节点的样式

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [{ name: "水果" }, { name: "蔬菜" }],
});

//树节点样式
const nodeStyle = ref({
  width: "60px",
  height: "30px",
  borderColor: "red",
  backgroundColor: "green",
  color: "#fff",
  textAlign: "center",
  lineHeight: "30px",
});
</script>

<template>
  <div class="canvas-container">
    <VueTree
      :data="treeData"
      default-node-key="name"
      :default-node-style="nodeStyle"
    />
  </div>
</template>

default-node-style属性可以指定成函数,给不同节点指定不同的样式。

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [{ name: "水果" }, { name: "蔬菜" }],
});

const nodeColor = {
  "食物": "blue",
  "水果": "red",
  "蔬菜": "green"
}

function handleNodeStyle({data}) {
  return {  
            width: '60px',
            height: '30px',
            backgroundColor: nodeColor[data.name],
            color: '#fff',
            textAlign: 'center',
            lineHeight: '30px',
          }
}
</script>

<template>
  <div class="canvas-container">
      <VueTree
        :data="treeData"
        default-node-key="name"
        :default-node-style="handleNodeStyle"
      />
    </div>
</template>

自定义节点

尺寸不变的节点

大多数场景树节点的样式比较复杂,默认的节点无法满足,可以使用Vue插槽的方式自定义树节点。

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [
    { name: "水果" }, 
    { name: "蔬菜" }
  ],
});
</script>

<template>
  <div class="canvas-container">
    <VueTree :data="treeData">
      <!-- 使用插槽自定义树节点 -->
      <template #node="{ data, index }">
        <div class="custom-node">
          <img :src="`/images/${data.name}.svg`" />
          {{ data.name }}
          <span>{{ `#${index + 1}` }}</span>
        </div>
      </template>
      <!-- 使用插槽自定义树节点 end -->
    </VueTree>
  </div>
</template>

<style scoped>
.canvas-container {
  width: 100%;
  height: 200px;
  border-radius: 5px;
  border: 1px solid gray;
}

.custom-node {
  width: 80px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  border: 1px solid grey;
  padding: 5px;
}

.custom-node img {
  width: 20px;
  height: 20px;
  margin-right: 5px;
}

.custom-node span {
  margin-left: 5px;
  color: blue;
  font-size: 12px;
}
</style>

尺寸变化的节点

当自定义节点的尺寸有变化时,需要调用reSize方法,来让组件重新根据变化后的大小来重绘。

SizeNode.vue

<script setup>
import { ref } from "vue"

const bigSize = ref(true)

const emit = defineEmits(["click"]);

function onClick() {
  bigSize.value = !bigSize.value
  emit('click')
}

</script>

<template>
  <div 
    class="change-size-node" 
    :class="[bigSize ? 'big-size' : 'small-size']"
    @click="onClick">
    {{ bigSize ? "点击变小" : "点击变大" }}
  </div>
</template>
<style scoped>
.change-size-node {
  display: flex;
  justify-content: center;
  align-items: center;
  border: solid 1px black;
  cursor: pointer;
  font-size: 10px;
}

.big-size {
  width: 60px;
  height: 60px;
}

.small-size {
  width: 40px;
  height: 40px;
}
</style>
<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";
import SizeNode from "./SizeNode.vue"

const treeData = ref({
  name: "食物",
  children: [{ name: "水果" }, { name: "蔬菜" }],
});

const treeRef = ref(null)
</script>

<template>
  <div class="canvas-container">
    <VueTree :data="treeData" ref="treeRef">
      <template #node="{ data, index }">
        <!-- 当节点大小改变后,调用reSize()方法,重新绘制 -->
        <SizeNode @click="() => treeRef.reSize()" />
      </template>
    </VueTree>
  </div>
</template>

<style scoped>
.canvas-container {
  width: 100%;
  height: 200px;
  border-radius: 5px;
  border: 1px solid gray;
}
</style>

节点收起和展开

点击树节点收起、展开

组件默认不能收起和展开节点,设置属性collapsed-way=clickTreeNode, 表示点击树节点来收起和展开。

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [
    {
      name: "水果",
      children: [{ name: "苹果" }, { name: "橘子" }]
    },
    {
      name: "蔬菜",
      children: [{ name: "黄瓜" }, { name: "萝卜" }]
    },
    { name: "粮食" },
  ],
});
</script>

<template>
  <div class="canvas-container">
    <VueTree 
      :data="treeData" 
      default-node-key="name" 
      collapsed-way="clickTreeNode" 
    />
  </div>
</template>

默认收起的节点会用虚线样式,可以指定自定义的样式进行覆盖

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [
    {
      name: "水果",
      children: [{ name: "苹果" }, { name: "橘子" }]
    },
    {
      name: "蔬菜",
      children: [{ name: "黄瓜" }, { name: "萝卜" }]
    },
    { name: "粮食" },
  ],
});

//树节点折叠样式
const nodeCollapsedStyle = {
  width: '60px',
  height: '30px',
  border: '1px dashed green',
  textAlign: 'center',
  lineHeight: '30px',
}
</script>

<template>
  <div class="canvas-container">
    <VueTree
      :data="treeData"
      collapsed-way="clickTreeNode"
      default-node-key="name"
      :default-node-collapsed-style="nodeCollapsedStyle"
    />
  </div>
</template>

使用连接节点收起、展开

组件提供了连接节点用于显示一个节点子节点的数量,组件默认不显示连接节点,需要通过指定show-knot属性来显示。

可以通过点击连接节点来收起和展开节点, 设置属性collapsed-way="clickKnotNode"

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [
    {
      name: "水果",
      children: [{ name: "苹果" }, { name: "橘子" }]
    },
    {
      name: "蔬菜",
      children: [{ name: "黄瓜" }, { name: "萝卜" }]
    },
    { name: "粮食" },
  ],
});
</script>

<template>
  <div class="canvas-container">
    <VueTree 
      show-knot
      :data="treeData" 
      default-node-key="name" 
      collapsed-way="clickKnotNode" 
    />
  </div>
</template>

连接节点也可以通过Vue插槽的方式来自定义,定制满足自己的连接节点。

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [
    {
      name: "水果",
      children: [{ name: "苹果" }, { name: "橘子" }]
    },
    {
      name: "蔬菜",
      children: [{ name: "黄瓜" }, { name: "萝卜" }]
    },
    { name: "粮食" },
  ],
});

</script>

<template>
  <div class="canvas-container">
    <VueTree 
      :data="treeData" 
      show-knot
      default-node-key="name"  
      collapsed-way="clickKnotNode">
      <!-- 自定义连接节点 -->
      <template #knot="{ node, data, index }">
        <div class="custom-knot">
          {{ node.numChildren }}
        </div>
      </template>
      <!-- 自定义连接节点 -->
    </VueTree>
  </div>
</template>

<style scoped>
.custom-knot {
  width: 15px;
  height: 15px;
  border-radius: 15px;
  text-align: center;
  line-height: 15px;
  font-size: 12px;
  background-color: #00BFFF;
  color: #fff;
  cursor: pointer;
}
</style>

排列方向

<script setup>
import { ref } from "vue"
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [
    { name: "水果" }, 
    { name: "蔬菜" }
  ],
});

//方向
const direction = ref("vertical");

</script>

<template>
  <div class="demo-main">
    <div>
      <div>
        <label>排列方向:</label>
        <label><input type="radio"  value="vertical" v-model="direction">竖向</label>
        <label><input type="radio"  value="horizontal" v-model="direction">横向</label>
      </div>
      <div class="canvas-container">
        <VueTree
          :data="treeData"
          :direction="direction"
          default-node-key="name"
        />
      </div>
    </div>
  </div>
</template>
<style scoped>
.canvas-container {
  width: 100%;
  height: 200px;
  border-radius: 5px;
  border: 1px solid gray;
}
</style>

缩放

组件默认是通过鼠标滚动进行缩放的,也可以手动进行缩放

<script setup>
import { ref } from "vue";
import VueTree from "vue3-d3-tree";

const treeData = ref({
  name: "食物",
  children: [{ name: "水果" }, { name: "蔬菜" }],
});

const treeRef = ref(null);
const zoomRatio = ref(1);

//放大
const zoomIn = () => {
  zoomRatio.value += 0.1;
  treeRef.value.zoom(zoomRatio.value);
}

//缩小
const zoomOut = () => {
  zoomRatio.value -= 0.1;
  treeRef.value.zoom(zoomRatio.value);
}

//还原
const zoomOrigin = () => {
  zoomRatio.value = 1;
  treeRef.value.zoom(zoomRatio.value);
}

</script>

<template>
  <div>
    <button @click="zoomIn">
      放大
    </button>
    <button @click="zoomOut">
      缩小
    </button>
    <button @click="zoomOrigin">
      还原
    </button>
    <div class="canvas-container">
      <VueTree 
        ref="treeRef" 
        :wheel-zoom="false" 
        :data="treeData" 
      />
    </div>
  </div>
</template>

连接线

连接线支持直线和曲线,也可以设置部分样式

<script setup>
import { ref } from "vue"
import VueTree from "vue3-d3-tree";

const linkStyle1 = {
  stroke: "gray",
  strokeDasharray: "none",
  strokeOpacity: 1,
  strokeWidth: 1,
};

const linkStyle2 = {
  stroke: "green",
  strokeDasharray: "none",
  strokeOpacity: 1,
  strokeWidth: 2,
};

const treeData = ref({
  name: "食物",
  children: [
    { name: "水果" }, 
    { name: "蔬菜" }
  ],
});

//连接线样式 polyline:折线;curve:曲线
const linkType = ref("polyline")
//连接线样式
const linkStyle = ref(linkStyle1)

</script>

<template>
  <div class="demo-main">
    <div>
      <div>
        <label>类型:</label>
        <label><input type="radio"  value="polyline" v-model="linkType">折线</label>
        <label><input type="radio"  value="curve" v-model="linkType">曲线</label>
      </div>
      <div>
        <label>样式:</label>
        <label><input type="radio"  :value="linkStyle1" v-model="linkStyle">样式1</label>
        <label><input type="radio"  :value="linkStyle2" v-model="linkStyle">样式2</label>
      </div>
      <div class="canvas-container">
        <VueTree
          :data="treeData"
          :lineType="linkType"
          :line-style="linkStyle"
          default-node-key="name"
          collapsed-way="clickTreeNode"
        />
      </div>
    </div>
  </div>
</template>