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

系列文章

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元件构建,本文先处理这么多事情。

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

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

相关文章

【xv6操作系统】Lab systems calls

一、实验前须知 阅读 xv6 文档的第 2 章和第 4 章的 4.3 节和 4.4 节以及相关源文件&#xff1a; 系统调用的用户空间代码在 user/user.h 和 user/usys.pl 中。 内核空间代码在 kernel/syscall.h 和 kernel/syscall.c 中。 与进程相关的代码在 kernel/proc.h 和 kernel/proc.c…

CorelDRAW Standard2024适合业余爱好者和家庭企业的图形设计软件

CorelDRAW Standard 2024是一款功能强大的矢量图形设计软件&#xff0c;专为图形爱好者、家庭用户、微型企业和学生们设计。该软件在Windows平台上运行&#xff0c;并提供了智能对象、布局、插图和模板等功能&#xff0c;帮助用户快速创建高质量的设计作品。 CorelDRAW Standa…

图机器学习(4)-面向连接层面的人工特征工程

0 问题定义 通过已经连接去猜未知连接&#xff1a; 有两个思路&#xff1a; &#xff08;1&#xff09;直接提取link的特征&#xff0c;把link变成D维向量&#xff1b; &#xff08;2&#xff09;把link两端节点的D维向量拼在一起&#xff0c;缺点&#xff1a;丢失了link本身…

【C++】手撕string类(超实用!)

前言 一、标准库中的string类 1.1 string类介绍 1.2 string的常用接口 1.2.1 常用的构造函数 1.2.2 容量操作接口 &#xff08;1&#xff09;size &#xff08;2&#xff09;capacity &#xff08;3&#xff09;empty &#xff08;4&#xff09;clear &#xff08…

MySQL 学习笔记(基础篇 Day3)

「写在前面」 本文为黑马程序员 MySQL 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. MySQL 学习笔记&#xff08;基础篇 Day1&#xff09; 2. MySQL 学习笔记&#xff08…

移掉 K 位数字(LeetCode 402)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路4.1 暴力法4.2 贪心 单调栈 参考文献 1.问题描述 给你一个以字符串表示的非负整数 num 和一个整数 k&#xff0c;移除这个数中的 k 位数字&#xff0c;使得剩下的整数最小。请你以字符串形式返回这个最小的整数。 示例 1 …

chrome插件开发的几种展现页面形式,3分钟看完

想要开发一个chrome浏览器插件&#xff0c;还是很有必要清楚插件都可以在哪些地方显示出来的&#xff0c;比如只想在pop页面弹出&#xff0c;还是添加右键菜单&#xff0c;还是提示桌面通知&#xff1f;还是在哪里展示&#xff1f;有哪些展示方式等 browserAction(浏览器右上角…

原油数据处理:1.聚类、盐含量测定与近红外光谱快速评估

一、原油种类的聚类分析 在塔里木盆地塔河油田的原油处理过程中&#xff0c;需要对原油进行地球化学特征研究&#xff0c;以了解其成因和特征。根据地球化学手段的综合研究结果&#xff0c;塔河油田奥陶系原油属于海相沉积环境&#xff0c;成熟度较高&#xff0c;正构烷烃分布…

内存映射实现父子进程通信

创建内存映射区&#xff1a; void *mmap(void *addr ,size_t length,int prot,int flags,int fd,off_t offset); 参数&#xff1a; addr 指定映射区的首地址。通常NULL&#xff0c;表示让系统自动分配length 共享内存映射区的长度prot 共享内存的读写属性 PROT_READ PR…

【记录37】VueBaiduMap 踩坑一

截图 错误 Error in callback for watcher “position.lng”: “TypeError: Cannot read properties of undefined (reading ‘setPosition’)” 解释 回调观察程序“content”时出错&#xff1a;“TypeError:无法读取未定义的属性&#xff08;读取’setContent’&#xff09;”…

数据结构:Heap(二叉树)的基本操作

目录 1.有关二叉树必须知道的几个基本概念 2.有关二叉树的基本操作 2.0有关元素的定义以及要进行的操作 2.1初始化和销毁操作 2.2插入操作以及上调操作 2.2.1插入操作以及上调操作的图解 2.2.2插入操作以及上调操作的代码 2.3删除根元素及其下调操作 2.3.2删除根元素及…

Android studio Gradle下载失败,如何手动配置解决该问题详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 前言&#xff1a; 今天在打开公司一个项目时&#xff0c;突然要重新下载相关的gradle&am…

Websocket实时音视频传输应用实战

背 景 随着互联网技术的发展&#xff0c;越来越多的企业和开发者开始寻求更高效、更稳定的通信解决方案。在这种背景下&#xff0c;WebSocket协议应运而生。WebSocket是一种在单个TCP连接上进行全双工通信的协议&#xff0c;它可以实现服务器和客户端之间的实时数据交换&#…

图像处理 mask掩膜

1&#xff0c;图像算术运算 图像的算术运算有很多种&#xff0c;比如两幅图像可以相加&#xff0c;相减&#xff0c;相乘&#xff0c;相除&#xff0c;位运算&#xff0c;平方根&#xff0c;对数&#xff0c;绝对值等&#xff1b;图像也可以放大&#xff0c;缩小&#xff0c;旋…

(二十一)从零开始搭建k8s集群——kubernates核心组件及功能介绍

前言 Kubernetes是一个可移植、可扩展、开源的平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;它促进了声明性配置和自动化。Kubernetes容器可以持续开发、集成和部署&#xff1a;可靠且频繁地构建和部署容器镜像&#xff0c;快速有效地回滚&#xff1b;开发与运…

点胶缺陷视觉检测都是怎么检测的?

点胶工艺是许多工业生产中不可或缺的一环&#xff0c;而点胶缺陷的存在往往直接影响到产品质量。为了提升点胶工艺的品质控制&#xff0c;点胶缺陷的视觉检测成为了一个重要的技术手段。 一、点胶缺陷的类型 点胶缺陷主要包括胶点大小不均、位置偏移、漏点、多点等。这些缺陷如…

IntelliJ IDEA 2020.2.4试用方法

打开idea&#xff0c;准备好ide-eval-resetter压缩包。 将准备好的压缩包拖入idea中 选中弹窗中的自动重置选项&#xff0c;并点击重置 查看免费试用时长

启动查看工具总结

启动目标&#xff1a;2s内优秀&#xff0c;2-5s普通&#xff0c;之后的都需要优化&#xff0c;热启动则是1.5s-2s内 1 看下大致串联启动流程&#xff1a; App 进程在 Fork 之后&#xff0c;需要首先执行 bindApplication Application 的环境创建好之后&#xff0c;就开始activ…

【Web前端】Vue核心基础

文章目录 1. Vue简介2. Vue官网使用指南3. 初识Vue3.1 搭建Vue开发环境3.2 HelloWorld案例3.3 el与data的两种写法3.4 MVVM模型3.5 模板语法 4. 数据绑定4.1 v-bind单向数据绑定4.2 v-model双向数据绑定 5. 事件处理5.1 v-on绑定事件5.2 事件修饰符5.3 键盘事件 6. 计算属性6.1…

typescript学习(更新中)

目录 开发环境搭建类型如何声明有哪些类型编译配置文件 开发环境搭建 npm i -g typescripttsc检查是否安装成功 类型如何声明 // 先声明再赋值 let a: number a 1// 直接赋值 let b 1function sum(a: number, b: number): number {return a b } console.log(sum(1, 2))有…