先说下本次案例业务需求, 输入 2 个节点, 获取数据后绘制出 2 个节点间的路径, 之前使用的是网状图, 但是网状图的效果不佳, 需要转换成流程图的模式:
那么如何在不修改数据的情况下, 实现类似效果尼?
看了下 echarts 的 graph 类型, 可以实现类似的, 下面是官方的实例
从实例中可以看出, 难点在于节点的显示位置 x,y 和曲线的设置. 业务数据中:
1, 节点的数量不定, 关系的数量不定,
2, 后台返回的数据只有单独的节点信息和关系信息
实现思路:
1, 分析数据, 获取前后节点关系, 获得行数位置(节点的 xIndex 信息)
在节点数组中找到开始节点并设置 xIndex 为 1, 然后从它开始找第二层的节点
- // 获取节点的 x 轴顺序
- setNodeOrder () {
- this.entityList.forEach(item => {
- if (item.id === this.startNode) {
- item.xIndex = 1;
- }
- // 设置一个对象, 记录节点信息, 用于后面查询信息, 比数组查询来的快
- this.nodesObj[item.id] = item;
- });
- this.findNodeOrder(2, this.startNode);
- }
- // 广度遍历 -- 按顺序找到第 index 层的数据, 并设置对应的参数, 再层层递进
- findNodeOrder (xIndex, sId){
- }
想一下, 如果是第二层的节点, 那么应该是关系中, 源是 startNode 或者目的节点是 startNode, 如此类推, 层层递进,
这里需要使用广度遍历而不是深度遍历, 设定 2 个数组, currentQueue 记录当前层的节点, afterQueue 记录下一层的节点, 当 currentQueue 层遍历完后, 将 afterQueue 变成 currentQueue, 继续遍历, 直至结束, 核心代码如下:
- ......
- let nextIds = [];
- this.relationList.forEach(item => {
- // 源
- if (item.source === sId) {
- if (!this.nodesObj[item.target].xIndex) {
- this.nodesObj[item.target].xIndex = xIndex;
- nextIds.push(item.target);
- }
- }
- // 目的
- if (item.target === sId) {
- if (!this.nodesObj[item.source].xIndex) {
- this.nodesObj[item.source].xIndex = xIndex;
- nextIds.push(item.source);
- }
- }
- });
- let nextIdsLen = nextIds.length;
- // 1, 没有当前的队列, 没有后续的队列, 有 nextIds
- if (
- !this.currentQueue.length &&
- !this.afterQueue.length &&
- nextIdsLen
- ) {
- this.currentQueue = nextIds;
- this.currentXindex = this.currentXindex + 1;
- this.setNextOrder();
- } else if (this.currentQueue.length && nextIdsLen) {
- // 2, 有当前的队列在遍历, 排队
- this.afterQueue = this.afterQueue.concat(nextIds);
- } else if (!this.currentQueue.length && this.afterQueue.length) {
- // 3, 没有当前的队列了, 有排队的队列, 则排队的进去
- if (nextIdsLen) {
- this.afterQueue = this.afterQueue.concat(nextIds);
- }
- this.currentQueue = JSON.parse(JSON.stringify(this.afterQueue));
- this.afterQueue = [];
- this.currentXindex = this.currentXindex + 1;
- }
setNextOrder 函数:
JS// 设置下个 setNextOrder () { while (this.currentQueue.length) { let id = this.currentQueue.shift(); this.findNodeOrder(this.currentXindex, id); } }
2, 根据行数信息设置列数的位置(节点的 yIndex 层的信息)
先排序第一层和第二层的顺序, 因为第一层就一个节点, 第二层和第一层的关系都是一样的, 所以顺序无所谓, 可以直接遇到就叠加
- // 排完了 x, 再排列 y
- // 先排第一和二行的元素的 y, 按顺序排
- let maxXindex = 1;
- let secondYIndexObj = {};
- for (let i in this.nodesObj) {
- let item = this.nodesObj[i];
- if (!item.yIndex) {
- maxXindex = item.xIndex> maxXindex ? item.xIndex : maxXindex;
- if (item.xIndex === 1) {
- item.yIndex = 1;
- this.yIndexObj[item.id] = {
- xIndex: 1,
- yIndex: 1
- };
- }
- if (item.xIndex === 2) {
- if (!secondYIndexObj[item.xIndex]) {
- item.yIndex = 1;
- // 记录当前的 y 轴上的节点个数
- secondYIndexObj[item.xIndex] = 1;
- this.yIndexObj[item.id] = {
- xIndex: 2,
- yIndex: 1
- };
- } else {
- item.yIndex = secondYIndexObj[item.xIndex] + 1;
- secondYIndexObj[item.xIndex] = item.yIndex;
- this.yIndexObj[item.id] = {
- xIndex: 2,
- yIndex: item.yIndex
- };
- }
- }
- }
- }
但是从第三层开始就要注意了, 不能再按照第二层的递增顺序的方法来控制. 因为第二层与第三层的关系和数量都不一定, 如果随机排列会导致线很乱, 最好的方法是根据第二层的顺序, 获取第三层的顺序. 即将第二层第 N 个节点有关的的节点设置在第 N 层. 这样如果第二层的数量与第三层的数量相等, 那么就完全在一条直线上了. 如果数量不等, 也不至于那么混乱~ 但是如果第三层有多个节点与第二层的同一个节点有关系, 这个时候我选择的是随机选择层级了~
- // 从第三层开始
- if (maxXindex> 2) {
- // 后面列的排序根据前一列为准(尽量保证在一条直线上)
- for (let j = 3; j <= maxXindex; j++) {
- for (let i in this.nodesObj) {
- let item = this.nodesObj[i];
- while (item.xIndex === j && !item.yIndex) {
- // 先看跟它一层的节点有多少个
- if (!this.nodeOrders[j]) {
- let totals = this.findYtotal(j);
- this.nodeOrders[j] = [];
- for (let i = 1; i <= totals; i++) {
- this.nodeOrders[j].push(i);
- }
- }
- // 找第二层中的对应的层级
- let findX = j - 1;
- // 找到所有的层级
- let findYs = this.findLinkNode(item.id, findX);
- // 找到还有的层级
- let sameArr = findYs.filter(x =>
- this.nodeOrders[j].includes(x)
- );
- let findY;
- // 找不到按顺序抽取了
- if (!sameArr.length) {
- // 只能随机选择一个了
- let ran = Math.floor(
- Math.random() * this.nodeOrders[j].length
- );
- findY = this.nodeOrders[j][ran];
- this.nodeOrders[j].splice(ran, 1);
- } else {
- findY = sameArr[0];
- // 去除该顺序
- let order;
- this.nodeOrders[j].find((num, k) => {
- if (num === findY) {
- order = k;
- return true;
- }
- });
- this.nodeOrders[j].splice(order, 1);
- }
- this.yIndexObj[item.id] = {
- xIndex: j,
- yIndex: findY
- };
- item.yIndex = findY;
- }
- }
- }
- }
3, 设置具体的位置信息
获取图表的中心位置 centerY, 将起点和终点的 y 位置放在中间, 其他的根据上面获取的 xIndex 和 yIndex 的还有根据情况设置的间距 (gapX,gapY) 及节点的大小 (size) 计算出每个节点的 x 和 y 信息
- // 设置节点的位置 x,y
- setNodesPositon () {
- for (let i in this.nodesObj) {
- let item = this.nodesObj[i];
- if (item.id === this.startNode) {
- item.y = this.centerY;
- item.x = 1;
- }
- if (!item.x) {
- item.x = this.gapX * (item.xIndex - 1) + this.size / 2;
- }
- if (!item.y && !item.total) {
- item.total = this.findYtotal(item.xIndex);
- if (item.total === 1) {
- item.y = this.centerY;
- } else {
- let middleNum = item.total / 2;
- let ceilNum = Math.ceil(middleNum);
- let topGap;
- let bottomGap;
- let ty = (ceilNum - item.yIndex) * (this.gapY + this.size);
- let by = (item.yIndex - ceilNum) * (this.gapY + this.size);
- if (item.total % 2 === 1) {
- topGap = this.centerY - ty;
- bottomGap = this.centerY + by;
- } else {
- topGap = this.centerY - (ty + this.gapY + 0.5 * this.size);
- bottomGap = this.centerY + (by - 0.5 * this.size);
- }
- if (item.yIndex <= middleNum) {
- item.y = topGap;
- } else {
- item.y = bottomGap;
- }
- // 奇数
- if (item.total % 2 === 1) {
- if (item.yIndex === ceilNum) {
- item.y = this.centerY;
- }
- }
- }
- }
- }
- }
来源: https://www.cnblogs.com/webhmy/p/11606448.html