在vue3中使用Echarts中的graph实现关系图谱

3,901 阅读3分钟

官网:series-graph

官网示例:关系图

前提是项目中有Echarts包,要是没有,则需要添加该包npm install Echarts。该文用的版本是"echarts": "^5.4.3"

  • 关系图组件a.vue
<template>
  <div class="contentWrap">
    <!-- 关系层级过滤 -->
    <a-row>
      <a-col :span="24">
        <a-form-item label="关系层级" :labelCol="{ xs: { span: 24 }, sm: { span: 5 } }" :wrapperCol="{ xs: { span: 24 }, sm: { span: 16 } }">
          <a-select placeholder="请选择关系层级" v-model:value="selectedLevel" @change="updateChart">
            <template v-for="(obj, index) in levelOptions" :key="index">
              <a-select-option :value="obj.value"> {{ obj.label }}</a-select-option>
            </template>
          </a-select>
        </a-form-item>
      </a-col>
    </a-row>
    //放echart图标的
    <div v-if="data.nodes.length" ref="chartContainer" class="wrap"></div>
    <a-empty v-else description="暂无数据" />
  </div>
</template>
  • 配置项及逻辑处理
<script lang="ts" setup>
  import { ref, unref, reactive, nextTick, onMounted, onUnmounted, defineProps, watch} from 'vue';
  import * as echarts from 'echarts';
  
  const chartContainer = ref<HTMLDivElement | null>(null);
  let myChart = reactive({});
  const selectedLevel = ref('1');
  const levelOptions = ref([
    { label: '一级关系', value: '1' },
    { label: '二级关系', value: '2' },
    { label: '三级关系', value: '3' },
  ]);
  // 接口返回的数据格式如下
  let data = reactive({
    // 关键点,nodes的那么不能重复,所以需要加上id加以区别,让data中的节点的name不能重复。
    nodes: [
      { id: '0', name: '张三', level: '0', parent: '', itemStyle: {color: '#ee6666'} },
      { id: '1', name: '李四', level: '1', parent: '张三', itemStyle: {color: '#9fe080'} },
      { id: '2', name: '王五', level: '1', parent: '张三', itemStyle: {color: '#9fe080'} },
      { id: '3', name: '赵六', level: '2', parent: '李四', itemStyle: {color: '#fc8452'} },
      { id: '4', name: '孙七', level: '2', parent: '王五', itemStyle: {color: '#fc8452'} },
      { id: '5', name: '孙七', level: '2', parent: '张三', itemStyle: {color: '#fc8452'} },
      { id: '6', name: '周八', level: '2', parent: '王五', itemStyle: {color: '#fc8452'} },
    ],
    links: [
      { source: 0, target: 1, level: '1', category: "夫妻" },
      { source: 0, target: 2, level: '1', category: "邻居" },
      { source: 1, target: 0, level: '1', category: "夫妻" },
      { source: 1, target: 3, level: '2', category: "堂兄弟" },
      { source: 2, target: 4, level: '2', category: "夫妻" },
      { source: 4, target: 0, level: '2', category: "祖孙" },
      { source: 2, target: 5, level: '2', category: "姐妹" },
      { source: 2, target: 6, level: '2', category: "姐妹" },
    ]
  });

  let timmer = ref(null);
  const props = defineProps({
    cstGxGraphData: { type: Object, default: ()=>{} },
  });
  const option = reactive({
      series: [{
        type: 'graph', // 图形类别-graph关系图
        layout: 'force', // 布局方式,force为力引导布局
        symbolSize: 40, // 节点的大小-圆圈
        roam: true, // 允许用户拖动和缩放图表
        label: { // 图形上的文本标签-圆圈里边展示的内容
          show: true,
        },
        edgeSymbol: ['circle', 'arrow'], // 连接线两端的形状,此为一端圆圈一端箭头
        edgeSymbolSize: [4, 8], //连接线两端形状的大小
        edgeLabel: {
          show: true,
          fontSize: 14,
          formatter: function(param) {// 标签内容-category
            return param.data.category;
          }
        },
        data: [],// 数据
        links: [],// 关系连接线及关系内容
        lineStyle: { // 连接线的样式
          width: 2,
          curveness: 0
        },
        emphasis: { // 高亮状态的图形样式。
          focus: 'adjacency',
          lineStyle: {
            width: 3,
            curveness: 0
          }
        },
        force: { // 力引导布局相关的配置项
          repulsion: 100,//节点之间的斥力因子,即值越大节点间的连线越长
          edgeLength: 90 //引力因子,值越大越往中心靠拢
        },
        zoom: 2, // 默认图标放大的倍数
        scaleLimit: {// 限制放大缩小的倍数
          min: 1,
          max: 5
        },
        //组件离容器上下左右侧的距离。
        left: 10,
        right: 10,
        top: 10,
        bottom: 10,
      }]
    });
	// 监听父组件传递过来的数据,由于改数据是在父组件中异步请求回来的数据,有延迟,需要监听,否则,拿出来的数据是上一次的数据
  watch(
    () => props.cstGxGraphData,
    (newValue, oldValue) => {
      if(newValue.nodes.length) {
        data.nodes = newValue.nodes;
        nextTick(() => {
          // console.log(newValue, 'DOM已更新watch:', oldValue,);
          if(Object.keys(myChart).length) {
            updateChart();
          }
        });
      }
      if(newValue.links.length) {
        data.links = newValue.links;
      }

    },{
      deep:true,
      immediate:true,
    });

  // 生命周期
  onMounted(()=>{
    // 初始化echart
    myChart = echarts.init(chartContainer.value);
    // console.log(chartContainer.value, 'myChart1:',myChart);
    //随着屏幕大小调节图表
    window.addEventListener("resize", () => {
      console.log('窗口变化啦'); 
      timmer.value = setTimeout(function (){
        myChart.resize();
      },200)
    });
  });
  onUnmounted(() => {
    // 移除监听
    window.removeEventListener('resize', () => {
      myChart.resize()
    }); // 组件销毁前移除事件监听
  });

  // 关系图配置项
  function updateChart() {
    const level = selectedLevel.value;
    // console.log(level);
    // 根据层级过滤对应的数据
    const nodes = data.nodes.filter(node => node.level <= level);
    const links = data.links.filter(link => link.level <= level);
    console.log('关系图配置项:',level,nodes,links);
    // chartData.nodes = nodes
    // chartData.links = lksinks
    drawGraph(nodes,links);
  };
  function drawGraph(nodes,links) {
    if(myChart) {
      myChart.dispose(); // 清除上一次已经画过得图,这样才能实时才到最新数据的图
      myChart = echarts.init(chartContainer.value);
    }
    option.series[0].data = nodes;
    option.series[0].links = links;
    myChart.setOption(option, true); //true用来解决数据不更新问题
    myChart.resize()
  }

</script>
  • 组件样式
<style lang="less" scoped>
  .contentWrap {
    width: 100%;
    height: 100%;
  }
  .wrap {
    width: 100%;
    height: calc(100% - 56px);
  }
  
  </style>
  • 效果图:可以放大缩小,移动