JointJS动态流程图

    最近公司有个导航项目需要做个流程图比较复杂的那种,作为一个前端小菜也是很蒙的,要求就两条一:动态加载流程图;二:动态连线;这两点要求也是难住了一阵呢,最后选择了jointJS,选着原因官网API很详细,代码量比较少封装起来使用也很便捷,进阶文档基础略过,没接触过得可以去在官网先学习一下,废话不多少直接上硬菜。

    我这块写了两个一个是节点型流程图和关系行流程图。(源码在下一篇文章可以下载查看示例)

节点型流程图

  一 准备文件

1.jquery

2.Lodash.js

3.Backbone.js

4.joint.js

5.joint.css

<script src="jquery.min.js"></script>
<script src="lodash.min.js"></script>
<script src="backbone-min.js"></script>
<script src="joint.js"></script>
<link rel="stylesheet" type="text/css" href="joint.css" />

这些官网都可以下载。

前四个顺序一定不能乱!!!(也是坑了一次)

二 进阶

 1.创建数据模块要出现的画板

<div class="ProcessDiv"></div>

     因为是动态加载的数据,但是导航图需要的是坐标,数据加载出来的位置是不固定的所以需要建立一个隐藏的区域把joint画板覆盖上边。

2.创建joint画板

<div id="paper"></div>

html代码:

<div class="ProcessDiv"></div>
<div id="paper"></div>

Js代码:

 var graph = new joint.dia.Graph;

    var paper = new joint.dia.Paper({
        el: $('#paper'),
        width: 20000,
        height: 20000,
        gridSize: 1,
        model: graph
    });

定义Joint的画布和画板,然后我封装了两个函数用来定义线和模块,方便使用减少代码量,在写代码方面也很很懒的,哈哈继续继续。

 //定义模块形状
var stat = function(x, y, shape, background, text) {
				var cell;
				if(shape === "rect") {
					cell = new joint.shapes.basic.Rect({
						position: {
							x: x,
							y: y
						}, //坐标
						size: {
							width: 140,
							height: 40
						}, //宽高
						attrs: {
							rect: {
								fill: {
									type: 'linearGradient',
									stops: [{
											offset: '0%',
											color: background
										}, //渐变开始
										{
											offset: '100%',
											color: background
										}
									], //渐变结束
									attrs: {
										x1: '0%',
										y1: '0%',
										x2: '0%',
										y2: '100%'
									}
								},
								stroke: background, //边框颜色
								'stroke-width': 1 //边框大小
							},
							text: {
								text: text,
								'ref-x': .5,
								'ref-y': .5,
								fill: '#000'
							}, //显示文字
						}
					});
				} else if(shape === "ellipse") {
					cell = new joint.shapes.basic.Ellipse({
						position: {
							x: x,
							y: y
						}, //坐标
						size: {
							width: 140,
							height: 40
						}, //宽高
						attrs: {
							ellipse: {
								fill: {
									type: 'linearGradient',
									stops: [{
											offset: '0%',
											color: background
										}, //渐变开始
										{
											offset: '100%',
											color: '#FFFFFF'
										} //渐变结束
									],
									attrs: {
										x1: '0%',
										y1: '0%',
										x2: '0%',
										y2: '100%'
									}
								},
								stroke: background, //边框颜色
								'stroke-width': 1 //边框大小
							},
							text: {
								text: text,
								'ref-x': .5,
								'ref-y': .5,
								fill: '#000'
							}, //显示文字
						}
					});
				}
				graph.addCell(cell);
				return cell;
			};

 rect为方形,circle为圆形,还有stroke:边框颜色,具体其他样式官网很详细。

//定义连线
			function link(source, target, label) {
				var cell = new joint.dia.Link({
					source: {
						id: source.id
					},
					target: {
						id: target.id
					},
					labels: [{
						position: 0.5,
						attrs: {
							text: {
								text: label || '',
								'font-weight': 'bold'
							}
						}
					}],

					attrs: {
						'.connection': {
							stroke: '#333333', //连线颜色
							'stroke-width': 4 //连线粗细
						},
						'.marker-target': {
							fill: '#000', //箭头颜色
							d: 'M 10 0 L 0 5 L 10 10 z' //箭头样式
						}
					}
				});
				graph.addCell(cell);
				return cell;
			}

导航流程图需要的根据节点进行连接,数据结构如下

var data = [
				[{
					title: '订单',
					tolink: "",
					state: "1",
					id: "1",
					name: '订单'
				}],
				[{
					title: '预售',
					tolink: "1",
					state: "1",
					id: "2",
					name: '预售'
				}, {
					title: '预售2',
					tolink: "1",
					state: "0",
					id: "3",
					name: '预售2'
				}],
				[{
					title: '采购清单',
					tolink: "2",
					state: "-1",
					id: "4",
					name: '采购清单'
				}, {
					title: '待加工',
					tolink: "2",
					state: "-1",
					id: "5",
					name: '待加工'
				}]
			]

      state代表状态,这里可以随意定义,比较懒就直接-1,0,1定义了,id是必须需要的因为要根据id找坐标,tolink是当前上一个连接节点的id。这里结构一定要根据节点划分,每一级的节点要在一个数组里也是方便下一步遍历操作,当然大家也可以用其他方法都是可以的这方面刚接触前端还在摸索中。

三 动态连接

  var arraylist = [];
    $.each(data, function (index, item1) {
        $(".ProcessDiv").append("<div class='process'></div>")
        $.each(item1, function (index2, item2) {
            $(".process").eq(index).append("<p id='" + item1[index2].id + "'>" + item1[index2].title + "</p>")
            debugger
            var id1 = item1[index2].id
            var arrValue1 = arraylist[id1];
            var y = $("#" + id1).offset().top;
            var x = $("#" + id1).offset().left;
            var name = item1[index2].name
            var n = item1[index2].state
            if (arrValue1 == undefined) {
                switch (n) {
                    case "1":
                        arraylist[id1] = stat(x, y, "rect", "green", name);
                        break;
                    case "0":
                        arraylist[id1] = stat(x, y, "rect", "yellow", name);
                        break;
                    default:
                        arraylist[id1] = stat(x, y, "rect", "red", name);
                }
            }
            if (item1[index2].tolink != "") {
                var id2 = item1[index2].tolink
                var arrValue2 = arraylist[id2];
                var y2 = $("#" + id2).offset().top;
                var x2 = $("#" + id2).offset().left;
                var state = item1[index2].state
                if (arrValue2 == undefined) {
                    arraylist[id2] = stat(x2, y2, "rect", "#000");
                }
                link(arraylist[id2], arraylist[id1]);
            }

        });

    });

   这里需要用到字典数组,放置重复生成模块这坑也是摔的妥妥的,定义一个数组arraylist,然后进行第一次遍历data,根据数组个数也就是节点创建节点区域process,然后进行二次遍历,拿到遍历到的节点添加到对应的节点区域,遍历时候根据id存入字典数组,再次遍历是如果存在就直接使用,没有在创建,这一步是防止一个父节点连接多个子节点时候重复创建,创建var根据offset().top;和offset().left,存入y,x坐标,,如果tolink==""就保存父节点,根据state决定当前接节点状态及颜色,这下坐标有了,id有了就可以连接了。

link(arraylist[id2], arraylist[id1]);

完整代码:

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<link rel="stylesheet" type="text/css" href="css/joint.css" />
		<script src="js/jquery.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/lodash.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/backbone.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/joint.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/template.js" type="text/javascript" charset="utf-8"></script>
		<style type="text/css">
			.left {
				float: left;
				height: 100%;
				width: 20%;
			}
			
			html,
			body,
			#paper {
				height: 100%;
				width: 100%;
			}
			
			#paper {
				position: absolute;
				top: 0;
			}
			
			p {
				margin: 50px 0;
			}
		</style>
	</head>

	<body>
		<div id="paper" class="paper"></div>
		<div class="box"></div>
		<script type="text/javascript">
			var graph = new joint.dia.Graph();
			//定义画布
			var paper = new joint.dia.Paper({
				el: $('#paper'),
				width: 1200,
				height: 600,
				gridSize: 1,
				model: graph
			});
			paper.$el.css('pointer-events', 'none')
			//定义形状
			var stat = function(x, y, shape, background, text) {
				var cell;
				if(shape === "rect") {
					cell = new joint.shapes.basic.Rect({
						position: {
							x: x,
							y: y
						}, //坐标
						size: {
							width: 140,
							height: 40
						}, //宽高
						attrs: {
							rect: {
								fill: {
									type: 'linearGradient',
									stops: [{
											offset: '0%',
											color: background
										}, //渐变开始
										{
											offset: '100%',
											color: background
										}
									], //渐变结束
									attrs: {
										x1: '0%',
										y1: '0%',
										x2: '0%',
										y2: '100%'
									}
								},
								stroke: background, //边框颜色
								'stroke-width': 1 //边框大小
							},
							text: {
								text: text,
								'ref-x': .5,
								'ref-y': .5,
								fill: '#000'
							}, //显示文字
						}
					});
				} else if(shape === "ellipse") {
					cell = new joint.shapes.basic.Ellipse({
						position: {
							x: x,
							y: y
						}, //坐标
						size: {
							width: 140,
							height: 40
						}, //宽高
						attrs: {
							ellipse: {
								fill: {
									type: 'linearGradient',
									stops: [{
											offset: '0%',
											color: background
										}, //渐变开始
										{
											offset: '100%',
											color: '#FFFFFF'
										} //渐变结束
									],
									attrs: {
										x1: '0%',
										y1: '0%',
										x2: '0%',
										y2: '100%'
									}
								},
								stroke: background, //边框颜色
								'stroke-width': 1 //边框大小
							},
							text: {
								text: text,
								'ref-x': .5,
								'ref-y': .5,
								fill: '#000'
							}, //显示文字
						}
					});
				}
				graph.addCell(cell);
				return cell;
			};

			//定义连线
			function link(source, target, label) {
				var cell = new joint.dia.Link({
					source: {
						id: source.id
					},
					target: {
						id: target.id
					},
					labels: [{
						position: 0.5,
						attrs: {
							text: {
								text: label || '',
								'font-weight': 'bold'
							}
						}
					}],

					attrs: {
						'.connection': {
							stroke: '#333333', //连线颜色
							'stroke-width': 4 //连线粗细
						},
						'.marker-target': {
							fill: '#000', //箭头颜色
							d: 'M 10 0 L 0 5 L 10 10 z' //箭头样式
						}
					}
				});
				graph.addCell(cell);
				return cell;
			}

			var data = [
				[{
					title: '订单',
					tolink: "",
					state: "1",
					id: "1",
					name: '订单'
				}],
				[{
					title: '预售',
					tolink: "1",
					state: "1",
					id: "2",
					name: '预售'
				}, {
					title: '预售2',
					tolink: "1",
					state: "0",
					id: "3",
					name: '预售2'
				}],
				[{
					title: '采购清单',
					tolink: "2",
					state: "-1",
					id: "4",
					name: '采购清单'
				}, {
					title: '待加工',
					tolink: "2",
					state: "-1",
					id: "5",
					name: '待加工'
				}]
			]

			console.log(data)
			var arraylist = [];
			$.each(data, function(index, item1) {
				$(".box").append("<div class='left'></div>")

				$.each(item1, function(index2, item2) {

					$(".left").eq(index).append("<p id='" + item1[index2].id + "'>" + item1[index2].title + "</p>")
					debugger
					var id1 = item1[index2].id
					var arrValue1 = arraylist[id1];
					var y = $("#" + id1).offset().top;
					var x = $("#" + id1).offset().left;
					var name = item1[index2].name
					var n = item1[index2].state
					if(arrValue1 == undefined) {

						switch(n) {
							case "1":
								arraylist[id1] = stat(x, y, "rect", "#99ff00", name);
								break;
							case "0":
								arraylist[id1] = stat(x, y, "rect", "#FFFF00", name);
								break;
							default:
								arraylist[id1] = stat(x, y, "rect", "red", name);

						}

					}
					if(item1[index2].tolink != "") {
						var id2 = item1[index2].tolink
						var arrValue2 = arraylist[id2];
						var y2 = $("#" + id2).offset().top;
						var x2 = $("#" + id2).offset().left;
						var state = item1[index2].state
						if(arrValue2 == undefined) {
							arraylist[id2] = stat(x2, y2, "rect", "#000");
						}
						link(arraylist[id2], arraylist[id1]);
					}

				});

			});
		</script>
	</body>

</html>

效果图

 

 

 

 

 

 

基本节点流程图就这样了只需要后台传入数据就可以了

关系型流程图

 关系型流程图就有点复杂,主要复杂在数据结构这方面,结构如下图

字典数组和遍历和节点流程图一样,但是关系这块要在一个数组里面包含所有关联的信息,在创建隐藏模版这块推荐使用模版引擎(template.js)搭配joint堪称完美!!

效果图如下:

 

 

写得有点仓促样式可能不太 咳咳,基本的方法效果还是实现了,先这样吧有不足的地方欢迎大家一起学习讨论!

 

 

 

 跨浏览器,可兼容IE7--IE10, FireFox, Chrome, Opera等几大内核的浏览器,且不需要浏览器再加装任何控件。  多系统兼容性、可移植性:由于只包括前台UI,因此二次开发者可很方便将本插件用在任何一种需要流程图的B/S系统应用上,流程图的详细实现逻辑完全交于后台程序开发者自己实现;对于后台,只要能返回/接收能被本插件解析的JSON格式数据即可.所以本插件可用于不同的服务器语言建立的后台上.  跨领域:流程图设计器不止用在电信领域,在其它需要IT进行技术支持的领域中都有重大作用.  以下从纯技术实现层面具体描述:  页面顶部栏、左边侧边栏均可自定义;  当左边的侧边栏设为不显示时,为只读状态,此时的视区可当作是一个查看器而非编辑器。  侧边工具栏除了基本和一些流程节点按钮外,还自定义新的节点按钮,自定义节点都可以有自有的标、类型名称,定义后在使用可可在工作区内增加这些自定义节点。  顶部栏可显示流程图数据组的标题,也可提供一些常用操作按钮。  顶部栏的按钮,除了撤销、重做按钮外,其余按钮均可自定义点击事件。  可画直线、折线;折线还可以左右/上下移动其中段。  具有区域划分功能,能让用户更直观地了解哪些节点及其相互间的转换,是属于何种自定义区域内的。  具有标注功能,用橙红色标注某个结点或者转换线,一般用在展示流程进度时。  能直接双击结点、连线、分组区域中的文字进行编辑  在对结点、连线、分组区域的各种编辑操作,如新增/删除/修改名称/重设样式或大小/移动/标注时,均可捕捉到事件,并触发自定义事件,如果自定义事件执行的方法返回FALSE,则会阻止操作。  具有操作事务序列控制功能,在工作区内的各种有效操作都能记录到一个栈中,然后可以进行撤销(undo())或重做(redo()),像典型的C/S软件一样。  0.4版中,加入了只导出在初始载入后被编辑的流程图中,只作了增删改等变更的元素,这样可用于用户快速存储,只保存本次变更过的内容,不用重新保存整个流程。  0.5版中,结点的样式不再受到原有程序的限制,所有样式均默认为淡蓝色长方形;如果要指定为圆形,可在初始化时定义结点类型为”原有类型”+” round”;如果要指定为复合结点,则可在初始化时定义结点类型为”原有类型”+” mix”。”原有类型”+” myType”:myType可为自己写的一种特殊样式类.  0.6版中,修正了一些BUG,改善了用户操作体验,并增加在可编辑状态下时,能用键盘上DELETE按键对元素进行删除功能。  0.7版中,修正了一些BUG,增加了连线变更要连的起始结点或结束结点的功能。  0.8版,取消原来的拟物化页面,变成如今的扁平化页面,并且支持主要位置的颜色自定义功能(如果想沿用原来老版本中的拟物化页面,只需保留原来的GooFlow.css文件即可);修正0.7版中的画线BUG。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值