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属性来示意不同节点与点击处节点的间隔,具体看代码啦。
体验一下就晓得成果了~