# Vue3中echarts力導(dǎo)向圖的簡(jiǎn)單使用和配置
最近有Vue項(xiàng)目中使用到Echarts,做一個(gè)簡(jiǎn)單記錄。
項(xiàng)目實(shí)現(xiàn)了一個(gè)顯示全部節(jié)點(diǎn)和部分節(jié)點(diǎn)(根據(jù)節(jié)點(diǎn)長(zhǎng)度進(jìn)行過(guò)濾)的功能
做的時(shí)候?qū)懙囊恍┧伎家矊懺诹俗⑨尷锩?/p>
data.json 跟 https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/examples/data/asset/data/les-miserables.json 一樣,就不專門貼出來(lái)了
```
<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">節(jié)點(diǎn)長(zhǎng)度</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()">點(diǎn)擊重繪</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", // 隨意設(shè)置一個(gè),初始化時(shí)根據(jù)參數(shù)重置
? ? });
? ? const lastNodes = ref();
? ? function setNodes(id, length, position, links) {
? ? ? let existNodes = [id];
? ? ? // 目的:查找某節(jié)點(diǎn)附近的ID,如果之前已經(jīng)查找過(guò),則過(guò)濾掉
? ? ? function findNearNodes(id) {
? ? ? ? let nodesID = [];
? ? ? ? const tempNodeID = [];
? ? ? ? // 根據(jù)已有的ID查找target和source所對(duì)應(yīng)的ID
? ? ? ? links.forEach((item) => {
? ? ? ? ? if (item.source === id) {
? ? ? ? ? ? tempNodeID.push(item.target);
? ? ? ? ? }
? ? ? ? ? if (item.target === id) {
? ? ? ? ? ? tempNodeID.push(item.source);
? ? ? ? ? }
? ? ? ? });
? ? ? ? // 先剔除自身重復(fù),即查找出來(lái)的nodesID的重復(fù)項(xiàng)
? ? ? ? nodesID = [...new Set(tempNodeID)];
? ? ? ? // 剔除已經(jīng)查找過(guò)的點(diǎn) []
? ? ? ? const res = nodesID.filter(function (v) {
? ? ? ? ? return existNodes.indexOf(v) == -1;
? ? ? ? });
? ? ? ? // 把新找出來(lái)的點(diǎn),加到已經(jīng)存在的list中 existNodes
? ? ? ? existNodes = existNodes.concat(res);
? ? ? ? // 返回已經(jīng)剔除出來(lái)的新找出來(lái)的節(jié)點(diǎn)
? ? ? ? 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);
? ? ? ? });
? ? ? ? // 查找節(jié)點(diǎn)附近的點(diǎn)
? ? ? ? let nearNodes = [];
? ? ? ? nodeLevel[levelIndex].forEach((item) => {
? ? ? ? ? nearNodes = nearNodes.concat(findNearNodes(item));
? ? ? ? });
? ? ? ? if (levelIndex < length) {
? ? ? ? ? setNodeLevel(nearNodes, levelIndex + 1);
? ? ? ? } else {
? ? ? ? ? // 根據(jù)這個(gè)nodeLevel設(shè)置對(duì)應(yīng)id的category屬性
? ? ? ? ? const visibleNodesId = nodeLevel.flat(); //去括號(hào),獲取所有要顯示的節(jié)點(diǎn) ID
? ? ? ? ? let coypNodes = cloneDeep(data.nodes);
? ? ? ? ? const visibleNodes = coypNodes.filter((item) => {
? ? ? ? ? ? return visibleNodesId.indexOf(item.id) !== -1;
? ? ? ? ? });
? ? ? ? ? nodeLevel.forEach((nodeIds, index) => {
? ? ? ? ? ? nodeIds.forEach((nodeId) => {
? ? ? ? ? ? ? // 多級(jí)遍歷設(shè)置對(duì)應(yīng)的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;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? });
? ? ? ? ? ? });
? ? ? ? ? });
? ? ? ? ? // 添加上次 查詢出來(lái)的點(diǎn)
? ? ? ? ? // 遍歷 上次查詢出來(lái)的點(diǎn)
? ? ? ? ? res = visibleNodes;
? ? ? ? }
? ? ? }
? ? ? // 示例,第一個(gè)節(jié)點(diǎn)id為'1'
? ? ? setNodeLevel([id], 0);
? ? ? return res;
? ? }
? ? function setConfig(nodeId, position) {
? ? ? let nodes = cloneDeep(data.nodes);
? ? ? // link可以不改變,但是category要改變,cloneDeep防止覆蓋
? ? ? // 是否顯示全部,不然有的節(jié)點(diǎn)永遠(yuǎn)選不到,肯定要有這么一個(gè)選項(xiàng)
? ? ? // 即:顯示全部、顯示當(dāng)前節(jié)點(diǎn)
? ? ? // 如果顯示全部節(jié)點(diǎn)category如何設(shè)置?隨機(jī)!
? ? ? // 如果節(jié)點(diǎn)部分設(shè)置,選中點(diǎn)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跟上一次的進(jìn)行合并
? ? ? const visibleNodes = cloneDeep(nodes);
? ? ? // const visibleNodesId = visibleNodes.map((item) => {
? ? ? //? return item.id;
? ? ? // });
? ? ? // 把上次的節(jié)點(diǎn)也顯示進(jìn)去
? ? ? // lastNodes.value.forEach((item) => {
? ? ? //? if (visibleNodesId.indexOf(item.id) === -1) {
? ? ? //? ? item.category = 0;
? ? ? //? ? item.symbolSize = 15;
? ? ? //? ? visibleNodes.push(item);
? ? ? //? }
? ? ? // });
? ? ? // 把當(dāng)前查出來(lái)的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, // 默認(rèn)顯示label
? ? ? ? ? ? ? formatter: function (params) {
? ? ? ? ? ? ? ? //連
? ? ? ? ? ? ? ? if (params.data.source) {
? ? ? ? ? ? ? ? ? //注意判斷,else是將節(jié)點(diǎn)的文字也初始化成想要的格式
? ? ? ? ? ? ? ? ? return (
? ? ? ? ? ? ? ? ? ? params.data.source +
? ? ? ? ? ? ? ? ? ? "是【" +
? ? ? ? ? ? ? ? ? ? params.data.target +
? ? ? ? ? ? ? ? ? ? "】的居間人"
? ? ? ? ? ? ? ? ? );
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? return params.name;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? },
? ? ? ? ? ? ? clolr: "#fff", // label顏色,
? ? ? ? ? ? ? fontSize: 12, // 字體大小
? ? ? ? ? ? },
? ? ? ? ? ? force: {
? ? ? ? ? ? ? edgeLength: 160, // TODO可以由用戶設(shè)置? 邊的兩個(gè)節(jié)點(diǎn)之間的距離,這個(gè)距離也會(huì)受 repulsion。
? ? ? ? ? ? ? // 支持設(shè)置成數(shù)組表達(dá)邊長(zhǎng)的范圍,此時(shí)不同大小的值會(huì)線性映射到不同的長(zhǎng)度。值越小則長(zhǎng)度越長(zhǎng)。
? ? ? ? ? ? ? repulsion: 100, // 節(jié)點(diǎn)之間的斥力因子。
? ? ? ? ? ? ? // 支持設(shè)置成數(shù)組表達(dá)斥力的范圍,此時(shí)不同大小的值會(huì)線性映射到不同的斥力。值越大則斥力越大
? ? ? ? ? ? ? gravity: 0.1, // 節(jié)點(diǎn)受到的向中心的引力因子。該值越大節(jié)點(diǎn)越往中心點(diǎn)靠攏。
? ? ? ? ? ? },
? ? ? ? ? },
? ? ? ? ],
? ? ? };
? ? ? return options;
? ? }
? ? // 三組配色方案 ok
? ? // 三組間距,球大小 ok
? ? // 點(diǎn)擊后,小球居中 ok
? ? // 出現(xiàn)問(wèn)題:拖拽后小球位置自動(dòng)移動(dòng),發(fā)生偏移
? ? // filter鏈路可調(diào)
? ? // 箭頭雙向過(guò)濾
? ? // 關(guān)于顏色的使用:因?yàn)榭傤伾邢?,中心點(diǎn)設(shè)為category為1,其他的依次相加,0作為默認(rèn)顏色
? ? 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) {
? ? ? ? // 點(diǎn)擊節(jié)點(diǎn)時(shí),才會(huì)觸發(fā)繪圖
? ? ? ? if (params.dataType === "node") {
? ? ? ? ? // 設(shè)置當(dāng)前選中點(diǎn)
? ? ? ? ? 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設(shè)置是否居中,居中的話方便查看,不居中的話可能會(huì)導(dǎo)致部分節(jié)點(diǎn)不顯示?maybe
? ? ? ? const option = setConfig(config.id, position);
? ? ? ? // 居中現(xiàn)實(shí)的話setoption第二個(gè)參數(shù)為 true
? ? ? ? myChart.value.setOption(option);
? ? ? }
? ? }
? ? onMounted(() => {
? ? ? initEcharts();
? ? });
? ? return {
? ? ? drawImage,
? ? ? scatterMap,
? ? ? myChart,
? ? ? initEcharts,
? ? ? config,
? ? };
? },
});
</script>
<style scoped>
.chart-wrapper {
? width: 100%;
? height: 600px;
}
</style>
```
實(shí)現(xiàn)效果

過(guò)濾效果

通過(guò)設(shè)置節(jié)點(diǎn)的category屬性來(lái)表示不同節(jié)點(diǎn)與點(diǎn)擊處節(jié)點(diǎn)的距離,具體看代碼啦。
體驗(yàn)一下就知道效果了~