`

FoxBPM 系列之-SVG输出浅析

阅读更多

为什么添加SVG支持?

       

       可伸缩矢量图形 (Scalable Vector Graphics)简称SVG,相对于普通的PNG图形文件,SVG格式有更好的展示效果,可以明显增加用户的体验。FoxBPM流程引擎同时支持PNG和SVG两种格式的输出。开发者可以根据用户需求动态的抉择。针对BPM产品SVG本身有着更多的优势:首先SVG本质就是一个XML,其对应的字符串可以直接在支持SVG的浏览器中展示,所以相对普通的图形文件SVG可以更加容易的输出到前端浏览器,在网络带宽不足的情况下这个优势就更加明显; 其次既然SVG本身是一个XML,那么我们就可以在前端像操作其他的DOM模型一样操作SVG,从而我们在浏览器前端也可以动态的改变流程图内容,从而添加更多功能(比如流程实例的运行轨迹,运行状态等)。

 

 主要知识点和难点:

        主要知识点包括:采用JAXB实现XML 和POJO之间的映射,VO的克隆,三次贝塞尔曲线的控制点计算,线条拐点的中心点计算等等。相关知识点核心代码如下所示:

 

VO对象克隆方法:

 /**
	 * DefsVO克隆 
	 * @param DefsVO 原对象
	 * @return clone之后的对象
	 */
	public final static GVO cloneGVO(GVO gVo) {
		return (GVO) clone(gVo);
	} 
	public final static DefsVO cloneDefsVO(DefsVO defsVo) {
		return (DefsVO) clone(defsVo);
	} 
 /**
	 * SvgVO模板对象需要多次引用,所以要克隆,避免产生问题 
	 * @param SvgVO  原对象
	 * @return clone之后的对象
	 */
	public final static SvgVO cloneSVGVo(SvgVO svgVo) {
		return (SvgVO) clone(svgVo);
	}        
 /**
	 * 克隆对象 
	 * @param object 原对象
	 * @return 目标对象
	 */
	public final static Object clone(Object object) {
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		ObjectInputStream ois = null;
		Object cloneObject = null;
		try {
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			oos.writeObject(object);
			ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
			cloneObject = ois.readObject();
		} catch (Exception e) {
			throw new FoxBPMException("SVG对象G节点克隆出现问题", e);
		} finally {
			try {
				if (bos != null) {
					bos.close();
				}
				if (oos != null) {
					oos.close();
				}
				if (ois != null) {
					ois.close();
				}
			} catch (Exception e) {
				throw new FoxBPMException("克隆之后关闭对象流时出现问题", e);
			}
		}

		return cloneObject;
	}

 

 

   JAXB映射方法(templateName是bpmn2.0官网提供的SVG模板名称):

 /**
	 * 第一次需要从svg文档加载
	 * 
	 * @param templateName
	 */
	private void init(String templateName) {
		try {
			JAXBContext context = JAXBContext.newInstance(SvgVO.class);
			Unmarshaller unMarshaller = context.createUnmarshaller();
			SAXParserFactory factory = SAXParserFactory.newInstance();
			// 解析的时候忽略SVG命名空间,否则会出错
			factory.setNamespaceAware(true);
			XMLReader reader = factory.newSAXParser().getXMLReader();
			String sourcePath = new StringBuffer(BPMN_PATH).append(FILE_SPERATOR)
					.append(templateName).toString();
			Source source = new SAXSource(reader, new InputSource(
					ReflectUtil.getResourceAsStream(sourcePath)));
			VONode object = (VONode) unMarshaller.unmarshal(source);
                        //将模板VO对象加入系统缓存
			this.svgTemplets.put(templateName, object);
		} catch (Exception e) {
			throw new FoxBPMException("template svg file load exception", e);
		} finally {
		}

	}
 /**
	 * 操作之后的SVG转化成String字符串 
	 * @param svgVo 容器对象
	 * @return SVG字符串
	 */
	public final static String createSVGString(VONode svgVo) {
		try {
			JAXBContext context = JAXBContext.newInstance(SvgVO.class);
			Marshaller marshal = context.createMarshaller();
			marshal.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			StringWriter writer = new StringWriter();

			marshal.marshal(svgVo, writer);
			return writer.toString();
		} catch (Exception e) {
			throw new FoxBPMException("svg object convert to String exception", e);
		}
	}

 

 

    三次贝塞尔曲线的控制点以及线条中心点计算方法:

/**
 * Copyright 1996-2014 FoxBPM ORG.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * @author MAENLIANG
 */
package org.foxbpm.engine.impl.diagramview.svg;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * 坐标工具类 
 * @author MAENLIANG
 * @date 2014-06-19
 * 
 */
public final class PointUtils {
	/**
	 * X、Y允许的最大偏移量,超过最大偏移量需要设置文本相对线条的中心位置,如果小于这个         * 值则不需要设置
	 */
	private static final float X_Y_LOCATION_MAXSHIFT = 100F;
	// TODO后续改善成动态规划算法
	/**
	 * 默认圆角的大小,暂时分三个梯度
	 */
	private final static float SEQUENCE_ROUNDCONTROL_FLAG = 15.0f;
	private final static float SEQUENCE_ROUNDCONTROL_MIN_FLAG = 3.0f;
	private final static float NONE_SCALE = 1.0f;
	
	/**
	 * 计算三次贝塞尔曲线的起始点,控制点以及终点 
	 * @param start
	 * @param center
	 * @param end
	 * @return 起始点 控制点 终点坐标数组
	 */
	public final static Point[] caclBeralPoints(Point start, Point center, Point end) {
		float lengthStartHerizon = 0.0f;
		float lengthStartVertical = 0.0f;
		float lengthStartCenter = 0.0f;
		float lengthEndHerizon = 0.0f;
		float lengthEndVertical = 0.0f;
		float lengthEndCenter = 0.0f;
		
		lengthStartHerizon = Math.abs(start.getX() - center.getX());
		lengthStartVertical = Math.abs(start.getY() - center.getY());
		lengthStartCenter = (float) Math.sqrt(lengthStartHerizon * lengthStartHerizon
		        + lengthStartVertical * lengthStartVertical);
		lengthEndHerizon = Math.abs(end.getX() - center.getX());
		lengthEndVertical = Math.abs(end.getY() - center.getY());
		lengthEndCenter = (float) Math.sqrt(lengthEndHerizon * lengthEndHerizon + lengthEndVertical
		        * lengthEndVertical);
		
		float scale = SEQUENCE_ROUNDCONTROL_FLAG;
		if (lengthEndCenter <= SEQUENCE_ROUNDCONTROL_FLAG
		        || lengthStartCenter <= SEQUENCE_ROUNDCONTROL_FLAG) {
			if (lengthEndCenter <= SEQUENCE_ROUNDCONTROL_MIN_FLAG
			        || lengthStartCenter <= SEQUENCE_ROUNDCONTROL_MIN_FLAG) {
				scale = NONE_SCALE;
			} else {
				scale = SEQUENCE_ROUNDCONTROL_MIN_FLAG;
			}
			
		}
		float controlScale = scale / 3;
		Point[] points = new Point[4];
		points[0] = caclBeralControlPoint(start, center, scale, lengthStartHerizon, lengthStartVertical, lengthStartCenter);
		points[1] = caclBeralControlPoint(start, center, controlScale, lengthStartHerizon, lengthStartVertical, lengthStartCenter);
		points[2] = caclBeralControlPoint(end, center, controlScale, lengthEndHerizon, lengthEndVertical, lengthEndCenter);
		points[3] = caclBeralControlPoint(end, center, scale, lengthEndHerizon, lengthEndVertical, lengthEndCenter);
		return points;
	}
	
	/**
	 * 计算三次贝塞尔曲线控制点
	 * 
	 * @param startEndPoint
	 * @param center
	 * @return
	 */
	private final static Point caclBeralControlPoint(Point startEndPoint, Point center,
	    float scale, float lengthHerizon, float lengthVertical, float lengthCenter) {
		float startX = 0.0f;
		float startY = 0.0f;
		
		if (startEndPoint.getX() > center.getX()) {
			startX = ((scale * lengthHerizon) / lengthCenter) + center.getX();
		} else {
			startX = (((lengthCenter - scale) * lengthHerizon) / lengthCenter)
			        + startEndPoint.getX();
		}
		if (startEndPoint.getY() > center.getY()) {
			startY = ((scale * lengthVertical) / lengthCenter) + center.getY();
		} else {
			startY = (((lengthCenter - scale) * lengthVertical) / lengthCenter)
			        + startEndPoint.getY();
		}
		
		Point resultPoint = new Point(Math.round(startX), Math.round(startY));
		return resultPoint;
		
	}
	
	/**
	 * 计算中心点位置,包括复杂情况,和简单情况
	 * 
	 * @param pointList
	 * @return
	 */
	public final static Point caclDetailCenterPoint(List<Point> pointList) {
		Float[][] xyArrays = getXYLocationArray(pointList);
		Float[] xArrays = xyArrays[0];
		Float[] yArrays = xyArrays[1];
		float xShift = calCariance(xArrays);
		float yShift = calCariance(yArrays);
		// 如果偏差过大就计算中心点
		if (xShift > X_Y_LOCATION_MAXSHIFT && yShift < X_Y_LOCATION_MAXSHIFT) {
			// X坐标拐点幅度大,Y不大,计算线条X中心位置,Y取平均值
			return caclXCenter(xArrays, yArrays);
			
		} else if (yShift > X_Y_LOCATION_MAXSHIFT && xShift < X_Y_LOCATION_MAXSHIFT) {
			// Y坐标拐点幅度大,X不大,计算线条Y中心位置,X取平均值
			return caclYCenter(xArrays, yArrays);
			
		} else if (yShift > X_Y_LOCATION_MAXSHIFT && xShift > X_Y_LOCATION_MAXSHIFT) {
			// XY拐点幅度都比较大
			return caclCenterPoint(pointList);
		} 
		// 如果没有计算中心点
		return null; 
	}
	
	/**
	 * 勾股定理, 取线段长度
	 * 
	 * @param pointA
	 * @param pointB
	 * @return
	 */
	public final static float segmentLength(Point pointA, Point pointB) {
		double length = 0;
		float width = pointA.getX() - pointA.getX();
		float height = pointA.getY() - pointB.getY();
		double dWidth = (double) width;
		double dHeight = (double) height; 
		length = Math.sqrt(Math.pow(dWidth, 2) + Math.pow(dHeight, 2));
		return (float) length;
	}
	
	/**
	 * 统计所有线段的长度
	 * 
	 * @param pointList
	 * @return
	 */
	private final static List<Float> getSegmentsLength(List<Point> pointList) {
		List<Float> segList = new ArrayList<Float>();
		
		float segLen = 0f;
		int size = pointList.size();
		size = size - 1;
		Point pointA = null;
		Point pointB = null;
		for (int i = 0; i < size; i++) {
			pointA = pointList.get(i);
			pointB = pointList.get(i + 1);
			
			segLen = segmentLength(pointA, pointB);
			segList.add(segLen);
		}
		
		return segList;
	}
	
	/**
	 * 计算所有x坐标值或者y坐标值的方差,判断是否需要重新设置所有拐点的中心点
	 * 
	 * @param array
	 * @return
	 */
	public final static Float calCariance(Float[] array) {
		int arrayLength = array.length;
		Float ave = 0.0F;
		for (int i = 0; i < arrayLength; i++) {
			ave += array[i];
		}
		ave /= arrayLength;
		Float sum = 0.0F;
		for (int i = 0; i < arrayLength; i++) {
			sum += (array[i] - ave) * (array[i] - ave);
		}
		sum = sum / arrayLength;
		return sum;
	}
	
	/**
	 * 获取所有点的XY坐标
	 * 
	 * @param pointList
	 * @return
	 */
	private final static Float[][] getXYLocationArray(List<Point> pointList) {
		int pointListsize = pointList.size();
		Float[][] xyLocationArray = new Float[2][pointListsize];
		Iterator<Point> iterator = pointList.iterator();
		for (int i = 0; i < pointListsize; i++) {
			Point next = iterator.next();
			xyLocationArray[0][i] = next.getX();
			xyLocationArray[1][i] = next.getY();
		}
		return xyLocationArray;
	}
	
	/**
	 * 计算线条在X方向上的中心位置
	 * 
	 * @param xArrays
	 * @param yArrays
	 * @return
	 */
	public final static Point caclXCenter(Float[] xArrays, Float[] yArrays) {
		Arrays.sort(xArrays);
		int length = yArrays.length;
		Float xCenter = xArrays[0] + (xArrays[xArrays.length - 1] - xArrays[0]) / 2;
		Float tempV = 0.0f;
		for (int i = 0; i < length; i++) {
			tempV += yArrays[i];
		}
		Float y = tempV / length;
		return new Point(xCenter, y);
	}
	
	/**
	 * 计算线条在y方向上的中心位置
	 * 
	 * @param xArrays
	 * @param yArrays
	 * @return
	 */
	public final static Point caclYCenter(Float[] xArrays, Float[] yArrays) {
		Arrays.sort(yArrays);
		Float yCenter = yArrays[0] + (yArrays[yArrays.length - 1] - yArrays[0]) / 2;
		Float tempV = 0.0f;
		int length = xArrays.length;
		for (int i = 0; i < length; i++) {
			tempV += xArrays[i];
		}
		Float x = tempV / length;
		return new Point(yCenter, x);
	}
	
	/**
	 * 计算中心点
	 * 
	 * @param svgLine
	 * @return
	 */
	public final static Point caclCenterPoint(List<Point> pointList) {
		List<Float> segList = PointUtils.getSegmentsLength(pointList);
		float totalLength = 0f;
		for (float seg : segList) {
			totalLength += seg;
		}
		
		float centerLen = totalLength / 2;
		
		int keySegIndex = 0;
		
		float keySeg = 0f;
		float tempLen = 0f;
		for (float seg : segList) {
			keySeg = seg;
			keySegIndex++;
			if ((tempLen + keySeg) > centerLen) {
				break;
			} else {
				tempLen += keySeg;
			}
			
		}
		
		float lastLen = centerLen - tempLen;
		
		Point startPoint = pointList.get(keySegIndex - 1);
		Point endPoint = pointList.get(keySegIndex); 
		float scale = lastLen / keySeg; 
		float x = startPoint.getX() + (endPoint.getX() - startPoint.getX()) * scale;
		float y = startPoint.getY() + (endPoint.getY() - startPoint.getY()) * scale;
		Point point = new Point(x, y);
		return point;
	}
}

 

模块结构详细说明:

结构如下图所示:


         1、模块构建基于工厂和创建者设计模式,独立于SVG,对其他矢量标记语言扩展友好。

 

         2、FoxBpmnViewBuilder为VO属性构建的上层接口,当创建新的元素时,可以针对元素的特性进行实现。流程引擎后期的维护和功能扩展也主要是针对该接口的实现。

 

         3、AbstractFlowElementVOFactory也是VO属性构建的上层抽象类,负责VO对象的获取、过滤、以及根据特定元素选择特定Builder构建VO属性。

 

         4、svg包模块是针对FoxBpmnViewBuilder和AbstractFlowElementVOFactory的svg实现,包括svg工厂、svg 构建者、svg VO模型、svg工具类。

 

SVG构建详细说明:

        1、流程引擎启动初始化的时候,将BPMN2.0官网提供的所有SVG模版(其模版以单个组件的形式提供)初始化,采用映射工具JAXB 将模版转化成VO对象并且加入缓存。

 

    VO定义如下代码所示: 

/**
 * Copyright 1996-2014 FoxBPM ORG.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * @author MAENLIANG
 */
package org.foxbpm.engine.impl.diagramview.vo;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;

/**
 * SVG对象的超类,包括 节点SVG对象,连接线SVG对象
 * 
 * @author MAENLIANG
 * @date 2014-06-10
 * 
 */
@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class VONode implements Serializable { 
	private static final long serialVersionUID = -817550474604039751L;
	/**
	 * ID Name 用于标记流程元素ID用于 流程图的前端操作
	 */
	@XmlAttribute(name = "id")
	protected String id;
	@XmlAttribute(name = "name")
	protected String name; 
	@XmlAttribute(name = "style")
	protected String style; 
	@XmlAttribute(name = "fill")
	protected String fill; 
	@XmlAttribute(name = "stroke")
	protected String stroke; 
	@XmlAttribute(name = "stroke-width")
	protected Float strokeWidth;
	@XmlAttribute(name = "stroke-linecap")
	protected String strokeLinecap;
	@XmlAttribute(name = "stroke-linejoin")
	protected String strokeLinejoin;
	@XmlAttribute(name = "width")
	protected Float width;
	@XmlAttribute(name = "height")
	protected Float height;
	@XmlAttribute(name = "x")
	protected Float x;
	@XmlAttribute(name = "y")
	protected Float y;
	@XmlValue
	protected String elementValue;

	public String getElementValue() {
		return elementValue;
	} 
	public void setElementValue(String elementValue) {
		this.elementValue = elementValue;
	} 
	public Float getX() {
		return x;
	} 
	public void setX(Float x) {
		this.x = x;
	} 
	public Float getY() {
		return y;
	} 
	public void setY(Float y) {
		this.y = y;
	} 
	public String getStyle() {
		return style;
	} 
	public void setStyle(String style) {
		this.style = style;
	} 
	public String getFill() {
		return fill;
	} 
	public void setFill(String fill) {
		this.fill = fill;
	} 
	public String getStroke() {
		return stroke;
	} 
	public void setStroke(String stroke) {
		this.stroke = stroke;
	} 
	public float getStrokeWidth() {
		return strokeWidth;
	} 
	public void setStrokeWidth(float strokeWidth) {
		this.strokeWidth = strokeWidth;
	} 
	public float getWidth() {
		return width;
	} 
	public void setWidth(float width) {
		this.width = width;
	} 
	public float getHeight() {
		return height;
	} 
	public void setHeight(float height) {
		this.height = height;
	} 
	public String getId() {
		return id;
	} 
	public void setId(String id) {
		this.id = id;
	} 
	public String getName() {
		return name;
	} 
	public void setName(String name) {
		this.name = name;
	} 
	public String getStrokeLinecap() {
		return strokeLinecap;
	} 
	public void setStrokeLinecap(String strokeLinecap) {
		this.strokeLinecap = strokeLinecap;
	} 
	public String getStrokeLinejoin() {
		return strokeLinejoin;
	} 
	public void setStrokeLinejoin(String strokeLinejoin) {
		this.strokeLinejoin = strokeLinejoin;
	}
}
 

 

           2、SVG模版相对独立,当创建时流程引擎提供包含所有流程元素定义的流程定义对象,流程元素包括 节点、线条、泳道、部件,其中节点包括活动任务、事件、网关。SVG创建者首先创建一个VO集合,然后根据流程元素从缓存中获取每个元素对应的自己的VO对象,获取VO 对象之后根据每个元素的式样属性来设置其VO属性,需要实现的接口为FoxBpmnViewBuilder,VO对象属性设置好之后添加到VO集合中。

 

FoxBpmnViewBuilder代码如下:

/**
 * Copyright 1996-2014 FoxBPM ORG.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * @author MAENLIANG
 */
package org.foxbpm.engine.impl.diagramview.builder;

import java.util.List;

import org.foxbpm.engine.impl.diagramview.svg.Point;

/**
 * 流程图形信息构造接口
 * 
 * @author MAENLIANG
 * @date 2014-06-10
 */
public interface FoxBpmnViewBuilder {
	public static final String SEQUENCE_STROKEWIDTH_DEFAULT = "2";
	public static final String COLOR_FLAG = "#";
	public static final String STROKE_DEFAULT = "black";
	public static final String STROKEWIDTH_DEFAULT = "1";
	public static final int LINEARGRADIENT_INDEX = 0;
	public static final String COMMA = ",";
	public static final String BACK_GROUND_PREFIX = "url(#";
	public static final String BACK_GROUND_SUFFIX = ") #";
	
	public static final String BRACKET_SUFFIX = ")";
	public static final String TRANSLANT_PREFIX = "translate(";
	public static final String ARIAL = "arial";
	
	

	/**
	 * 设置节点信息
	 */
	public void setWayPoints(List<Point> pointList); 
	public void setWidth(float width); 
	public void setHeight(float height); 
	public void setXAndY(float x, float y); 
	public void setStroke(String stroke); 
	public void setStrokeWidth(float strokeWidth); 
	public void setFill(String fill); 
	public void setID(String id); 
	public void setName(String name); 
	public void setStyle(String style);

	/**
	 * 设置文本信息
	 */
	public void setText(String text); 
	public void setTextFont(String font); 
	public void setTextStrokeWidth(float textStrokeWidth); 
	public void setTextX(float textX); 
	public void setTextY(float textY); 
	public void setTextFontSize(String textFontSize); 
	public void setTextStroke(String textStroke); 
	public void setTextFill(String textFill); 

        /**
         * 设置子类型
         */
	public void setTypeStroke(String stroke); 
	public void setTypeStrokeWidth(float strokeWidth); 
	public void setTypeFill(String fill); 
	public void setTypeStyle(String style);

	/**
	 * 泳道负责重写该方法 setTextLocationByHerizonFlag
	 */
	public void setTextLocationByHerizonFlag(boolean herizonFlag);
}

 

 

          3、所有元素的VO对象设置好之后,再根据流程定义提供的基本信息创建SVG模版对象,然后将所有的VO对象克隆并且添加到模版对象,然后将模版对象用JAXB 工具转化成最终的SVG XML文档

 

   流程定义对象所包含的元素如下所以:

	List<KernelFlowNodeImpl> flowNodes = new ArrayList<KernelFlowNodeImpl>(); 
	Map<String, KernelSequenceFlowImpl> sequenceFlows = new HashMap<String, KernelSeque        nceFlowImpl>(); 
	List<KernelLaneSet> laneSets = new ArrayList<KernelLaneSet>();
	List<KernelArtifact> artifacts = new ArrayList<KernelArtifact>();
 

 

foxbpm流程引擎构建SVG示意图如下:



 
 

 

  • 大小: 32.4 KB
  • 大小: 62.7 KB
  • 大小: 15.5 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics