当前位置: 首页 > news >正文

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

效果展示

项目概述

        svg flow editor 是一款流程图编辑器,提供了一系列流程图交互、编辑所必需的功能,支持前端研发自定义开发各种逻辑编排场景,如流程图、ER 图、BPMN 流程等。

        目前也有比较好的流程图设计框架,但是还是难满足项目个性化定制,BMPN.js、Jsplumb 的拓展能力不足,自定义节点支持成本很高。

技术选型

        本项目使用typescript与svg、canvas等技术进行搭建,脱离vue、react等框架的限制,使得用户更快、更轻松融合到自己的项目中,在底层结合typescript,使得数据类型得到更加健壮、完整的支持,对图形元组使用 svg 技术进行绘制,使得用户操作、底层实现更加轻松,同时对其他模块(背景网格、水印)使用了canvas技术进行绘制。

功能规划

        本项目大体功能模块如下:

background 背景

        背景模块支持网格绘制、水印的绘制、水印定制化配置等

graph 

        graph 是系统交互的核心元素,支持Rect(矩形)、Circle(圆形)、Ellipse(椭圆)、Polygon(多边形)、Diamond(菱形)、Triangle(三角形)、Text(文本)、HTML(HTML元素)、Image(图片)、Line(线)等多种类型,后期会考虑慢慢完善元件库

websocket

        websocket 是用于处理用户协同的模块

graphData

        graph Data 是双向绑定的数据管理模块

tools

        工具模块,包含图片导出、一键美化、层级处理、布局方式、元件组合、辅助线等

apis

        API 是外部访问内部实现执行动作、获取数据的窗口,并在设计上提供了command、adapt 两个类,在command中隔离内部对象,通过调用adapt实现数据的处理,放置用户通过command对象对内部对象进行风险操作

event

        提供统一的事件处理机制,支持对内部事件的监听、外部事件的注册等,同时,还对graph元件的统一事件进行处理,例如元件的点击事件、双击事件等

history

        历史记录管理模块,支持 redo undo version 等历史相关操作

项目架构

        项目对外暴露基础操作,例如: svg 构造器、command api操作、event事件中心以及全局api,通过暴露对象 sfEditor,实现对内部的数据访问、对象操作等。在核心模块中,需要考虑用户的使用习惯,封装完整的工具类,实现流程图的基本操作、拓展功能。底层依赖了svg对项目元件库的基础元件进行创作,同时使用了canvas对背景网格、水印等进行绘制,使用html进行页面布局,并且提供了typescript的全类型支持。

        在API设计的设计上,采取了Command CommandAdapt 两个类实现,Command中不进行用户方法的直接处理,增加adapt类进行方法中转,防止用户通过API直接操作核心类。Command调用 adapt 的实例方法,在adapt 中获取draw、svg 等核心类进行用户的响应。

        未来的功能模块规划中,还是以协同为核心重点。

项目结构说明

        如上图,核心类在 core 中,index.ts 向外暴露了API,main.ts 则是测试结果的入口文件,interface是类型文件,命名上基本上都是按功能模块走的。 

Graph 实体类

构建 svg 对象

export class SVG {private xmlns!: string;private svg: Element;private svgID!: string;private draw: Draw; // 绘制实例private graphOption: IGraphOption | undefined;constructor(graphOption?: IGraphOption) {this.draw = new Draw();this.svgID = getNanoid();this.graphOption = graphOption;//SVG命名空间this.xmlns = graphOption?.xmlns || "http://www.w3.org/2000/svg";// 1. 判断是否存在当前命名空间的svgconst svgElement = this.draw.getSvg(this.xmlns);// 2. 如果存在 则保存if (svgElement) throw new Error(messageInfo.isHaveSvgElement); // 如果已经存在相同xmlns属性的svg 则报错// 3. 不存在 则创建新的 svgthis.svg = this.draw.createSvg(this.xmlns, this.svgID);}// 将当前创建的svg添加到html DOM 的节点上public addTo(container: string | Element) {this.draw.addTo(container, this.svg); // 添加到指定容器this.size(); // 设置默认大小const { gridLines, waterMark, waterMarkText } = this.graphOption || {};if (gridLines !== false) this.draw.gridLines(); // 绘制网格if (waterMark !== false) this.draw.waterMark(waterMarkText); // 绘制水印return this; // 返回 this 供链式调用}// 设置当前 svg 的大小public size(width?: number, height?: number) {this.svg.setAttribute("width", width?.toString() || "100%");this.svg.setAttribute("height", height?.toString() || "100%");return this;}

        相关的draw方法:

import { messageInfo } from "../Message";// 绘制、DOM 操作的核心类 尽量将所有的DOM操作都汇集在该类中,防止多处操作DOM引起的其他问题
export class Draw {constructor() {}// 通过指定的 xmlns 获取 svgpublic getSvg(xmlns: string) {return document.querySelector(`svg[xmlns="${xmlns}"]`);}//   创建 svgpublic createSvg(xmlns: string, svgID: string) {const svg = document.createElementNS(xmlns, "svg");svg.setAttribute("ID", svgID);svg.setAttribute("xmlns", xmlns);svg.setAttribute("version", "1.1");svg.setAttribute("baseProfile", "full");return svg;}// 将创建 svg 添加到指定容器public addTo(container: string | Element, svg: Element) {const type = typeof container === "string";// 判断传入参数是选择器还是domlet dom = type ? document.querySelector(container) : container;dom?.appendChild(svg);}// 绘制网格线public gridLines() {console.log("gridLines");}// 绘制水印public waterMark(waterMarkText?: string) {const text = waterMarkText || messageInfo.waterMarkText;}// 清除网格线public clearGridLines() {}// 清除水印public clearWaterMark() {}
}

构建 Rect 类

import { Common } from "./Common";
import { SVG } from "./index";// 矩形类
export class Rect extends Common {private svg: SVG; // 根元素 svgprivate rect: Element;constructor(svg: SVG, width: number, height: number) {super();this.svg = svg;this.rect = super.getDraw().createRect(svg.getSvgXmlns());// 设置宽高this.setAttribute(width, height);// 将当前创建的元件添加到 svg 下super.addToSvg(this);}// 独有属性设置private setAttribute(width: number, height: number) {this.rect.setAttribute("width", width.toString());this.rect.setAttribute("height", height.toString());}//   获取基本Elementpublic getElement() {return this.rect;}// 获取 xmlnspublic getXmlns() {return this.svg.getSvgXmlns();}
}

抽离公共类

        svg 元件具有的公共方法,例如 设置位置信息、设置宽高、设置样式等,还有事件处理机制,都是每一个元件都拥有的方法属性,因此,抽离为独立的类,实现 元件集成即可。

// svg 元件公共类import { IGraphAttributes } from "../../interface/Graph";
import { Draw } from "../Draw";
import { Rect } from "./Rect";// 定义元件类型
type IGraph = Rect;export class Common {private draw: Draw;constructor() {this.draw = new Draw();}// 设置元件IDpublic setID() {}// 获取IDpublic getID() {const element = (this as unknown as IGraph).getElement();return this.draw.getID(element);}// 将创建的元件 添加到 svg 下protected addToSvg(graph: IGraph) {// 创建了基本元件后,需要构建 g 分组,方便处理 hover 及 click 的锚点const xmlns = graph.getXmlns();const element = graph.getElement();const nodeID = graph.getID() as string;// 1. 获取分组const group = this.draw.createGroup(element, xmlns, nodeID);// 2. 获取当前的 svg 根元素const svg = this.draw.getSvg(xmlns);// 3. 初始化默认属性this.attr.call(graph, {});// 3. 将当前分组添加到根元素上this.draw.addTo(svg as Element, group);}// 设置位置public position(x: number, y: number) {const graph = this as unknown as IGraph;const element = graph.getElement();// 因为设置位置属性的时候,不同的元素不一致,因此需要建立 原型与属性的映射const { tagName } = element;const attrMap: { [key: string]: string[] } = {rect: ["x", "y"],circle: ["cx", "cy"],ellipse: ["cx", "cy"],};element.setAttribute(attrMap[tagName][0], x.toString());element.setAttribute(attrMap[tagName][1], y.toString());// 重新渲染this.draw.updateLinkAnchorPoint(graph.getID() as string,element,graph.getXmlns());return this;}//  设置属性public attr({ stroke, fill }: IGraphAttributes) {// 设置样式const graph = this as unknown as IGraph;const element = graph.getElement();element.setAttribute("stroke", stroke || "black");element.setAttribute("fill", fill || "#F2F2F2");return this;}// 获取 draw 操作对象protected getDraw() {return this.draw;}
}

实现效果

 公共事件处理机制

  Common.ts// 为所有的子类构造事件public click!: (_fun: Function) => IGraph;public dblclick!: (_fun: Function) => IGraph;public mousedown!: (_fun: Function) => IGraph;public mousemove!: (_fun: Function) => IGraph;public mouseup!: (_fun: Function) => IGraph;public mouseover!: (_fun: Function) => IGraph;public mouseout!: (_fun: Function) => IGraph;// 初始化公共事件private initCommonEvent(graph: IGraph) {/*** 事件处理机制: 不管用户有没有添加 click ,都需要实现 addEventListener*/const eventList: IEventList = {click: (e: Event, graph: IGraph) => this.commonEvent.click(e, graph),};const element = graph.getElement();Object.keys(eventList).forEach((eventname) => {let userfun: null | Function;// @ts-ignore 用户自定义事件graph[eventname] = (_fun: Function | null) => {userfun = _fun;return graph;};// 给元素添加事件element.addEventListener(eventname, (e) => {// 1. 先执行默认事件eventList[eventname](e, graph);// 在这里处理用户自定义的事件userfun && userfun(e);// 阻止事件冒泡e.preventDefault();});});}

全局指令

// 暴露对外操作API 需要经过 Command Adapt的中转,防止用户直接通过 Command 获取到内部对象
import { Draw } from "../Draw";
import { CommandAdapt } from "./CommandAdapt";export class Command {// 测试设置水印public executeWatermark: CommandAdapt["watermark"];constructor(draw: Draw) {const adapt = new CommandAdapt(draw);this.executeWatermark = adapt.watermark.bind(adapt);}
}
import { Draw } from "../Draw";// Command Adapt API 操作核心库
export class CommandAdapt {private draw: Draw;constructor(draw: Draw) {this.draw = draw;}public watermark() {console.log("watermark");}
}

事件机制

        事件处理中主要使用event Bus 实现:

export class EventBus<EventMap> {private eventHub: Map<string, Set<Function>>constructor() {this.eventHub = new Map()}public on<K extends string & keyof EventMap>(eventName: K,callback: EventMap[K]) {if (!eventName || typeof callback !== 'function') returnconst eventSet = this.eventHub.get(eventName) || new Set()eventSet.add(callback)this.eventHub.set(eventName, eventSet)}public emit<K extends string & keyof EventMap>(eventName: K,payload?: EventMap[K] extends (payload: infer P) => void ? P : never) {if (!eventName) returnconst callBackSet = this.eventHub.get(eventName)if (!callBackSet) returnif (callBackSet.size === 1) {const callBack = [...callBackSet]return callBack[0](payload)}callBackSet.forEach(callBack => callBack(payload))}public off<K extends string & keyof EventMap>(eventName: K,callback: EventMap[K]) {if (!eventName || typeof callback !== 'function') returnconst callBackSet = this.eventHub.get(eventName)if (!callBackSet) returncallBackSet.delete(callback)}public isSubscribe<K extends string & keyof EventMap>(eventName: K): boolean {const eventSet = this.eventHub.get(eventName)return !!eventSet && eventSet.size > 0}
}

总结

        至此,整体项目的框架已经跑通了,包括API的封装(command adapt)、事件处理机制、svg元件构建,本文先处理这么多事情。

相关文章:

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

效果展示 项目概述 svg flow editor 是一款流程图编辑器&#xff0c;提供了一系列流程图交互、编辑所必需的功能&#xff0c;支持前端研发自定义开发各种逻辑编排场景&#xff0c;如流程图、ER 图、BPMN 流程等。 目前也有比较好的流程图设计框架&#xff0c;但是还是难满足项目…...

头像剪切上传

头像剪切上传 文章说明核心Api示例源码效果展示源码下载 文章说明 本文主要为了学习头像裁剪功能&#xff0c;以及熟悉canvas绘图和转文件的相关操作&#xff0c;参考教程&#xff08;Web渡一前端–图片裁剪上传原理&#xff09; 核心Api 主要就一个在canvas绘图的操作 context…...

24计算机考研调剂 | 北京信息科技大学

北京信息科技大学接收调剂研究生 考研调剂招生信息 学校:北京信息科技大学 专业:工学->控制科学与工程->控制理论与控制工程 年级:2024 招生人数:- 招生状态:正在招生中 联系方式:********* (为保护个人隐私,联系方式仅限APP查看) 补充内容 各位同学&#xff0c;…...

06 - 镜像管理

1 了解镜像 Docker镜像是一个特殊的文件系统&#xff0c;除了提供容器运行时所需的程序、库、资源、配置等文件外&#xff0c;还包含了一些为运行时准备的一些配置参数&#xff08;如匿名卷、环境变量、用户等&#xff09;。 但注意&#xff0c; 镜像不包含任何动态数据&#…...

最简单 导航栏 html css

dhl.html <!DOCTYPE html> <html><head><meta charset"utf-8"><title>导航栏</title><link type"text/css" rel"stylesheet" href"css/dhl.css"></head><div class"dhl&quo…...

PostgreSQL的学习心得和知识总结(一百三十一)|深入理解PostgreSQL数据库如何使用psql中的变量

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库链接,点击前往 4、日本著名PostgreSQL数据库专家 铃木启修 网站…...

支付宝小程序模板开发,实现代小程序备案申请

大家好&#xff0c;我是小悟 支付宝小程序备案整体流程总共分为五个环节&#xff1a;备案信息填写、平台初审、工信部短信核验、通管局审核和备案成功。 服务商可以代小程序发起备案申请。在申请小程序备案之前&#xff0c;需要确保小程序基本信息已填写完成、小程序至少存在一…...

怎么培养孩子的学习习惯?

问&#xff1a;在亲子阅读中&#xff0c;应该用哪些方法引导孩子自己主动阅读呢&#xff1f; 有很多家长会问如何培养孩子主动阅读的兴趣&#xff1f; 我想给你四个词来分享&#xff0c;分别是环境、选择的权利、适龄&#xff0c;还有增强回路。第一个环境&#xff0c;就是把…...

deeplearning with pytorch (三)

一.基本概念 1.Convolutional Neural Network Intro mnist数据集 2.Image Filter / Image Kernel Image Kernels explained visually 访问这个网站可以直观看到image kernels对图片的影响 3.Convolutional Layer and RGB 为什么要用巻积神经网络代替人工神经网络 上图是…...

LLaMA模型的发布与创新潮流

时间线从2023年2月24日开始&#xff0c;Meta推出了LLaMA模型&#xff0c;虽然开源了代码&#xff0c;却没有开源模型权重。LLaMA模型可以说是相对较小的&#xff0c;拥有7B、13B、33B和65B参数的几种版本&#xff0c;训练时间相对较长&#xff0c;因此相对于其大小而言能力强大…...

Python之Web开发初学者教程—ubuntu中安装配置redis

Python之Web开发初学者教程—ubuntu中安装配置redis 准备环境&#xff1a;VMWare&#xff0c;ubuntu18.04.6 LTS 一、安装 从 Ubuntu 存储库安装 Redis&#xff0c;打开Ubuntu的终端&#xff0c;输入下面的命令&#xff1a; sudo apt-get install redis-server &#xff08…...

如何在Vue中进行单元测试?

前端开发中&#xff0c;单元测试是一个非常重要的环节&#xff0c;它可以帮助我们在开发过程中发现潜在的问题&#xff0c;并确保我们的代码在不断迭代的过程中依然能够保持稳定。在Vue中进行单元测试同样非常重要&#xff0c;本文将介绍如何在Vue项目中进行单元测试。 在Vue中…...

开源组件安全风险及应对

在软件开发的过程中&#xff0c;为了提升开发效率、软件质量和稳定性&#xff0c;并降低开发成本&#xff0c;使用开源组件是开发人员的不二选择&#xff08;实际上&#xff0c;所有软件开发技术的演进都是为了能够更短时间、更低成本地构建软件&#xff09;。这里的开源组件指…...

nginx出现 “414 request-uri too large”

nginx出现 “414 request-uri too large” 1.修改传参方式 POST 2.字段能变成后端获取就自己获取&#xff0c;不用前端传 3.修改nginx配置&#xff0c;添加client_header_buffer_size 512k;large_client_header_buffers 4 512k;配置...

堆和二叉树的动态实现(C语言实现)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…...

Vue前端+快速入门【详解】

目录 1.Vue概述 2. 快速入门 3. Vue指令 4.表格信息案例 5. 生命周期 1.Vue概述 1.MVVM思想 原始HTMLCSSJavaScript开发存在的问题&#xff1a;操作麻烦&#xff0c;耦合性强 为了实现html标签与数据的解耦&#xff0c;前端开发中提供了MVVM思想&#xff1a;即Model-Vi…...

day06_菜单管理(查询菜单,添加菜单,添加子菜单,修改菜单,删除菜单,角色分配菜单,查询菜单,保存菜单,动态菜单)

文章目录 1 菜单管理1.1 表结构介绍1.2 查询菜单1.2.1 需求说明1.2.2 页面制作1.2.3 后端接口SysMenuSysMenuControllerSysMenuServiceMenuHelperSysMenuMapperSysMenuMapper.xml 1.2.4 前端对接sysMenu.jssysMenu.vue 1.3 添加菜单1.3.1 需求说明1.3.3 页面制作1.3.3 后端接口…...

探究与以太坊智能合约的交互

# 概述 智能合约是部署在区块链上的一串代代码&#xff0c;通常我们与智能合约的打交道 可以通过前端的Dapp&#xff0c;etherscan&#xff0c;metamask 等方式。作为开发人员可以通过调用提供的相关包来与之交互&#xff0c;如web3.js&#xff0c;ether.js , web3.j(java 语言…...

Windows如何安装docker-desktop

下载 docker-desktop设置环境安装wsl可能遇到的错误 下载 docker-desktop 下载官网&#xff1a;https://www.docker.com/products/docker-desktop/ 设置环境 如果没有Hyper-V选项的,按照以下步骤 添加一个文件Hyper-V.bat 添加以下内容,并双击运行后重启电脑 pushd "%~…...

芯片设计后端遇到的各种文件类型和文件后缀

芯片设计后端遇到的各种文件类型和文件后缀 文件类型 描述 文件后缀 netlist网表文件 verilog文件格式&#xff0c;记录了芯片里各个instance的逻辑连接关系 .v (for Verilog netlists) Lib&#xff0c;liberty timing file 记录了cell的timing信息及一定power信息。有的…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...