前端绘制流程节点数据

news2024/11/25 7:42:27

根据数据结构和节点的层级、子节点id,前端自己绘制节点位置和关联关系、指向、已完成节点等
在这里插入图片描述

<template>
  <div>
    <div>通过后端节点和层级,绘制出节点以及关联关系等</div>
    <div class="container" ref="container">
      <div v-for="(item, index) in nodeList" :key="index" class="point"
        :style="{ position: 'absolute', top: item.y + 'px', left: item.x + 'px', }">{{ item.name }}-{{ item.isDone }}
      </div>
    </div>
  </div>
</template>

<script>

export default {
  data() {
    return {
      // 节点数据 level代表层级 lastIds代表与其关联的下一级节点的id
      nodeList: [
        { name: '点1', id: 1, level: 1, lastIds: [2, 3] },
        { name: '点2', id: 2, level: 2, lastIds: [4] },
        { name: '点3', id: 3, level: 2, lastIds: [5, 10, 6] },
        { name: '点4', id: 4, level: 3, lastIds: [7] },
        { name: '点5', id: 5, level: 3, lastIds: [7] },
        { name: '点10', id: 10, level: 3, lastIds: [9] },
        { name: '点6', id: 6, level: 3, lastIds: [8] },
        { name: '点7', id: 7, level: 4, lastIds: [9] },
        { name: '点8', id: 8, level: 4, lastIds: [9] },
        { name: '点9', id: 9, level: 5, lastIds: [] },
      ],
      lineList: [], // 两两连接线关系的数据项
      pathsList: [], // 首位相连的完整链路
      num: 7, // 当前节点
      idsSet: [], //当前节点及已路过的节点集合
    };
  },
  mounted() {
    const container = document.getElementsByClassName('container')[0]

    // 计算定位节点
    const addCoordinates = (nodes, width) => {
      // 按 level 分组节点
      const groupedNodes = nodes.reduce((acc, node) => {
        if (!acc[node.level]) {
          acc[node.level] = [];
        }
        acc[node.level].push(node);
        return acc;
      }, {});

      // 处理每个 level 的节点
      Object.keys(groupedNodes).forEach(level => {
        const group = groupedNodes[level];
        const count = group.length;
        const spacing = width / (count + 1); // 间距

        group.forEach((node, index) => {
          node.x = spacing * (index + 1);
          node.y = node.level * 66;
        });
      });

      // 返回处理后的节点数组
      return nodes;
    };

    // 查找存在连接关系的项,并将信息存入 lineList 数组
    const findConnectedNodes = (nodes) => {
      const lineList = [];

      nodes.forEach(node => {
        node.lastIds.forEach(lastId => {
          // 根据 id 找到相应的节点
          const connectedNode = nodes.find(item => item.id === lastId);
          // 将节点信息存入 lineList 数组,确保起点是当前节点,终点是连接的节点
          if (connectedNode) {
            lineList.push({
              x1: node.x,
              y1: node.y,
              x2: connectedNode.x,
              y2: connectedNode.y,
              lineAB: [node.id, connectedNode.id]
            });
          }
        });
      });

      return lineList;
    };




    this.nodeList = addCoordinates(this.nodeList, 600);
    console.log('nodeList 节点定位', this.nodeList);

    // 查找存在连接关系的项
    this.lineList = findConnectedNodes(this.nodeList);
    console.log('lineList 两两关联', this.lineList);


    // 绘制线段
    const drawLine = (x1, y1, x2, y2, isDone) => {
      // console.log(x1, y1, x2, y2, isDone);

      const length = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
      const angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI);

      const line = document.createElement('div');
      line.className = 'line';
      line.style.width = `${length}px`;
      line.style.transform = `rotate(${angle}deg)`;
      line.style.transformOrigin = '0 0';
      line.style.position = 'absolute';
      line.style.top = `${y1}px`;
      line.style.left = `${x1}px`;
      line.style.backgroundColor = 'black';
      line.style.height = '2px';
      line.style.backgroundColor = isDone ? '#1fff' : 'black';


      // 创建箭头
      const arrow = document.createElement('div');
      arrow.className = 'arrow';
      arrow.style.position = 'absolute';
      arrow.style.width = '0';
      arrow.style.height = '0';
      arrow.style.borderLeft = '5px solid transparent';
      arrow.style.borderRight = '5px solid transparent';
      arrow.style.borderTop = '15px solid black';
      arrow.style.borderTopColor = isDone ? '#1fff' : 'black';

      // 计算箭头位置
      arrow.style.top = `${y2 - 10}px`;
      arrow.style.left = `${x2 - 6}px`;
      arrow.style.transform = `rotate(${angle + 270}deg)`;
      arrow.style.transformOrigin = 'center center';


      if (container) {
        container.appendChild(line);
        container.appendChild(arrow);
      }
    };




    // 找到完整链路
    const getPath = (arr) => {
      let pathsList = [];

      // 构建图的邻接表表示
      let graph = {};
      arr.forEach(({ lineAB: [start, end] }) => {
        if (!graph[start]) {
          graph[start] = [];
        }
        graph[start].push(end);
      });


      // 深度优先搜索函数
      function dfs(node, path) {
        path.push(node);
        if (!graph[node] || graph[node].length === 0) {
          pathsList.push([...path]);
        } else {
          for (let neighbor of graph[node]) {
            dfs(neighbor, path);
          }
        }
        path.pop();
      }

      // 找到所有的起点(即那些不作为任何其他点的终点的点)
      let allPoints = new Set(arr.flatMap(({ lineAB }) => lineAB));
      let endPoints = new Set(arr.map(({ lineAB: [, end] }) => end));
      let startPoints = [...allPoints].filter(point => !endPoints.has(point));

      // 从每个起点开始搜索完整路径
      startPoints.forEach(start => {
        dfs(start, []);
      });

      return pathsList
    }

    this.pathsList = getPath(this.lineList)
    console.log('pathsList完整链路', this.pathsList);


    let that = this
    function updateNodeListWithDoneStatus(nodeList, pathsList, num) {
      let idsSet = new Set();

      // 找到所有包含 num 的路径,并提取 num 之前的节点
      pathsList.forEach(path => {
        let index = path.indexOf(num);
        if (index !== -1) {
          for (let i = 0; i <= index; i++) {
            idsSet.add(path[i]);
          }
        }
      });

      idsSet.forEach(val => {
        that.idsSet.push(val)
      });
      console.log('当前及链路上的节点', that.idsSet);

      // 更新 nodeList,添加 isDone 属性
      nodeList.forEach(node => {
        if (idsSet.has(node.id)) {
          node.isDone = true;
        } else {
          node.isDone = false;
        }
      });

      // 更新 lineList 添加 isDone 属性
      that.lineList.forEach(line => {
        // 如果 lineAB 中的任意一个节点在 idsSet 中,则标记为已完成
        // line.isDone = idsSet.has(line.lineAB[0]) && idsSet.has(line.lineAB[1]);
        line.isDone = line.lineAB.every(item => that.idsSet.includes(item))
      });

      return nodeList;
    }

    // 示例:查找当前几点之前的链路ids集合并更新nodeList
    let updatedNodeList = updateNodeListWithDoneStatus(this.nodeList, this.pathsList, this.num);

    this.nodeList = updatedNodeList

    // 遍历绘制线段
    for (let index = 0; index < this.lineList.length; index++) {
      let element = this.lineList[index]
      setTimeout(() => {
        drawLine(element.x1, element.y1, element.x2, element.y2, element.isDone)
      }, 110);
    }


    this.$forceUpdate()

    console.log('最终节点数据', this.nodeList);
    console.log('最终两两连接线关系的数据', this.lineList);

  },

};
</script>

<style lang="less" scoped>
.container {
  position: relative;
  width: 600px;
  height: 600px;
  border: 1px solid #000;
}

.point {
  background-color: red;
}

.line {
  background-color: black;
  height: 2px;
  position: absolute;
  pointer-events: none; // 防止影响鼠标事件
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1685737.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6.2 else if语句

本节必须掌握的知识点&#xff1a; 示例代码二十 代码分析 汇编解析 ■if语句表达形式3 if(表达式1) statement1 else if(表达式2) statement2 else if(表达式3) statement3 …… else statementN 解析&#xff1a; 如果表达式1非0&#xff0c;则执行statement1&#…

C++ const_cast学习

语法&#xff0c; const_cast<type_name>(expression) type_name是转换的类型&#xff0c;expression是被转换的对象或者表达式&#xff1b; const_case有两个功能&#xff0c;分别是去掉const和加上const&#xff0c;更多用于去掉const&#xff0c;修改被const修…

如何使用Android NDK将头像变成“遗像”

看完本文的标题&#xff0c;可能有人要打我。你说黑白的老照片不好吗&#xff1f;非要说什么遗像&#xff0c;我现在就把你变成遗像&#xff01;好了&#xff0c;言归正传。我想大部分人都用过美颜相机或者剪映等软件吧&#xff0c;它们的滤镜功能是如何实现的&#xff0c;有人…

乡村振兴的乡村旅游新模式:挖掘乡村旅游资源,创新旅游开发方式,打造乡村旅游新品牌,助力美丽乡村建设

目录 一、引言 二、乡村旅游资源挖掘 1、自然景观资源 2、人文历史资源 3、农业产业资源 三、旅游开发方式创新 1、多元化旅游产品 2、体验式旅游模式 3、智慧旅游建设 四、乡村旅游新品牌打造 1、品牌定位与策划 2、品牌传播与推广 3、品牌维护与提升 五、助力美…

如何使用Suno:免费的AI歌曲生成器

文章目录 Suno AI 是什么&#xff1f;Suno AI 如何工作&#xff1f;选择Suno AI的理由&#xff1a;核心优势易于操作多样化创作灵活的定价策略版权保障技术突破 如何使用Suno AI创作歌曲&#xff1f;第1步&#xff1a;注册Suno AI账户第2步&#xff1a;输入提示词创建第 3 步&a…

基于51单片机智能大棚浇花花盆浇水灌溉补光散热设计

一.硬件方案 本设计通过光敏电阻检测光照强度&#xff0c;然后A/D模块PCF8591处理后&#xff0c;将光照强度值实时显示在液晶上&#xff0c;并且可以按键控制光照的强度值&#xff0c;当光照低于设定的阈值&#xff0c;1颗白色高亮LED灯亮进行补光&#xff0c;光照高于设定的阈…

QT C++ QTableWidget 演示

本文演示了 QTableWidget的初始化以及单元格值改变时响应槽函数&#xff0c;打印单元格。 并且&#xff0c;最后列不一样,是combobox &#xff0c;此列的槽函数用lambda函数。 在QT6.2.4 MSVC2019 调试通过。 1.界面效果 2.头文件 #ifndef MAINWINDOW_H #define MAINWINDOW…

HIOKI日置测试仪SS7081-50

HIOKI日置测试仪SS7081-50 HIOKI日置测试仪SS7081-50 HIOKI日置测试仪SS7081-50 扭力测试仪补偿功能* 扭矩计的测量误差、会给马达的分析带来很大的影响。PW8001 可用户定义「非直线型补偿」和「摩擦补偿」&#xff0c;并可根据补偿数据进行演算。 传统的高效电机评估系统通…

[机缘参悟-185] - 《道家-水木然人间清醒1》读书笔记 - 真相本质 -8- 认知觉醒 - 逻辑谬误、认知偏差:幸存者偏差

目录 前言&#xff1a; 一、幸存者偏差 二、幸存者偏差在现实中的应用 第一个故事&#xff1a; 第二个故事&#xff1a; 三、生活中的幸存者偏差 四、迷恋成功者经验的原因&#xff1a;鸡汤、幻想、传奇、希望 备注&#xff1a; 前言&#xff1a; 幸存者偏差&#xff0…

关于学习Go语言的并发编程

开始之前&#xff0c;介绍一下​最近很火的开源技术&#xff0c;低代码。 作为一种软件开发技术逐渐进入了人们的视角里&#xff0c;它利用自身独特的优势占领市场一角——让使用者可以通过可视化的方式&#xff0c;以更少的编码&#xff0c;更快速地构建和交付应用软件&#…

Node.js下载安装教程及环境配置【超详细图文】

一、下载安装包 下载安装Node.js安装程序&#xff0c;网盘资源下载地址&#xff1a; 点击这里下载 二、开始安装 双击下载 .msi安装程序&#xff0c;接下里只需要点击默认下一步即可。 详细如图&#xff1a; 下一步 修改安装盘符&#xff0c;只要不在C盘即可。 此处选…

Simplicity Studui V5 新安装后无法Product Updates

之前&#xff08;2021年&#xff09;在SiliconLabs官网下载了SSV5&#xff0c;安装包我也保存在硬盘了&#xff0c;最近换了台电脑安装SSV5后安装 SDK之前必须Product Updates&#xff0c;但死活安装不上&#xff0c;老是提示发生了错误。来来回回卸载安装几十遍&#xff0c;后…

先进电气技术 —— 控制理论中的“观测器”概述

一、背景 观测器在现代控制理论中的地位十分重要&#xff0c;它是实现系统状态估计的关键工具。观测器的发展历程可以从以下几个方面概述&#xff1a; 1. 起源与发展背景&#xff1a; 观测器的概念源于对系统状态信息的需求&#xff0c;特别是在只能获取部分或间接输出信息…

上位机图像处理和嵌入式模块部署(mcu的按键输入)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 做技术的同学&#xff0c;大部分都会把精力放在技术本身&#xff0c;却忽视了学的东西有什么实际的用途。就拿gpio来说&#xff0c;一般我们点灯也…

LLM答案抽取|xFinder:针对大型语言模型的稳健且精确的答案提取

【摘要】大型语言模型&#xff08;LLM&#xff09;的不断进步使人们越来越关注开发公平可靠的方法来评估其性能的关键问题。特别是测试集泄漏、提示格式过拟合等主观或非主观作弊现象的出现&#xff0c;给法学硕士的可靠评估带来了重大挑战。由于评估框架通常利用正则表达式 (R…

用神经网络预测三角形的面积

周末遛狗时&#xff0c;我想起一个老问题&#xff1a;神经网络能预测三角形的面积吗&#xff1f; 神经网络非常擅长分类&#xff0c;例如根据花瓣长度和宽度以及萼片长度和宽度预测鸢尾花的种类&#xff08;setosa、versicolor 或 virginica&#xff09;。神经网络还擅长一些回…

SQL注入:pikachu靶场中的SQL注入通关

目录 1、数字型注入&#xff08;post&#xff09; 2、字符型注入&#xff08;get&#xff09; 3、搜索型注入 4、XX型注入 5、"insert/update"注入 Insert&#xff1a; update&#xff1a; 6、"delete"注入 7、"http header"注入 8、盲…

在kaggle中的notebook 如何自定义 cuda 版本以及如何使用自定义的conda或python版本运行项目(一)

问题 第一部分 当前kaggle中带有gpu的notebook 默认的cuda 是12.1版本&#xff0c;如果我要跑一个项目是11.3的&#xff0c;如何将默认的cuda 改为自己需要的cuda 11.3 方法 step1 从官网下载需要的版本cuda run 文件&#xff08;如cuda 11.3&#xff09; 在nvidia cuda 下…

小程序丨数据功能如何使用

查询发布完成后&#xff0c;如发现信息有误或想要修改信息&#xff0c;老师可以使用数据功能在线修改已发布的查询内容。 数据功能包含导出、添加、编辑、更多操作&#xff0c;下面来教大家如何使用吧。 &#x1f4cc;使用教程 数据功能主要用于在线修改已发布的查询内容&#…

深入探索Kafka:了解其不可或缺的核心组件

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《数据流专家&#xff1a;Kafka探索》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Kafka简介 2、Kafka的应用场景 3、Kafka与其他消…