摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。

前言

相信大家在职场中经常会用到流程图,在互联网行业,绘制流程图不论在产品的设计阶段,还是后期优化业务流程的阶段,都有着巨大的价值。事实上,不仅是互联网行业,流程图其实广泛应用于各行各业。

比如说,银行在办理开户业务时,会有一套较为复杂的流程,中间不仅有固定的步骤,如审核材料、打印凭证等,还会有一些判断条件,如金额是否超过20万、是否需凭密码支取等,这个时候可以通过一张流程图来清晰明了地展现整个业务流:

再例如在进行新员工培训时,可能有多个部门之间的协作,那么用流程图也可以很好地帮助员工了解后续的培训步骤:

再举个例子,在互联网行业中,一个项目上线前需要进行完整的测试,测试分为很多类型,如黑盒、白盒、冒烟测试等,按照步骤进行,还要生成对应的测试报告。如果其中某一步测试不通过,需要通知研发修改bug,并重新测试,下面的流程图就可以非常清晰地展现研发、产品和测试相互合作的过程:

之所以有这么多的行业都在使用流程图,是因为它具备以下几点好处:

1. 让复杂的过程易于理解。

2. 方便识别业务瓶颈,改进现有流程。

3. 容易向其他人解释业务。

而SreadJS(本公司产品,一款前端在线Excel表格)也支持插入流程图,但是有时候我们并不想自己去绘制流程图,而是希望根据某种数据结构自动生成一个流程图,而这个数据可以在服务端自动拼接好,也可以直接是其他三方服务的数据。将数据发送到前端后,自动生成一个流程图,如果对流程图做出一定的修改,数据也跟着变动,这样流程图的数据也可以保存下来,方便下次使用。

假定我们目前有一个这样的数据结构(省略部分数据)。

其中elements数组保存了所有的流程,id为流程的唯一id值;text代表流程框中的文本;type代表是普通流程的矩形框还是决策型的菱形框;process代表当前流程的进展,分为未开始、进行中和已完成,默认为未开始;width和height代表形状的宽高,可以不写。

edge数组则包含了各个流程中间的连接关系,表现在流程图中就是箭头从source指向target,其中flag代表在决策型流程中的结果。

最终想要在SpreadJS中生成这样一个流程图:

那么如何才能实现这样的效果呢?我认为大致需要做以下几个工作:

1. 根据elements的信息,向SpreadJS插入所有流程图形状

2. 根据edge的连线关系,将各个形状摆放到正确的位置

3. 创建形状之间的连线

4. 添加监听,当流程发生变化时,动态地改变数据

实现步骤

接下来就讲一下以上四个步骤具体如何实现。

1. 根据elements的信息,向SpreadJS插入所有流程图形状

这一步比较简单,无非就是遍历elements,并向当前的sheet插入形状:

我们通过数据中的信息来修改shape的样式,包括宽高以及背景色。

这里我们可以将id挂在shape上,后续方便通过id找到对应的形状或者数据model。

另外,读者可以注意到我在数据model上挂载了两个函数,分别为next和prev,这是为了方便寻找流程的下一级或者上一级,后续会经常用到这两个函数。

这样处理后,SpreadJS中的形状就已经生成好了:

2. 根据edge的连线关系,将各个形状摆放到正确的位置

这一步是比较困难的,假设我们的流程图是由左向右扩展的,那么横坐标(x方向)的位置是很好确定的,每深入一级,横坐标向右移动即可,困难的在于纵坐标(y方向)的位置计算,我这里抛砖引玉,如果大家有更好的算法,可以在评论区分享~

大致思路如下:请看下图的结构,左右两图中,B、C两节点距离A的纵向位置是不同的,这是因为左图的B节点拥有更多的子节点,而右图则较少,所以左侧在纵向需要拉开更多的距离,以摆放后续的节点。

当然,这里并不是单指子节点数量的多少,而是要看他们所占据的高度,也就是兄弟节点的数量。所以在代码中,我们需要递归计算一个节点后续的高度值:

其中hasCountMax值是为了预防节点循环的情况,比如A→B→A,代码中还有类似的处理。

得到节点的高度值后,就可以计算位置了,这个高度值越大,它离父级节点的纵向距离就越远,当然这里还要考虑当前节点占据兄弟节点的第几个,以及父级、兄弟节点的高度情况:

最后的代码可以看到这里也是一个递归计算,需要计算每一个节点下一级的位置。

3. 创建形状之间的连线

这一步的思路有一点小复杂,但是实现起来比较简单。

难点主要在于确定连接点的位置。矩形和菱形都有4个连接点,在下图中,左2连接右2并不是最好的连接方式,我们希望是以下三种方式:左2右1、左3右1、左3右0 ,所以我们就需要根据两个形状的位置关系来确定连接点。

这里要用到一点初高中的数学知识,我们要计算从起始形状到目标形状中心位置的连线,与横轴夹角的正弦值来确定连接点:

这里一共有12种情况,不做一一赘述。

决策图的连线还要标明“是”和“否”两种不同的情况,计算连线的中心点并插入形状即可:

4. 添加监听,当流程图发生变化时,动态地改变数据

因为用户可以随时拖动流程图,而流程图的变化属性大致有以下几种:位置、长宽、文字属性,我们要将他们最新的值记录下来,方便下次还原。另外,当位置和长宽变化时,连线也会自动更新,我们需要更新上文提到的“是”和“否”的文字的位置,这里用到了防抖模式以提升性能。

另外,我还在设计器中添加了一些新菜单,可以对流程的状态进行修改,也可以将流程图导出为图片,或打印流程图的数据model等,感兴趣的读者可以在源码中自行查看实现方式。

这里展示一下修改流程图后,数据同步修改的功能,我们以“换货”举例,默认状态下,换货的数据是这样的:

我们通过上方的按钮调整换货为已完成,修改文本,并且移动它的位置,改变其宽高后:

可以看到,表示其状态的process已经从1(进行中)变成了2(已完成),宽高、位置、文本也有相应的变化。

结语:

流程图是一种非常有用,也很常用的工具,结合SpreadJS,你可以比较方便的实现动态生成流程图的功能,在这个demo的基础上,你还可以添加右键新增子级、新增兄弟级元素等功能,借助SpreadJS丰富、开放的Api,你可以非常灵活地实现你要的功能。

最后,希望这篇文章能对你有帮助~

源码地址:

https://gcdn.grapecity.com.cn/forum.php?mod=attachment&aid=Mjg0MjY2fDg4YjJmMDRlfDE2OTA4NzU1NTN8ODE2MDZ8MTc5NzE0

扩展链接:

Spring Boot框架下实现Excel服务端导入导出

项目实战:在线报价采购系统(React +SpreadJS+Echarts)

Svelte 框架结合 SpreadJS 实现纯前端类 Excel 在线报表设计

Canvas好难,如何让研发低成本实现Web端流程图设计功能的更多相关文章

  1. snkrs web端分析,canvas中的fingerpint

    snkrs web端分析,canvas中的fingerpint 代码如下 (()=>{ const canvas = document.createElement("canvas&qu ...

  2. 流量难、获客难、增长难?增长黑客思维“解救”B端业务

    随着市场竞争的不断加剧,流量越来越贵.留存与转化越来越难,实现用户和业务的增长并不容易.无论是B2C 还是B2B的企业,都可能遇到增长的挑战.对于营销团队而言,传统的漏斗式营销思维已有些力不从心,需要 ...

  3. web图片前端裁剪功能实现_利用html5 canvas技术实现图片裁剪

    用户上传头像然后截图的需求很常见,很多做法是把图像发送到后端,把裁剪后的结果发送给浏览器,这种方式会增加处理时延.最近正好学习了HTML5里的canvas,发现它的图片处理功能比较强大,就打算用can ...

  4. [canvas入坑3] 类似ps中魔术棒或者画图中油漆桶的功能

    查看效果请到 http://philippica.github.io/  点击fill 这功能其实实现很low,最早高一看黑书的时候看到了floodfill算法感觉好神奇,转念一想这不就是bfs么!! ...

  5. 剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记

    前言 网购已经不再是现在的时髦,而变成了我们每天的日常生活.上网已经和买买买紧密地联系在了一起,成为了我们的人生信条.而逛街一词,越来越多地变成了一种情怀.有时候我们去逛街,要么是为了打发时间,要么是 ...

  6. 学习React Native必看的几个开源项目

    学习React native ,分享几个不错的开源项目,相信你学完之后,一定会有所收获.如果还没有了解RN的同学们可以参考手把手教你React Native 实战之开山篇<一> 1.Fac ...

  7. django请求生命周期,FBV和CBV,ORM拾遗,Git

    一.django 请求生命周期 流程图: 1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post, ...

  8. python 全栈开发,Day84(django请求生命周期,FBV和CBV,ORM拾遗,Git)

    一.django 请求生命周期 流程图: 1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post, ...

  9. 9月16日,base 福州,2018MAD技术论坛邀您一起探讨最前沿AR技术!

    “ 人工智能新一波浪潮带动了语音.AR等技术的快速发展,随着智能手机和智能设备的普及,人机交互的方式也变得越来越自然. 9月16日,由网龙网络公司.msup联合主办的MAD技术论坛将在福州举行.本次论 ...

  10. 搞IT产品,请谨记Mobile First

    我们在哪儿? 作为一名企业IT的老鸟,发现一个比较有意思的事情,就是我们的企业IT产品,仍然投入大量的精力,在基于PC的WEB端的设计和交付上,而在APP上的,移动端的考虑,一直都是在PC搞完之后,再 ...

随机推荐

  1. define定义常量和宏

     define:预处理指令 使用方法有两种 1.define定义符号 denfine定义常量 2.define定义宏 宏是有参数的,它的参数是替换 常规来说这样写define定义宏没啥问题 但是这样写 ...

  2. linux随心记

    linux前言 1.计算机有哪两部分组成? 硬件和软件 2.常见的操作系统有哪些? pc端:window ,linux,MacOS 移动端:Android,ios,鸿蒙 3.什么是Linux系统内核 ...

  3. 【GiraKoo】常用编码的对比(ASCII,GB2312,GBK,GB18030,UCS,Unicode)

    常用编码的对比(ASCII,GB2312,GBK,GB18030,UCS,Unicode) 在程序开发中,文字编码一直扮演着人畜无害,却背后捅一刀的角色. 可能在源代码文件中,注释莫名其妙地变成了乱码 ...

  4. weex 中出现 loading无法关闭

    如题使用weex 搞个app 一安装就有一个bug 一直这里转!!! 找了半天原来是自己没按规定来,在index.vue中直接使用了rower <template> <router- ...

  5. 【踩坑记录】字节流数据按照string的方式读取然后按照string的方案存储,编码导致二进制数据发生变化,原理记录

    ​ 目录 问题缘由 背后原理 C#代码示例 总结 问题缘由 由于公司需求,需要读取游戏Redis数据做内外网数据迁移,没有与游戏组过多的沟通.  使用的数据类型是Hash, key是string,va ...

  6. HttpURLConnection调用webservice,c#、java、python等HTTP调用webservice,简单的webservice调用

    以前调用webservice一般使用axis.axis2先生成java类后,直接引用,多方便.但是有的webservice接口非常的函数,生成的java类非常多,有没有一种非常简化的方法. axis2 ...

  7. spring-boot集成mybatis真的很简单吗?

    在日常的后端开发中,使用mybatis作为DAO层的持久框架已经是惯例.但很多时候都是在别人搭好的框架中进行开发,对怎么搭建环境是一知半解,今天就来实践下. 一.集成分哪些步骤 来看下集成mybati ...

  8. 项目小结:使用Docker迁移服务到离线服务器

    前言 最近遇到的这个场景,需要把之前开发的一套系统迁移到一个离线的服务器上,这个服务器有点麻烦,接入VPN后通过堡垒机才能访问,速度也很慢,遇到不少坑,本文记录一下迁移过程. 基本信息 原本这套系统也 ...

  9. 驱动开发:内核LoadLibrary实现DLL注入

    远程线程注入是最常用的一种注入技术,在应用层注入是通过CreateRemoteThread这个函数实现的,该函数通过创建线程并调用 LoadLibrary 动态载入指定的DLL来实现注入,而在内核层同 ...

  10. 使用 ProcessBuilder API 优化你的流程

    ProcessBuilder 介绍 Java 的 Process API 为开发者提供了执行操作系统命令的强大功能,但是某些 API 方法可能让你有些疑惑,没关系,这篇文章将详细介绍如何使用 Proc ...