Svg Flow Editor 原生svg流程图编辑器(四)

news/2024/4/28 0:08:03/文章来源:https://blog.csdn.net/weixin_47746452/article/details/136950639

系列文章

Svg Flow Editor 原生svg流程图编辑器(一)

Svg Flow Editor 原生svg流程图编辑器(二)

Svg Flow Editor 原生svg流程图编辑器(三)

Svg Flow Editor 原生svg流程图编辑器(四)

实现Echart统计图

        统计图的底层我们采用apache echarts 实现【Apache ECharts】,先封装GEchart,GEchart是我们的外层框架,支持形变、旋转,与Rect等svg元件类似的结构,但是内部是div实现:

        同时,样式设置上与svg还是有些不同,需要单独处理下。

/*** 需要向外暴露 setOption 方法,供数据变化后重新渲染* @param option*/private setOption() {if (!this.option) throw new Error(messageInfo.optionError);this.myChart.setOption(this.option);return this;}/*** 向外提供 update 方法,供用户在 option 变化后更新页面内容*  因 option 是引用地址,因此 可以不需要传递参数,从而实现数据更新* @returns*/public update() {return this.setOption();}

        事件上,则是核心的 setOption 与 update 两个方法,update则是向外提供给用户更新时使用。同时,缩放会导致父节点尺寸变化,因此还需要监听尺寸变化实现动态Echart重绘,使用第三方库实现此功能【也可以使用 const ob = new ResizeObserver() 这个原生API实现哈,看自己的需求】:

import elementResizeDetectorMaker from "element-resize-detector";
var erd = elementResizeDetectorMaker();// 监听元素尺寸变化,重新渲染echart 使得宽高自适应
erd.listenTo(this.div, () => this.myChart.resize());

        还需要封装一层插件,因为GEchart是核心类,不能直接提供给用户,也不便于结构管理:

// echarts 插件 多一层的原因是构建新的实例
export class SEchart {private draw: Draw;constructor(draw: Draw) {this.draw = draw;}/*** 初始化 Echart* @param option* @returns*/public init(option: object) {return new GEchart(this.draw, option);}
}
// 关键!需要注册插件,提供 echart 绘制能力  
const echart = editor.plugin("echart");// 初始化echart
const line = echart?.init(option); // 模拟数据更新
setTimeout(() => {data[0] = "123123mode";line?.update();
}, 1000);

        注意哈,class GEchart extends GraphCommon, GEchart 类继承了 Common 类,拥有元件的所有属性方法,包括 setWidth position 等;而 SEchart 则是隔离用户触碰核心类,同时也给用户注册多实例提供可能,也是对插件化提供支持。

        Echart 的事件处理,则是基于EventBus 实现实例的 emit on 操作:

this.event = new EventBus();// Echarts click
this.myChart.on("click", (params: object) =>this.event.emit("click", params)
);// Echarts 鼠标移出
this.myChart.on("mouseout", (params: object) =>this.event.emit("mouseout", params)
);// Echarts 鼠标移入
this.myChart.on("mouseover", (params: object) =>this.event.emit("mouseover", params)
);const echart = editor.plugin('echart')const bar=echart.init(barOption)bar.event.on('click',p=>{// p 是回调的参数
})

直角折线

        以下实现思路参考logicFlow直角折线思想,连接锚点mousedown事件中,我们要记录当前锚点的位置信息:

    // 1. 获取当前元素的宽高位置信息const width = graph.getWidth();const height = graph.getHeight();const x = graph.getX();const y = graph.getY();// 2. 需要计算起点位置 -- 锚点位置受padding影响const typeMap: { [key: string]: { sx: number; sy: number } } = {"0": { sx: x, sy: y + height / 2 },"1": { sx: x + width / 2, sy: y },"2": { sx: x + width, sy: y + height / 2 },"3": { sx: x + width / 2, sy: y + height },};const sx = typeMap[type].sx + 10;const sy = typeMap[type].sy + 10;

        使用editorBox来接收mousemove事件以实现流程的拖动,但是在拖动过程中,可能会经过多个元素,导致 offset 值得变化,需要做优化:

/*** 需要做位置矫正*  class='sf-editor-box-graphs' 正确*  class='sf-editor-box-graphs-main' 异常 偏移量加 offsetLeft offsetTop 值*  class='' 异常
*/

 

         绘制结束后,需要根据logicFlow的思想,构建出下面这个图形:

        实现的思路是根据线的起点、终点关联的元件计算得出:

实现关键代码:

 console.log("### 绘制最终折线,根据框的宽高位置信息获取基础数据");const eid = this.line.getAttribute("eid") as string;const sid = this.line.getAttribute("sid") as string;if (!eid) return this.lineBox.remove();// 不然处理折线的寻径算法this.line.setAttribute("stroke-dasharray", "");// 1. 获取 sid eid 构建 graphconst Sgraph = new Graph(this.draw, sid);const Egraph = new Graph(this.draw, eid);// 2. 获取start的宽高 位置信息const sx = Sgraph.getX();const sy = Sgraph.getY();// 3. 获取 end 的宽高 位置信息const ex = Egraph.getX();const ey = Egraph.getY();// 4. 需要知道哪个元件在最后 也就是 graph x 最大const maxGrapg = sx > ex ? Sgraph : Egraph;// 5. 构建 OFFSET 的矩形 --- 受padding的影响const lx = Math.min(sx, ex) - OFFSET + 10;const ly = Math.min(sy, ey) - OFFSET + 10;this.setX(lx);this.setY(ly);this.lineBox.style.backgroundColor = "rgba(0,0,0,0.1)";// 6. 取消直线this.line.setAttribute("points", "");// 7. 设置宽高const mw = maxGrapg.getWidth();const mh = maxGrapg.getHeight();const mx = maxGrapg.getX();const my = maxGrapg.getY();// 自此,整个线的宽高= lx -> mx + mw + OFFSETconst lw = mx - lx + mw + OFFSET + 10;const lh = my - ly + mh + OFFSET + 10;this.setWidth(lw);this.setHeight(lh);

         根据矩形框,找出所有可能路径关键点:

    // 做点的纠正-因为 计算得到的是 基于背景的 而线的绘制基于新的 div 坐标,需要做处理 【并且受 padding 的影响】const getX = (x: number) => x - lx + 10;const getY = (y: number) => y - ly + 10;// 起点const startType = typeMap[st]({ x: sx, y: sy, w: sw, h: sh });const startPoint = { x: getX(startType.x), y: getY(startType.y) };points.push(startPoint);// 终点const endType = typeMap[et]({ x: ex, y: ey, w: ew, h: eh });const endPoint = { x: getX(endType.x), y: getY(endType.y) };points.push(endPoint);// 如果存在间隙,则取偏移量的点,如果不存在间隙,则不取偏移量的点const intervalX = maxGrapg.getX() - minGraph.getX() + minGraph.getWidth();const intervalY = maxGrapg.getY() - minGraph.getY() + minGraph.getHeight();if (intervalX > OFFSET && intervalY > OFFSET) {// 取偏移量const startOffsetPoint = { x: getX(startType.ox), y: getY(startType.oy) };points.push(startOffsetPoint);const endOffsetPoint = { x: getX(endType.ox), y: getY(endType.oy) };points.push(endOffsetPoint);}

         轴线与边界的交点:

        A* 算法实现:

const list: expandP[] = JSON.parse(JSON.stringify(points)); // 深拷贝简单实现var optimal: expandP[] = []; // 记录最优解// 计算当前点的最短路径const computedDistance = (p: expandP) => {console.group("开始 A* 算法");console.log("当前点", p);// 获取list中 x、y 相同的点,并计算最短路径const ps = list.filter((i: expandP) => i.x === p.x || i.y === p.y);// 循环当前可达的点,并计算距离ps.forEach((i: expandP) => {i.d = Infinity; // 默认无穷大// 并且计算传入的点与当前点的向量是否穿过矩形// 计算曼哈顿距离i.d = Math.abs(i.x - endPoint.x) + Math.abs(i.y - endPoint.y);});ps.sort((a, b) => (a.d as number) - (b.d as number));console.log("当前可达的点", ps);this.drawPoint(ps[0], "green");console.groupEnd();return ps[0];};/*** 【开始寻径算法】*  1. 找到当前点的 x y 相同的点作为可达点,*  2. 并且规定,当前点的可达距离为1,取到终点的曼哈顿距离*  3. 还需要判断当前两点的向量是否穿过矩形*/const search = () => {const point = optimal.length ? optimal[optimal.length - 1] : startPoint; // 当前的最优解if (point.x === endPoint.x && point.y === endPoint.y)return console.log("A* 算法结束,最优路径为 => ", optimal);const optimalPoint = computedDistance(point);optimal.push(optimalPoint);};

        这样会穿过元件,不符合,因此需要使用处理计算,判断是否穿过节点本身:

  // 检查两个点组成的线段是否穿过起终点元素private checkLineThroughElements(p1: p, p2: p) {let rects = [this.Sgraph, this.Egraph];let minX = Math.min(p1.x, p2.x);let maxX = Math.max(p1.x, p2.x);let minY = Math.min(p1.y, p2.y);let maxY = Math.max(p1.y, p2.y);// 水平线if (p1.y === p2.y) {for (let i = 0; i < rects.length; i++) {let rect = rects[i];if (minY > rect.getY() - this.ly &&minY < rect.getY() + rect.getHeight() - this.ly &&minX < rect.getX() + rect.getWidth() - this.lx &&maxX > rect.getX() - this.lx) {return true;}}} else if (p1.x === p2.x) {// 垂直线for (let i = 0; i < rects.length; i++) {let rect = rects[i];if (minX > rect.getX() - this.lx &&minX < rect.getX() + rect.getWidth() - this.lx &&minY < rect.getY() + rect.getHeight() - this.ly &&maxY > rect.getY() - this.ly) {return true;}}}return false;}

        推荐大家看一下该博客,我也借鉴了些思路,在点的分析,定位,算法的实现上,都看了他的代码,但是还有些部分需要根据项目实际进行优化的点。关联线探究,如何连接流程图的两个节点

        最终实现效果:

        这部分应该是最难的了,目前实现起来,在临界值的处理上,还是有些问题,包括距离相近时,和优化线的方向问题,还没有做兼容处理。

发布NPM

        包的发布过程就不细说了,大家可以参考网上的教程,如果 npm login 报错,基本上切换淘宝镜像就可以解决问题了。目前经过 mvp 版本的升级迭代,基本具备流程图的绘制、交互功能,我们发布 1.0 版本,主要是测试应用功能模块是否正常。【worker 路径存在问题,目前版本使用同步实现,后续优化】

        当你升级版本的时候报错,一定需要全部提交代码,才可以进行升级:

         版本升级成功,可以在 npm 官网进行查看:

        新建空项目,进行测试:

 

        使用过程中,发现有些静态资源请求地址有问题,我们采取base64编码的方式处理,不走请求,即可解决该问题,整体效果如下:

总结

        目前已经基本实现流程图的图形绘制、自定义icon 、文本输入,剩余的优化问题包括:

1. 折线算法优化;

2. 元件库拓展;

3. 顶部菜单栏及相应 command API开发;

4. 快捷键的完善与相应功能实现。

        大家可以下载包试试,有啥问题随时反馈改进哦。

npm i svg-flow-editor-mvp

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

边缘计算与云计算总结

一. EdgeGallery 简介 MEC场景下的EdgeGallery是让资源边缘化&#xff0c;实时完成移动网络边缘的业务处理&#xff0c;MEC场景下的EdgeGallery让开发者能更便捷地使用 5G 网络能力&#xff0c;让5G能力在边缘触手可及。 EdgeGallery是由华为、信通院、中国移动、中国联通、…

TheMoon 恶意软件短时间感染 6,000 台华硕路由器以获取代理服务

文章目录 针对华硕路由器Faceless代理服务预防措施 一种名为"TheMoon"的新变种恶意软件僵尸网络已经被发现正在侵入全球88个国家数千台过时的小型办公室与家庭办公室(SOHO)路由器以及物联网设备。 "TheMoon"与“Faceless”代理服务有关联&#xff0c;该服务…

Linux内核之最核心数据结构之一:struct file(三十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

ubuntu编译OpenCV and seetaFace2

opencv opencv-4.5.2 opencv_contrib-4.5.2 SeetaFace2 SeetaFace2-master https://github.com/seetafaceengine 指定安装目录&#xff0c;和OpenCV放一个目录下了 安装前 安装 安装后 Qt安装 Windows下 Linux下 报错1 原因&#xff1a; 报错…

Linux指令大全,第二篇(超详细版)

目录 9.1 链接文件的命令 10.1. 用户和权限 11.1 文件压缩和解压命令 12.1 文件操作命令补充 13.1 Vim编辑器的使用 14.1 Linux开关机命令 15.1 Shell Linux 程序设计&#xff08;未完持续更新~&#xff09; 16.1 以上运行效果图如下 9.1 链接文件的命令 9.1.1 …

ChatGPT与传统搜索引擎的区别:智能对话与关键词匹配的差异

引言 随着互联网的快速发展&#xff0c;信息的获取变得比以往任何时候都更加便捷。在数字化时代&#xff0c;人们对于获取准确、及时信息的需求愈发迫切。传统搜索引擎通过关键词匹配的方式为用户提供了大量的信息&#xff0c;然而&#xff0c;这种机械式的检索方式有时候并不…

鱼眼相机的测距流程及误差分析[像素坐标系到空间一点以及测距和误差分析]

由于最近在整理单目测距的内容&#xff0c;顺手也总结下鱼眼相机的测距流程和误差分析&#xff0c;如果有错误&#xff0c;还请不吝赐教。 参考链接: 鱼眼镜头的成像原理到畸变矫正&#xff08;完整版&#xff09; 相机模型总结&#xff08;针孔、鱼眼、全景&#xff09; 三维…

机器人是怎么计时的(通用定时器 - 时基单元)

目录 一&#xff0c;引言 二&#xff0c;机器人的“大脑” 三&#xff0c;时基单元介绍 1&#xff0c;定时器框图 2&#xff0c;时基单元 &#xff08;1&#xff09;预分频器 &#xff08;2&#xff09;CNT计数器 &#xff08;3&#xff09;自动重装载寄存器 四&#…

智慧管道物联网远程监控解决方案

智慧管道物联网远程监控解决方案 智慧管道物联网远程监控解决方案是近年来在智能化城市建设和工业4.0背景下&#xff0c;针对各类管道网络进行高效、安全、精准管理的前沿科技应用。它融合了物联网技术、大数据分析、云计算以及人工智能等多种先进技术手段&#xff0c;实现对管…

【MATLAB源码-第171期】基于matlab的布谷鸟优化算法(COA)无人机三维路径规划,输出做短路径图和适应度曲线

操作环境&#xff1a; MATLAB 2022a 1、算法描述 布谷鸟优化算法&#xff08;Cuckoo Optimization Algorithm, COA&#xff09;是一种启发式搜索算法&#xff0c;其设计灵感源自于布谷鸟的独特生活习性&#xff0c;尤其是它们的寄生繁殖行为。该算法通过模拟布谷鸟在自然界中…

Java Spring创建bean流程及自定义构造方法示例

一、spring容器创建bean的简单步骤 1、扫描被Service&#xff0c;Component等注解标识的类。 2、找到类的构造方法 默认使用无参构造方法构建bean&#xff0c;如果类中定义了有参构造方法则会按照有参构造方法构建bean。 3、依赖注入 如果类中存在Autowired或有参的构造方法&am…

如何使用PHP和RabbitMQ实现延迟队列(方式二)?

前言 前几天写了一篇关于PHP和RabbitMQ如何通过插件实现延迟队列的功能。 今天写另外一篇不需要插件的方式&#xff0c;使用RabbitMQ的死信队列&#xff08;Dead-Letter-Exchanges, DLX&#xff09;和消息TTL&#xff08;Time-To-Live&#xff09;。 这种方法涉及到设置消息…

记一次 .NET某防伪验证系统 崩溃分析

一&#xff1a;背景 1. 讲故事 昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障&#xff0c;因为看小伙子昨天在群里问了一天也没搞定&#xff0c;干脆自己亲自上阵吧&#xff0c;抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式&#xff0c;省去了很多沟通…

[flink] flink macm1pro 快速使用从零到一

文章目录 快速使用 快速使用 打开 https://flink.apache.org/downloads/ 下载 flink 因为书籍介绍的是 1.12版本的&#xff0c;为避免不必要的问题&#xff0c;下载相同版本 解压 tar -xzvf flink-1.11.2-bin-scala_2.11.tgz启动 flink ./bin/start-cluster.sh打开 flink web…

RTOS线程切换的过程和原理

0 前言 RTOS中最重要的一个概念就是线程&#xff0c;线程的按需切换能够满足RTOS的实时性要求&#xff0c;同时能将复杂的需求分解成一个个线程执行减轻我们开发负担。 本文从栈的角度出发&#xff0c;详细介绍RTOS线程切换的过程和原理。 注&#xff1a;本文参考的RTOS是RT-T…

<QT基础(5)>事件监听

事件监听 事件监听&#xff08;Event Handling&#xff09;是在程序中监视和响应发生的事件的一种机制。在Qt中&#xff0c;事件监听是一种常见的用于处理用户输入、系统事件以及其他类型事件的方法。通过事件监听&#xff0c;您可以在发生特定事件时捕获事件并执行相应的操作…

【AI】在本地 Docker 环境中搭建使用 Hugging Face 托管的 Llama 模型

目录 Hugging Face 和 LLMs 简介利用 Docker 进行 ML格式的类型请求 Llama 模型访问创建 Hugging Face 令牌设置 Docker 环境快速演示访问页面入门克隆项目构建镜像运行容器结论推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战Hugging Fa…

7、鸿蒙学习-共享包概述

HarmonyOS提供了两种共享包&#xff0c;HAR&#xff08;Harmony Archive&#xff09;静态共享包&#xff0c;和HSP&#xff08;Harmony Shared Package&#xff09;动态共享包。 HAR与HSR都是为了实现代码和资源的共享&#xff0c;都可以包含代码、C库、资源和配置文件&#xf…

iPhone用GPT替代Siri

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 前一段时间&#xff0c;因为iCloud协议的更新&#xff0c;我的云盘空间无法正常…

RISC-V特权架构 - 中断定义

RISC-V特权架构 - 中断定义 1 中断类型1.1 外部中断1.2 计时器中断1.3 软件中断1.4 调试中断 2 中断屏蔽3 中断等待4 中断优先级与仲裁5 中断嵌套6 异常相关寄存器 本文属于《 RISC-V指令集基础系列教程》之一&#xff0c;欢迎查看其它文章。 1 中断类型 RISC-V 架构定义的中…