Vue3中echarts力导向图的简略应用和配置

最近有Vue我的项目中应用到Echarts,做一个简略记录。

我的项目实现了一个显示全副节点和局部节点(依据节点长度进行过滤)的性能

做的时候写的一些思考也写在了正文外面

data.json 跟 https://cdn.jsdelivr.net/gh/a... 一样,就不专门贴出来了

<template>  <div id="graph" style="width: 100%; height: 100%">    <div style="padding-top: 15px">      <el-checkbox v-model="config.showAll">显示全副</el-checkbox>    </div>    <div>      <span v-if="!config.showAll">节点长度</span      ><el-input-number        size="mini"        v-if="!config.showAll"        v-model="config.length"        :min="1"        :max="10"      />    </div>    <div>      <el-button size="mini" @click="drawImage()">点击重绘</el-button>    </div>    <div id="chart" ref="scatterMap" class="chart-wrapper" />  </div></template><script lang="ts">import * as echarts from "echarts";import { defineComponent, onMounted, ref, reactive } from "vue";import cloneDeep from "lodash/cloneDeep";import { data } from "./data.js";export default defineComponent({  name: "echarts",  props: {},  setup() {    function getCenterPoint() {      return {        x: scatterMap.value.clientWidth / 2,        y: scatterMap.value.clientHeight / 2,      };    }    const config = reactive({      color: 0,      colorOptions: [        { label: "计划一", value: 0 },        { label: "计划二", value: 1 },        { label: "计划三", value: 2 },      ],      size: 1,      sizeOptions: [        { label: "小", value: 0 },        { label: "中", value: 1 },        { label: "大", value: 2 },      ],      length: 1,      showAll: true,      id: "0", // 随便设置一个,初始化时依据参数重置    });    const lastNodes = ref();    function setNodes(id, length, position, links) {      let existNodes = [id];      // 目标:查找某节点左近的ID,如果之前曾经查找过,则过滤掉      function findNearNodes(id) {        let nodesID = [];        const tempNodeID = [];        // 依据已有的ID查找target和source所对应的ID        links.forEach((item) => {          if (item.source === id) {            tempNodeID.push(item.target);          }          if (item.target === id) {            tempNodeID.push(item.source);          }        });        // 先剔除本身反复,即查找进去的nodesID的反复项        nodesID = [...new Set(tempNodeID)];        // 剔除曾经查找过的点 []        const res = nodesID.filter(function (v) {          return existNodes.indexOf(v) == -1;        });        // 把新找进去的点,加到曾经存在的list中 existNodes        existNodes = existNodes.concat(res);        // 返回曾经剔除进去的新找进去的节点        return res;      }      const nodeLevel = [];      for (let i = 0; i < length + 1; i++) {        nodeLevel.push([]);      }      let res = [];      function setNodeLevel(nodes, levelIndex) {        nodes.forEach((item) => {          nodeLevel[levelIndex].push(item);        });        // 查找节点左近的点        let nearNodes = [];        nodeLevel[levelIndex].forEach((item) => {          nearNodes = nearNodes.concat(findNearNodes(item));        });        if (levelIndex < length) {          setNodeLevel(nearNodes, levelIndex + 1);        } else {          // 依据这个nodeLevel设置对应id的category属性          const visibleNodesId = nodeLevel.flat(); //去括号,获取所有要显示的节点 ID          let coypNodes = cloneDeep(data.nodes);          const visibleNodes = coypNodes.filter((item) => {            return visibleNodesId.indexOf(item.id) !== -1;          });          nodeLevel.forEach((nodeIds, index) => {            nodeIds.forEach((nodeId) => {              // 多级遍历设置对应的category属性              visibleNodes.forEach((node) => {                if (node.id === nodeId) {                  node.category = index + 1;                  node.symbolSize = 15;                }                if (node.id === id) {                  node.x = position.x;                  node.y = position.y;                  node.fixed = true;                }              });            });          });          // 增加上次 查问进去的点          // 遍历 上次查问进去的点          res = visibleNodes;        }      }      // 示例,第一个节点id为'1'      setNodeLevel([id], 0);      return res;    }    function setConfig(nodeId, position) {      let nodes = cloneDeep(data.nodes);      // link能够不扭转,然而category要扭转,cloneDeep避免笼罩      // 是否显示全副,不然有的节点永远选不到,必定要有这么一个选项      // 即:显示全副、显示以后节点      // 如果显示全副节点category如何设置?随机!      // 如果节点局部设置,选中点category为1,其余依照链路累加      if (config.showAll) {        nodes.forEach(function (node) {          node.symbolSize = 15;          node.category = Math.floor(Math.random() * 8);        });      } else {        nodes = setNodes(nodeId, config.length, position, data.links);      }      // TODO      // handle 解决 要显示的node跟上一次的进行合并      const visibleNodes = cloneDeep(nodes);      // const visibleNodesId = visibleNodes.map((item) => {      //   return item.id;      // });      // 把上次的节点也显示进去      // lastNodes.value.forEach((item) => {      //   if (visibleNodesId.indexOf(item.id) === -1) {      //     item.category = 0;      //     item.symbolSize = 15;      //     visibleNodes.push(item);      //   }      // });      // 把以后查出来的nodes保留到lastNodes中      lastNodes.value = cloneDeep(nodes);      // nodes 跟上次的合并解决      const options = {        title: {          text: "",          subtext: "",          top: "bottom",          left: "right",        },        tooltip: {},        series: [          {            // edgeSymbol:["circle","arrow"],            // edgeSymbolSize:10,            name: "Les Miserables",            type: "graph",            layout: "force",            draggable: true,            data: visibleNodes,            links: data.links,            categories: data.categories,            roam: true,            label: {              position: "right",              show: false, // 默认显示label              formatter: function (params) {                //连                if (params.data.source) {                  //留神判断,else是将节点的文字也初始化成想要的格局                  return (                    params.data.source +                    "是【" +                    params.data.target +                    "】的居间人"                  );                } else {                  return params.name;                }              },              clolr: "#fff", // label色彩,              fontSize: 12, // 字体大小            },            force: {              edgeLength: 160, // TODO能够由用户设置  边的两个节点之间的间隔,这个间隔也会受 repulsion。              // 反对设置成数组表白边长的范畴,此时不同大小的值会线性映射到不同的长度。值越小则长度越长。              repulsion: 100, // 节点之间的斥力因子。              // 反对设置成数组表白斥力的范畴,此时不同大小的值会线性映射到不同的斥力。值越大则斥力越大              gravity: 0.1, // 节点受到的向核心的引力因子。该值越大节点越往中心点聚拢。            },          },        ],      };      return options;    }    // 三组配色计划 ok    // 三组间距,球大小 ok    // 点击后,小球居中 ok    // 呈现问题:拖拽后小球地位主动挪动,产生偏移    // filter链路可调    // 箭头双向过滤    // 对于色彩的应用:因为总色彩无限,中心点设为category为1,其余的顺次相加,0作为默认色彩    let myChart = ref(null);    const scatterMap = ref();    const initEcharts = async () => {      myChart.value = echarts.init(scatterMap.value);      lastNodes.value = data.nodes;      draw();      config.id = data.nodes[0].id;      myChart.value.on("click", function (params) {        // 点击节点时,才会触发绘图        if (params.dataType === "node") {          // 设置以后选中点          config.id = params.data.id;          draw();        }      });    };    function drawImage() {      draw();    }    function draw() {      const position = getCenterPoint();      if (config.showAll) {        const option = setConfig(0, position);        myChart.value.setOption(option);      } else {        // TODO设置是否居中,居中的话不便查看,不居中的话可能会导致局部节点不显示?maybe        const option = setConfig(config.id, position);        // 居中事实的话setoption第二个参数为 true        myChart.value.setOption(option);      }    }    onMounted(() => {      initEcharts();    });    return {      drawImage,      scatterMap,      myChart,      initEcharts,      config,    };  },});</script><style scoped>.chart-wrapper {  width: 100%;  height: 600px;}</style>

实现成果

过滤成果

通过设置节点的category属性来示意不同节点与点击处节点的间隔,具体看代码啦。
体验一下就晓得成果了~