用HTML5构建一个流程图绘制工具
zhezhongyun 2024-12-11 18:14 31 浏览
在我们的开发工程中经常会使用到各种图,所谓的图就是由节点和节点之间的连接所形成的系统,数学上专门有一个分支叫图论(Graph Theroy)。利用图我们可以做很多工具,比如思维导图,流程图,状态机,组织架构图,等等。今天我要做的是用开源的HTML5工具来快速构造一个做图的工具。
工具选择
工预善其事,必先利其器。第一件事是选择一件合适的工具,开源时代,程序员还是很幸福的,选择很多。
- flowchart.js http://adrai.github.io/flowchart.js/ , 基于SVG创建Flow Chart
- go.js http://www.gojs.net/latest/index.html go.js 提供一整套的JS工具 ,支持各种交互式图表的创建。有免费版和收费版
- joint.js http://www.jointjs.com/ joint.js 是另一个创建图标库的工具,也提供免费版和商业版
- jsPlumb http://www.jsplumb.org/ jsPlumb是一套开源的流程图创建工具 ,小巧精悍,使用简单
- d3 http://d3js.org 在html5领域,d3可谓是最好的可视化基础库,提供方面的DOM操作,非常强大。
最终,我选择了jsPlumb,因为它完全开源,使用很简单,用D3的话可能会多花很多功夫。joint.js也不错。大家可以根据自己的需要选择。
构建静态应用
下面我们一步一步的来使用jsPlumb来创建我们的流程图工具。
第一步是等待DOM和jsPlumb初始化完毕,类似document.ready()和jquery.ready(), 要使用jsPlumb, 需要把代码放在这个函数里:
jsPlumb.ready(function()?{ ????//?...?your?code?goes?here?... }
创建一个jsPlumb的实例,并初始化jsPlumb的配置参数:
//Initialize?JsPlumb var?color?=?"#E8C870"; var?instance?=?jsPlumb.getInstance({ ????//?notice?the?'curviness'?argument?to?this?Bezier?curve.??the?curves?on?this?page?are?far?smoother ????//?than?the?curves?on?the?first?demo,?which?use?the?default?curviness?value.?????? ????Connector?:?[?"Bezier",?{?curviness:50?}?], ????DragOptions?:?{?cursor:?"pointer",?zIndex:2000?}, ????PaintStyle?:?{?strokeStyle:color,?lineWidth:2?}, ????EndpointStyle?:?{?radius:5,?fillStyle:color?}, ????HoverPaintStyle?:?{strokeStyle:"#7073EB"?}, ????EndpointHoverStyle?:?{fillStyle:"#7073EB"?}, ????Container:"container-id" ?});
这里给给出了一些配置包括,连接线(这里配置了一个贝塞尔曲线),线的风格,连接点得风格。Container需要配置一个对应的DIV容器的id。(这里也可以使用setContainer的方法)
下面我们要创建一个节点(node),每一个节点可以用一个DIV来实现。我这里提供了一个函数来创建节点。
function?addNode(parentId,?nodeId,?nodeLable,?position)?{ ??var?panel?=?d3.select("#"?+?parentId); ??panel.append('div').style('width','120px').style('height','50px') ????.style('position','absolute') ????.style('top',position.y).style('left',position.x) ????.style('border','2px?#9DFFCA?solid').attr('align','center') ????.attr('id',nodeId).classed('node',true) ????.text(nodeLable); ??return?jsPlumb.getSelector('#'?+?nodeId)[0]; }
这里做的事情就是创建了一个DIV元素,并放在对应的容器的制定位置上,注意为了支持拖拽的功能,必须使用position:absolute 。
我使用D3来操作DOM,大家可能会更习惯JQuery,这纯属个人喜好的问题。
最后返回创建节点的实例引用,这是的selector使用了jsPlumb.getSelector()方法,它和JQuery的selector是一样的,这样用的好处是你可以使用不同的DOM操作库,例如Vanilla
下面我使用一个函数来创建端点/锚点(anchor),锚点就是节点上的连接点,用于连接不同的节点。
function?addPorts(instance,?node,?ports,?type)?{ ??//Assume?horizental?layout ??var?number_of_ports?=?ports.length; ??var?i?=?0; ??var?height?=?$(node).height();??//Note,?jquery?does?not?include?border?for?height ??var?y_offset?=?1?/?(?number_of_ports?+?1); ??var?y?=?0; ??for?(?;?i?<?number_of_ports;?i++?)?{ ????var?anchor?=?[0,0,0,0]; ????var?paintStyle?=?{?radius:5,?fillStyle:'#FF8891'?}; ????var?isSource?=?false,?isTarget?=?false; ????if?(?type?===?'output'?)?{ ??????anchor[0]?=?1; ??????paintStyle.fillStyle?=?'#D4FFD6'; ??????isSource?=?true; ????}?else?{ ??????isTarget?=true; ????} ????anchor[1]?=?y?+?y_offset; ????y?=?anchor[1]; ????instance.addEndpoint(node,?{ ??????uuid:node.getAttribute("id")?+?"-"?+?ports[i], ??????paintStyle:?paintStyle, ??????anchor:anchor, ??????maxConnections:-1, ??????isSource:isSource, ??????isTarget:isTarget ????}); ??} }
instance是jsPlumb的实例
node是我们用addNode方法创建的Node实例
ports,是一个string的数组,指定端点的个数和名字
type,可能是output或者input,指定端点的种类,一个节点的输出端口可以连接另一个节点的输入端口。
这里anchor是一个四维数组,0维和1维分别是锚点在节点x轴和y轴的偏移百分比。我这里希望把端口画在节点的左右两侧,并按照端口的数量均匀分布。
最后使用instance.addEndpoint来创建端点。注意这里只要指定isSource和isTarget就可以用drag&drop的方式来连接端点,非常方便。
下面一步我们提供一个函数来连接端点:
function?connectPorts(instance,?node1,?port1,?node2?,?port2)?{ ??//?declare?some?common?values: ??var?color?=?"gray"; ??var?arrowCommon?=?{?foldback:0.8,?fillStyle:color,?width:5?}, ??//?use?three-arg?spec?to?create?two?different?arrows?with?the?common?values: ??overlays?=?[ ????[?"Arrow",?{?location:0.8?},?arrowCommon?], ????[?"Arrow",?{?location:0.2,?direction:-1?},?arrowCommon?] ??]; ??var?uuid_source?=?node1.getAttribute("id")?+?"-"?+?port1; ??var?uuid_target?=?node2.getAttribute("id")?+?"-"?+?port2; ??instance.connect({uuids:[uuid_source,?uuid_target]}); }
node1和node2是源节点和目标节点的引用,port1和port2是源端口和目标端口的名字。
使用instance.connect方法来创建连接。 overlays用来添加连接线的箭头效果或者其他风格,我这里没有使用,因为觉得都不是很好看。大家如果要用,只要把overlays加入到instance.connect的方法参数就可以了。
调用以上方法来创建节点,端点和连接线。
var?node1?=?addNode('container-id','node1',?'node1',?{x:'80px',y:'20px'}); var?node2?=?addNode('container-id','node2',?'node2',?{x:'280px',y:'20px'}); addPorts(instance,?node1,?['out1','out2'],'output'); addPorts(instance,?node2,?['in','in1','in2'],'input'); connectPorts(instance,?node1,?'out2',?node2,?'in');
这里我们创建了两个节点,第一个节点有两个输出端口,第二个节点有三个输入端口,然后把第一个节点的out2端口连接到第二个端点的in端口。效果如下:
最后我们给节点增加drag&drop的功能,这样我们就可以拖动这些节点来改变图的布局了。
instance.draggable($('.node'));
这里似乎依赖于JQuery-UI,我还不是很清楚。
交互式创建节点
我们已经初步具有了创建图的功能,可是节点的创建必须通过程序,我们希望用交互的方式来创建节点。
通常我们希望有一个tree view的控件,让后通过拖拽来创建对应类型的节点。这里我使用了这个开源的tree view,基于bootstrap https://github.com/jonmiles/bootstrap-treeview
我们先创建一个tree view:
function?getTreeData()?{ ??var?tree?=?[ ????{ ??????text:?"Nodes", ??????nodes:?[ ????????{ ??????????text:?"Node1", ????????}, ????????{ ??????????text:?"Node2" ????????} ??????] ????} ??];? ??return?tree; } //Initialize?Control?Tree?View $('#control-panel').treeview({data:?getTreeData()});
树上有两个节点:
然后我实现从树上拖拽对应的节点,到流程图上的逻辑。
//Handle?drag?and?drop $('.list-group-item').attr('draggable','true').on('dragstart',?function(ev){ ??//ev.dataTransfer.setData("text",?ev.target.id); ??ev.originalEvent.dataTransfer.setData('text',ev.target.textContent); ??console.log('drag?start'); }); $('#container-id').on('drop',?function(ev){ ??//avoid?event?conlict?for?jsPlumb ??if?(ev.target.className.indexOf('_jsPlumb')?>=?0?)?{ ????return; ??} ??ev.preventDefault(); ??var?mx?=?''?+?ev.originalEvent.offsetX?+?'px'; ??var?my?=?''?+?ev.originalEvent.offsetY?+?'px'; ??console.log('on?drop?:?'?+?ev.originalEvent.dataTransfer.getData('text')); ??var?uid?=?new?Date().getTime(); ??var?node?=?addNode('flow-panel','node'?+?uid,?'node',?{x:mx,y:my}); ??addPorts(instance,?node,?['out'],'output'); ??addPorts(instance,?node,?['in1','in2'],'input'); ??instance.draggable($(node)); }).on('dragover',?function(ev){ ??ev.preventDefault(); ??console.log('on?drag?over'); });
这里要注意的是要避免和jsPlumb拖拽端点的逻辑冲突,当检测到target是jsPlumb对象是需要直接从drop方法中退出以执行对应的jsPlumb的drop逻辑。
好了,一个绘制流程图的软件工具初步完工。
我把代码放在oschina的代码托管服务上了, 大家有兴趣可以去试试。
相关推荐
- 自助机网络监控(自助设备监控)
-
今日领导打电话说上次的自助机修好了,今天又不行了,这是第二次打电话了。目前也没有其他人反馈这个机器的问题。于是我就想能不能根据IP去ping这个自助机的网络是否正常,如果不正常,关机了就自动给我发到科...
- C# 打字练习(WinForm)快速打字小游戏,输入正确自动爆炸效果
-
打字游戏窗体初始化:publicintk;PictureBoxpic=newPictureBox();//生成飞机控件privatev...
- 这三款颜值极高的工具完美契合win10风格,逼格满满
-
今天介绍三款开源软件让你的Windows10逼格再飞跃一个等级,而且功能与颜值兼具!ModernFlyouts这是一款Windows10弹出UI的替代品,比原生UI更漂亮、更现代。提示框的位置可以随意...
- VC界面开发组件Xtreme Toolkit Pro全新发布v17.0.0
-
Codejock软件公司的XtremeToolkitPro是屡获殊荣的VC界面库,是MFC开发中最全面界面控件套包,它提供了Windows开发所需要的11种主流的VisualC++MFC控件,...
- 字体|好看的英文字体合集(四)(最好看的十种英文字体)
-
电脑里的字体不在于多而在精,拥有10000个字体不用的话也是浪费,找起来也不容易,只有经常使用的才是好字体。获取方式:私31.潮流赛博朋克金属字体32.潮流创意可扩展英文字体StretchPro3...
- Qt5 C++入门教程-第13章 自定义控件(Custom Widgets)
-
大道至简,在Qt5C++入门教程的这一部分,我们将创建一个自定义部件。大多数工具包通常仅提供诸如按钮、文本部件或滑块等最常见的部件。没有哪个工具包能够提供所有可能的部件。程序员必须自行创建此类部件...
- 艾灸的美容养生功效(艾灸美容养颜)
-
艾灸,属于医外治物理疗法,通过百草之王艾草和13种名贵中草药特制成香条状,点燃以温热刺激患处四周或相关穴位,帮助人体全面温通经络,从而加速皮肤的血液循环,淡化色斑。艾灸疗法-温和灸1.四白穴:四白...
- 你可能不知道的10个CSS3中的隐藏特性
-
CSS3为web设计增添了许多令人惊叹的特性,这其中你经常会用到box-shadow(图层阴影),border-radius(边框圆角),transform(变形)这一类受欢迎的常用特性。但是还有一些...
- 不会才学-4 Tkinter 控件1一Label、Button、Entry
-
一、标签控件(Label)序号可选项&描述1anchor文本或图像在背景内容区的位置,默认为center,可选值为(n,s,w,e,ne,nw,sw,se,center)eswn是东南西北英文...
- 从0到1建立一张评分卡之可视化分析
-
上一篇文章介绍了如何进行数据预处理,接下来介绍如何进行探索性数据分析。探索性数据分析又叫EDA,即ExploratoryDataAnalysis。其实数据预处理也属于EDA的一部分,EDA的目...
- 高温致日本冲绳最大珊瑚群90%白化褪色
-
来源:新华网高温致日本冲绳最大珊瑚群90%白化褪色A+A-分享2016-09-0412:50:53 来源:新华网查看原文<divclass="video-box"data-vid="...
- 学习笔记(十七)|PPT考点总结(ppt.1)
-
分享兴趣,传播快乐,增长见闻,留下美好!亲爱的您,这里是LearningYard学苑。今天小编为大家带来“学习笔记(十七)|PPT考点总结”,欢迎您的访问。Shareyourinterests,...
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●浏览器兼容性问题是指因为不同的浏览器对同一段CSS代码解析不同导致页面显示效果不统一...
- 304071纽郎缝包机送料牙架(缝包机送料牙更换)
-
portant;white-space:normal;font-size-adjust:none;font-stretch:normal;background-color:rgb(25...
- 安卓手机微信设置字体大小导致页面错位的解决方案
-
切图网专注网页切图、小程序切图服务,在最近的客户反应中,当安卓手机设置字体大小以后,会导致从微信公众号进入的页面也会字体放大,导致页面的不同程度的错位,要想解决这个问题,可以通过代码解决,ios相对简...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- CSS 水平对齐 (Horizontal Align) (30)