论坛首页 Web前端技术论坛

用canvas绘制的osworkflow流程显示

浏览 5379 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2013-06-03   最后修改:2013-06-03
这个是一个兼容osworkflow流程设计器设计出来的.xml和.lyt文件的显示流程图的js
使用了jquery和excanvas.js兼容IE、Fiefox、Chrome、Opera浏览器
支持鼠标事件



解压src.zip后,打开demo.html点击生成流程图就可以观看演示效果



(function() {

	/**
	 * 流程图绘制
	 * @param canvas
	 * @param xmlStr
	 * @param lytStr
	 * @param currentSteps
	 * @param historySteps
	 * @returns {WfCanvas}
	 */
	function WfCanvas(canvas, xmlStr, lytStr, currentSteps, historySteps) {
		currentSteps = currentSteps || [];
		historySteps = historySteps || [];
		var 
			cvs = $(canvas),
			//bcolor = $(canvas).css("background-color"),
			fontFamily = "Arial",
			fontSize = 15,
			
			font = "normal normal bold " + fontSize + "px " + fontFamily,
			labelFontSize = 12,
			labelFont = "normal normal normal " + labelFontSize + "px " + fontFamily,
			fontColor = "Black",
			borderColor = "Black",
			stepColor = "#FFFF37",
			lineColor = "Black",
			shadowOffset = 6;
			initActionCorlor = "#9AFF02",
			currentStepColor = "#FF5151",
			historyStepColor = "#9AFF02",
			shadwColor = "#d0d0d0",
			ctx = null,
			xml = null,
			lyt = null,
			xmlNodeMap = {},
			cellNodeMap = {},
			connectorNodeMap = {},
			cellMap = {},
			connectorMap = {};

		/**
		 * 画流程图
		 */
		function drawWf() {
			init();
			mouseSupport();
			draw();
		}

		function init() {
			
			if(!canvas.getContext) {
				window.G_vmlCanvasManager.initElement(canvas);  
			}
			ctx = canvas.getContext("2d");

			xml = $.parseXML(xmlStr);
			lyt = $.parseXML(lytStr);

			$.each($(xml).find("[id]"), function(i, node) {
				xmlNodeMap[$(node).attr("id")] = node;
			});

			$.each($(lyt).find("[id]"), function(i, node) {
				if (node.tagName == "cell") {
					cellNodeMap[$(node).attr("id")] = node;
				} else if (node.tagName == "connector") {
					connectorNodeMap[$(node).attr("id")] = node;
				}
			});

			$.each(cellNodeMap, function(id, cellNode){
				cellNode = $(cellNode);
				var xmlNode = $(xmlNodeMap[id]);
				var isCurrent = false;
				var isHistory = false;
				$.each(currentSteps, function(idx, stepId){
					if (stepId == id) {
						isCurrent = true;
					}
				});
				$.each(historySteps, function(idx, stepId){
					if (stepId == id) {
						isHistory = true;
					}
				});
				cellMap[id] = {
					id: id,
					name: xmlNode.attr("name"),
					type : cellNode.attr("type"),
					height: parseFloat(cellNode.attr("height")),
					width: parseFloat(cellNode.attr("width")),
					labelx: parseFloat(cellNode.attr("labelx")),
					labely: parseFloat(cellNode.attr("labely")),
					x: parseFloat(cellNode.attr("x")),
					y: parseFloat(cellNode.attr("y")),
					isCurrent: isCurrent,
					isHistory: isHistory
				};
			});

			$.each(connectorNodeMap, function(id, connectorNode){
				connectorNode = $(connectorNode);
				var xmlNode = $(xmlNodeMap[id]);
				var actionNode = xmlNode.parent().parent();
				var stepNode = actionNode.parent().parent();
				var srcId = stepNode.attr("id");
				if (actionNode.parent()[0].tagName == "initial-actions") {
					srcId = actionNode.attr("id");
				}
				var routings = [];
				var routingNodes = connectorNode.children("routing");
				$.each(routingNodes, function(idx, routingNode) {
					routingNode = $(routingNode);
					var routing = {
						x: parseFloat(routingNode.attr("x")),
						y: parseFloat(routingNode.attr("y"))
					};
					routings.push(routing);
				});
				connectorMap[id] = {
					id: id,
					label: actionNode.attr("name"),
					color : connectorNode.attr("color"),
					linewidth: parseFloat(connectorNode.attr("linewidth")),
					labelx: parseFloat(connectorNode.attr("labelx")),
					labely: parseFloat(connectorNode.attr("labely")),
					from: parseInt(connectorNode.attr("from")),
					to: parseInt(connectorNode.attr("to")),
					src: cellMap[srcId],
					target: cellMap[xmlNode.attr("step")],
					routings: routings
				};
			});

			var size = calculateCanvasSize();
			//cvs.width(size.width);
			//cvs.height(size.height);
			canvas.width = size.width + 10;
			canvas.height = size.height + 10;
		}

		function calculateCanvasSize() {
			var minx = 0, miny = 0, maxx = 0, maxy=0;
			$.each(cellMap, function(id, cell) {
				minx = Math.min(minx, cell.x);
				miny = Math.min(miny, cell.y);
				maxx = Math.max(maxx, cell.x + cell.width);
				maxy = Math.max(maxy, cell.y + cell.height);
			});
			$.each(connectorMap, function(id, connector) {
				$.each(connector.routings, function(i, r) {
					minx = Math.min(minx, r.x);
					miny = Math.min(miny, r.y);
					maxx = Math.max(maxx, r.x);
					maxy = Math.max(maxy, r.y);
				});
			});
			return {
				width: maxx - minx,
				height: maxy - miny
			};
		}

		function mouseSupport() {
			cvs.unbind("mousemove").mousemove(function(e) {
				$.each(cellMap, function(id, cell){
					if (!cell.isHover) {
						if (isInRange(e, cell)) {
							cell.isHover = true;
							cvs.css("cursor", "pointer");
						} else {
							cell.isHover = false;
						}
					} else {
						if (!isInRange(e, cell)) {
							cell.isHover = false;
							cvs.css("cursor", "default");
						} else {
							cell.isHover = true;
						}
					}
				});
			});
			cvs.unbind("click").click(function(e) {
				$.each(cellMap, function(id, cell){
					if (isInRange(e, cell)) {
						alert(cell.name);
					}
				});
			});
		}
		
		function isInRange(e, cell) {
			var x = e.offsetX || e.clientX - cvs.offset().left,
				y = e.offsetY || e.clientY - cvs.offset().top;
			
			if (x >= cell.x && x <= cell.x + cell.width 
					&& y >= cell.y && y <= cell.y + cell.height) {
				return true;
			}
			
			return false;
		}
		
		/**
		 * 画流程图
		 */
		function draw() {
			ctx.clearRect(0, 0, cvs.width(), cvs.height());
			$.each(cellMap, function(id, cell) {
				drawCell(cell);
			});
			$.each(connectorMap, function(id, connector) {
				drawConnector(connector);
			});
		}

		/**
		 * 画步骤
		 * @param cell
		 */
		function drawCell(cell) {
			ctx.strokeStyle = borderColor;
			if (cell.type == "InitialActionCell") {
				ctx.fillStyle = initActionCorlor;
				drawEllipse(cell, cell.width, cell.height);
			} else {
				if (cell.isCurrent) {
					ctx.fillStyle = currentStepColor;
				} else if (cell.isHistory){
					ctx.fillStyle = historyStepColor;
				} else {
					ctx.fillStyle = stepColor;
				}
				drawRect(cell, cell.width, cell.height);
			}
			
			var fx = cell.x + (cell.width - cell.name.length * fontSize) / 2;
			var fy = cell.y + (cell.height - fontSize) / 2 + fontSize;

			ctx.font = font;
			ctx.fillStyle = fontColor;
			ctx.fillText(cell.name, fx, fy);
		}

		/**
		 * 画连接
		 * @param c
		 */
		function drawConnector(c) {
			var points = [];
			// 起点
			points.push(getPortPoint(c.src, c.from));
			// 中间点
			$.each(c.routings, function(idx, routing) {
				points.push(routing);
			});
			// 终点
			points.push(getPortPoint(c.target, c.to));
			
			var last = points.length - 1;
			// 如果两端是cell的中点,就获取与cell相交的点
			if (c.from == 0) {
				points[0] = getCrossPoint(c.src, points[1], points[0]);
			};
			if (c.to == 0) {
				points[last] = getCrossPoint(c.target, points[last-1], points[last]);
			};

			// 画线
			ctx.strokeStyle = lineColor;
			for (var i = 0; i < last; i++) {
				drawLine(points[i], points[i+1]);
			}
			
			// 画文字
			var dx = points[last].x - points[0].x,
				dy = points[last].y - points[0].y,
				fx = c.labelx / 1000 * dx + points[0].x,
				fy = c.labely / 1000 * dy + points[0].y,
				fw = c.label.length * labelFontSize,
				fh = labelFontSize;
			
			fx -= fw / 2;
			fy += fh / 2;
			
			//ctx.fillStyle = bcolor;
			//ctx.fillRect(fx, fy, fw, fh);
			//ctx.strokeRect(fx, fy, fw, fh);
			
			ctx.font = labelFont;
			ctx.fillStyle = fontColor;
			ctx.fillText(c.label, fx, fy);
			
			// 画箭头
			drawArrow(points[last-1], points[last]);

		};

		/**
		 * 画圆角方块
		 * @param p 坐标点
		 * @param w 宽度
		 * @param h 高度
		 * @param fill 是否填充
		 * @param stroke 是否画线
		 * @param drawShadow 是否画阴影
		 */
		function drawRect(p, w, h, fill, stroke, drawShadow) {
			fill = typeof(fill) == "undefined" ? true : fill;
			stroke = typeof(stroke) == "undefined" ? true : stroke;
			drawShadow = typeof(drawShadow) == "undefined" ? true : drawShadow;
			if (drawShadow) {
				var offset = shadowOffset;
				var oldStyle = ctx.fillStyle;
				ctx.fillStyle = shadwColor;
				drawRect({x: p.x + offset, y: p.y + offset}, w, h, true, false, false);
				ctx.fillStyle = oldStyle;
			}

			var x = p.x,
				y = p.y,
				r = 5; // 圆角半径
			if (w < 2 * r) {
				r = w / 2;
			}
			if (h < 2 * r) {
				r = h / 2;
			}
			ctx.beginPath();
			ctx.moveTo(x + r, y);
			ctx.lineTo(x + w - r, y);
			ctx.quadraticCurveTo(x + w, y, x + w, y + r);
			ctx.lineTo(x + w, y + h - r);
			ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
			ctx.lineTo(x + r, y + h);
			ctx.quadraticCurveTo(x, y + h, x, y + h - r);
			ctx.lineTo(x, y + r);
			ctx.quadraticCurveTo(x, y, x + r, y);
			ctx.closePath();
			if (stroke) {
				ctx.stroke();
			}
			if (fill) {
				ctx.fill();
			} 
		}

		/**
		 * 画椭圆
		 * @param p 坐标点
		 * @param w 宽度
		 * @param h 高度
		 * @param fill 是否填充
		 * @param stroke 是否画线
		 * @param drawShadow 是否画阴影
		 */
		function drawEllipse(p, w, h, fill, stroke, drawShadow) {
			fill = typeof(fill) == "undefined" ? true : fill;
			stroke = typeof(stroke) == "undefined" ? true : stroke;
			drawShadow = typeof(drawShadow) == "undefined" ? true : drawShadow;
			if (drawShadow) {
				var offset = shadowOffset;
				var oldStyle = ctx.fillStyle;
				ctx.fillStyle = shadwColor;
				drawEllipse({x: p.x + offset, y: p.y + offset}, w, h, true, false, false);
				ctx.fillStyle = oldStyle;
			}
			var k = 0.5522848,
				a = w / 2,
				b = h / 2,
				ox = a * k, // 水平控制点偏移量
				oy = b * k, // 垂直控制点偏移量
				x = p.x + a, y = p.y + b;
			ctx.beginPath();
			// 从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线
			ctx.moveTo(x - a, y);
			ctx.bezierCurveTo(x - a, y - oy, x - ox, y - b, x, y - b);
			ctx.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y);
			ctx.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b);
			ctx.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y);
			ctx.closePath();
			if (fill) {
				ctx.fill();
			}
			if (stroke) {
				ctx.stroke();
			}
		}

		function getCrossPoint(cell, p1, p2) {
			var x = p2.x, 
				y = p2.y,
				dx = p2.x - p1.x,
				dy = p2.y - p1.y;
			if (dx == 0 && dy == 0) {
				return p2;
			}
			if (Math.abs(dy) > 0 && Math.abs(dx) / Math.abs(dy) < cell.width / cell.height) {
				var tan = dx / dy;
				if (p2.y < p1.y) {
					x += cell.height / 2 * tan;
					y += cell.height / 2;
				} else {
					x -= cell.height / 2 * tan;
					y -= cell.height / 2;
				}
			} else {
				var tan = Math.abs(dx) > 0 ? dy / dx : 1;
				if (p2.x < p1.x) {
					x += cell.width / 2;
					y += cell.width / 2 * tan;
				} else {
					x -= cell.width / 2;
					y -= cell.width / 2 * tan;
				}
			}
			return {x: x, y: y};
		}

		function getPortPoint(cell, no) {
			var x = cell.x,
				y = cell.y;
			switch(no) {
			case 1: 
				x = cell.x;
				y = cell.y;
				break;
			case 2:
				x += cell.width / 2;
				break;
			case 3:
				x += cell.width;
				break;
			case 4:
				y += cell.height / 2;
				break;
			case 5:
				x += cell.width;
				y += cell.height / 2;
				break;
			case 6:
				y += cell.height;
				break;
			case 7:
				x += cell.width / 2;
				y += cell.height;
				break;
			case 8:
				x += cell.width;
				y += cell.height;
				break;
			case 0:
				x += cell.width / 2;
				y += cell.height / 2;
				break;
			}
			return {x:x, y:y};
		}

		/**
		 * 画线
		 * @param p1
		 * @param p2
		 */
		function drawLine(p1, p2) {
			ctx.beginPath();
			ctx.moveTo(p1.x, p1.y);
			ctx.lineTo(p2.x, p2.y);
			ctx.stroke();
		}

		/**
		 * 画箭头
		 * @param p1
		 * @param p2
		 */
		function drawArrow(p1, p2) {
			var 
				awrad = Math.PI / 6, // 箭头角度(30度)
				arrowLen = 10,      // 箭头长度
				ap0 = toRelative(p1, p2), // 旋转源点(line.p1相对于line.p2的坐标)
				ap1 = rotateVec(ap0, awrad, arrowLen), // 第一端点(相对于line.p2的坐标)
				ap2 = rotateVec(ap0, -awrad, arrowLen); // 第二端点(相对于line.p2的坐标)

			ap1 = toAbsolute(ap1, p2);
			ap2 = toAbsolute(ap2, p2);

			drawLine(p2, ap1);
			drawLine(p2, ap2);
		}

		// 转换成相对坐标
		function toRelative(p, p0) {
			return {
				x: p.x - p0.x,
				y: p.y - p0.y
			};
		}

		// 转换回绝对坐标
		function toAbsolute(p, p0) {
			return {
				x: p.x + p0.x,
				y: p.y + p0.y
			};
		}

		/**
		 * 矢量旋转函数
		 * @param p 坐标源点
		 * @param ang 旋转角
		 * @param newLen 新长度
		 * @returns {x,y}
		 */
		function rotateVec(p, ang, newLen) {
			var vx = p.x * Math.cos(ang) - p.y * Math.sin(ang);
			var vy = p.x * Math.sin(ang) + p.y * Math.cos(ang);
			var d = Math.sqrt(vx * vx + vy * vy);
			if (Math.abs(d) > 0) {
				vx = vx / d * newLen;
				vy = vy / d * newLen;
			}
			return {
				x : vx,
				y : vy
			};
		}

		/**
		 * 计算一点相对于圆心旋转后的坐标
		 * @param c 圆心
		 * @param p 点
		 * @param r 旋转弧度
		 */
		function rotate(c, p, r) {
			return {
				x: (p.x - c.x) * Math.cos(r) - (p.y - c.y) * Math.sin(r) + c.x,
				y: (p.y - c.y) * Math.cos(r) + (p.x - c.x) * sin(r) + c.y
			};
		}
		
		$.extend(this, {
			drawWf: drawWf
		});
	}
	
	window.drawWf = function(canvas, xmlStr, lytStr, currentSteps, historySteps) {
		var wf = new WfCanvas(canvas, xmlStr, lytStr, currentSteps, historySteps);

		wf.drawWf();
	};
})();




  • 大小: 8.5 KB
  • src.zip (91.9 KB)
  • 下载次数: 342
   发表时间:2013-06-05  
目前好像都很少用osworkflow的了
0 请登录后投票
   发表时间:2014-03-18  
非常不错.
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics