根据jbpm4的.jpdl.xml流程定义文件,绘制出流程图
yy 于 2009年09月17日23:16:10
原本是想自己做个web的流程设计器,可惜没有什么时间,
也好,就先练习根据jbpm4的jpdl流程定义文件,绘制出流程图,
正好也学习学习java的绘图和一些忘记了的几何知识.
这也可应用在流程监控的展示中, 比如可以用不同的颜色标识出已经走过的transition.
这个只需要自己将已走的transition传递过来,然后再绘制函数中设置出不同的颜色即可
[这里就没做处理了]
<<src.zip>>
先看节点类的定义:
public class Node {
private String name;
private String type;
private Rectangle rectangle;
private List<Transition> transitions = new ArrayList<Transition>();
public Node(String name, String type) {
this.name = name;
this.type = type;
}
public Node(String name, String type, int x, int y, int w, int h) {
this.name = name;
this.type = type;
this.rectangle = new Rectangle(x, y, w, h);
}
public Rectangle getRectangle() {
return rectangle;
}
public void setRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addTransition(Transition transition) {
transitions.add(transition);
}
public List<Transition> getTransitions() {
return transitions;
}
public void setTransitions(List<Transition> transitions) {
this.transitions = transitions;
}
public int getX() {
return rectangle.x;
}
public int getY() {
return rectangle.y;
}
public int getCenterX() {
return (int) rectangle.getCenterX();
}
public int getCenterY() {
return (int) rectangle.getCenterY();
}
public int getWitdth() {
return rectangle.width;
}
public int getHeight() {
return rectangle.height;
}
}
其次是Transition的定义:
public class Transition {
private Point labelPosition;
private List<Point> lineTrace = new ArrayList<Point>();
private String label;
private String to;
public Transition(String label, String to) {
this.label = label;
this.to = to;
}
public Point getLabelPosition() {
return labelPosition;
}
public void setLabelPosition(Point labelPosition) {
this.labelPosition = labelPosition;
}
public List<Point> getLineTrace() {
return lineTrace;
}
public void setLineTrace(List<Point> lineTrace) {
this.lineTrace = lineTrace;
}
public void addLineTrace(Point lineTrace) {
if (lineTrace != null) {
this.lineTrace.add(lineTrace);
}
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
}
类JpdlModel
/**
* CopyRight (C) 2006-2009 yy
* @author yy
* @project jbpm
* @version 1.0
* @mail yy629_86 at 163 dot com
* @date 2009-9-6 下午06:00:14
* @description
*/
package sofocus.bpm.jbpm.jpdl.model;
import java.awt.Point;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class JpdlModel {
private Map<String, Node> nodes = new LinkedHashMap<String, Node>();
public static final int RECT_OFFSET_X = -7;
public static final int RECT_OFFSET_Y = -8;
public static final int DEFAULT_PIC_SIZE = 48;
private final static Map<String, Object> nodeInfos = new HashMap<String, Object>();
static {
nodeInfos.put("start", "start_event_empty.png");
nodeInfos.put("end", "end_event_terminate.png");
nodeInfos.put("end-cancel", "end_event_cancel.png");
nodeInfos.put("end-error", "end_event_error.png");
nodeInfos.put("decision", "gateway_exclusive.png");
nodeInfos.put("fork", "gateway_parallel.png");
nodeInfos.put("join", "gateway_parallel.png");
nodeInfos.put("state", null);
nodeInfos.put("hql", null);
nodeInfos.put("sql", null);
nodeInfos.put("java", null);
nodeInfos.put("script", null);
nodeInfos.put("task", null);
nodeInfos.put("sub-process", null);
nodeInfos.put("custom", null);
}
public JpdlModel(InputStream is) throws Exception {
this(new SAXReader().read(is).getRootElement());
}
@SuppressWarnings("unchecked")
private JpdlModel(Element rootEl) throws Exception {
for (Element el : (List<Element>) rootEl.elements()) {
String type = el.getQName().getName();
if (!nodeInfos.containsKey(type)) { // 不是可展示的节点
continue;
}
String name = null;
if (el.attribute("name") != null) {
name = el.attributeValue("name");
}
String[] location = el.attributeValue("g").split(",");
int x = Integer.parseInt(location[0]);
int y = Integer.parseInt(location[1]);
int w = Integer.parseInt(location[2]);
int h = Integer.parseInt(location[3]);
if (nodeInfos.get(type) != null) {
w = DEFAULT_PIC_SIZE;
h = DEFAULT_PIC_SIZE;
} else {
x -= RECT_OFFSET_X;
y -= RECT_OFFSET_Y;
w += (RECT_OFFSET_X + RECT_OFFSET_X);
h += (RECT_OFFSET_Y + RECT_OFFSET_Y);
}
Node node = new Node(name, type, x, y, w, h);
parserTransition(node, el);
nodes.put(name, node);
}
}
@SuppressWarnings("unchecked")
private void parserTransition(Node node, Element nodeEl) {
for (Element el : (List<Element>) nodeEl.elements("transition")) {
String label = el.attributeValue("name");
String to = el.attributeValue("to");
Transition transition = new Transition(label, to);
String g = el.attributeValue("g");
if (g != null && g.length() > 0) {
if (g.indexOf(":") < 0) {
transition.setLabelPosition(getPoint(g));
} else {
String[] p = g.split(":");
transition.setLabelPosition(getPoint(p[1]));
String[] lines = p[0].split(";");
for (String line : lines) {
transition.addLineTrace(getPoint(line));
}
}
}
node.addTransition(transition);
}
}
private Point getPoint(String exp) {
if (exp == null || exp.length() == 0) {
return null;
}
String[] p = exp.split(",");
return new Point(Integer.valueOf(p[0]), Integer.valueOf(p[1]));
}
public Map<String, Node> getNodes() {
return nodes;
}
public static Map<String, Object> getNodeInfos() {
return nodeInfos;
}
}
根据JpdlModel绘制出流程图
/**
* CopyRight (C) 2006-2009 yy
* @author yy
* @project Jbpm
* @version 1.0
* @mail yy629_86 at 163 dot com
* @date 2009-9-6 下午06:00:14
* @description
*/
package sofocus.bpm.jbpm.jpdl;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import sofocus.bpm.jbpm.jpdl.model.JpdlModel;
import sofocus.bpm.jbpm.jpdl.model.Node;
import sofocus.bpm.jbpm.jpdl.model.Transition;
/**
* @author yeyong
*
*/
public class JpdlModelDrawer {
public static final int RECT_OFFSET_X = JpdlModel.RECT_OFFSET_X;
public static final int RECT_OFFSET_Y = JpdlModel.RECT_OFFSET_Y;
public static final int RECT_ROUND = 25;
public static final int DEFAULT_FONT_SIZE = 12;
public static final Color DEFAULT_STROKE_COLOR = Color.decode("#03689A");
public static final Stroke DEFAULT_STROKE = new BasicStroke(2);
public static final Color DEFAULT_LINE_STROKE_COLOR = Color.decode("#808080");
public static final Stroke DEFAULT_LINE_STROKE = new BasicStroke(1);
public static final Color DEFAULT_FILL_COLOR = Color.decode("#F6F7FF");
private final static Map<String, Object> nodeInfos = JpdlModel.getNodeInfos();
public BufferedImage draw(JpdlModel jpdlModel) throws IOException {
Rectangle dimension = getCanvasDimension(jpdlModel);
BufferedImage bi = new BufferedImage(dimension.width, dimension.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bi.createGraphics();
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, dimension.width, dimension.height);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("宋体", Font.PLAIN, DEFAULT_FONT_SIZE);
g2.setFont(font);
Map<String, Node> nodes = jpdlModel.getNodes();
drawNode(nodes, g2, font);
drawTransition(nodes, g2);
return bi;
}
/**
* 获得图片的矩形大小
*
* @return
*/
private Rectangle getCanvasDimension(JpdlModel jpdlModel) {
Rectangle rectangle = new Rectangle();
Rectangle rect;
for (Node node : jpdlModel.getNodes().values()) {
rect = node.getRectangle();
if (rect.getMaxX() > rectangle.getMaxX()) {
rectangle.width = (int) rect.getMaxX();
}
if (rect.getMaxY() > rectangle.getMaxY()) {
rectangle.height = (int) rect.getMaxY();
}
for (Transition transition : node.getTransitions()) {
List<Point> trace = transition.getLineTrace();
for (Point point : trace) {
if (rectangle.getMaxX() < point.x) {
rectangle.width = point.x;
}
if (rectangle.getMaxY() < point.y) {
rectangle.height = point.y;
}
}
}
}
rectangle.width += 60;
rectangle.height += 20;
return rectangle;
}
/**
* @param g2
* @throws IOException
*/
private void drawTransition(Map<String, Node> nodes, Graphics2D g2) throws IOException {
g2.setStroke(DEFAULT_LINE_STROKE);
g2.setColor(DEFAULT_LINE_STROKE_COLOR);
for (Node node : nodes.values()) {
for (Transition transition : node.getTransitions()) {
String to = transition.getTo();
Node toNode = nodes.get(to);
List<Point> trace = new LinkedList<Point>(transition.getLineTrace());
int len = trace.size() + 2;
trace.add(0, new Point(node.getCenterX(), node.getCenterY()));
trace.add(new Point(toNode.getCenterX(), toNode.getCenterY()));
int[] xPoints = new int[len];
int[] yPoints = new int[len];
for (int i = 0; i < len; i++) {
xPoints[i] = trace.get(i).x;
yPoints[i] = trace.get(i).y;
}
final int taskGrow = 4;
final int smallGrow = -2;
int grow = 0;
if (nodeInfos.get(node.getType()) != null) {
grow = smallGrow;
} else {
grow = taskGrow;
}
Point p = GeometryUtils.getRectangleLineCrossPoint(node.getRectangle(), new Point(xPoints[1],
yPoints[1]), grow);
if (p != null) {
xPoints[0] = p.x;
yPoints[0] = p.y;
}
if (nodeInfos.get(toNode.getType()) != null) {
grow = smallGrow;
} else {
grow = taskGrow;
}
p = GeometryUtils.getRectangleLineCrossPoint(toNode.getRectangle(), new Point(xPoints[len - 2],
yPoints[len - 2]), grow);
if (p != null) {
xPoints[len - 1] = p.x;
yPoints[len - 1] = p.y;
}
g2.drawPolyline(xPoints, yPoints, len);
drawArrow(g2, xPoints[len - 2], yPoints[len - 2], xPoints[len - 1], yPoints[len - 1]);
String label = transition.getLabel();
if (label != null && label.length() > 0) {
int cx, cy;
if (len % 2 == 0) {
cx = (xPoints[len / 2 - 1] + xPoints[len / 2]) / 2;
cy = (yPoints[len / 2 - 1] + yPoints[len / 2]) / 2;
} else {
cx = xPoints[len / 2];
cy = yPoints[len / 2];
}
Point labelPoint = transition.getLabelPosition();
if (labelPoint != null) {
cx += labelPoint.x;
cy += labelPoint.y;
}
cy -= RECT_OFFSET_Y + RECT_OFFSET_Y / 2;
g2.drawString(label, cx, cy);
}
}
}
}
private void drawArrow(Graphics2D g2, int x1, int y1, int x2, int y2) {
final double len = 8.0;
double slopy = Math.atan2(y2 - y1, x2 - x1);
double cosy = Math.cos(slopy);
double siny = Math.sin(slopy);
int[] xPoints = { 0, x2, 0 };
int[] yPoints = { 0, y2, 0 };
double a = len * siny, b = len * cosy;
double c = len / 2.0 * siny, d = len / 2.0 * cosy;
xPoints[0] = x2 - (int) (b + c);
yPoints[0] = y2 - (int) (a - d);
xPoints[2] = x2 - (int) (b - c);
yPoints[2] = y2 - (int) (d + a);
g2.fillPolygon(xPoints, yPoints, 3);
}
/**
* @param g2
* @throws IOException
*/
private void drawNode(Map<String, Node> nodes, Graphics2D g2, Font font) throws IOException {
for (Node node : nodes.values()) {
String name = node.getName();
if (nodeInfos.get(node.getType()) != null) {
BufferedImage bi2 = ImageIO.read(getClass().getResourceAsStream(
"/icons/48/" + nodeInfos.get(node.getType())));
g2.drawImage(bi2, node.getX(), node.getY(), null);
} else {
int x = node.getX();
int y = node.getY();
int w = node.getWitdth();
int h = node.getHeight();
g2.setColor(DEFAULT_FILL_COLOR);
g2.fillRoundRect(x, y, w, h, RECT_ROUND, RECT_ROUND);
g2.setColor(DEFAULT_STROKE_COLOR);
g2.setStroke(DEFAULT_STROKE);
g2.drawRoundRect(x, y, w, h, RECT_ROUND, RECT_ROUND);
FontRenderContext frc = g2.getFontRenderContext();
Rectangle2D r2 = font.getStringBounds(name, frc);
int xLabel = (int) (node.getX() + ((node.getWitdth() - r2.getWidth()) / 2));
int yLabel = (int) ((node.getY() + ((node.getHeight() - r2.getHeight()) / 2)) - r2.getY());
g2.setStroke(DEFAULT_LINE_STROKE);
g2.setColor(Color.black);
g2.drawString(name, xLabel, yLabel);
}
}
}
}
工具类,用来计算一些坐标的
/**
* CopyRight (C) 2006-2009 yy
* @author yy
* @project jbpm
* @version 1.0
* @mail yy629_86 at 163 dot com
* @date 2009-9-11 上午06:16:26
* @description
*/
package sofocus.bpm.jbpm.jpdl;
import java.awt.Point;
import java.awt.Rectangle;
/**
* @author yeyong
*
*/
public class GeometryUtils {
/**
* 获得直线(x1,y1)-(x2,y2)的斜率
*
* @param x1
* @param y1
* @param x2
* @param y2
* @return
*/
public static double getSlope(int x1, int y1, int x2, int y2) {
return ((double) y2 - y1) / (x2 - x1);
}
/**
* 获得直线(x1,y1)-(x2,y2)的y轴截距
*
* @param x1
* @param y1
* @param x2
* @param y2
* @return
*/
public static double getYIntercep(int x1, int y1, int x2, int y2) {
return y1 - x1 * getSlope(x1, y1, x2, y2);
}
/**
* 获得矩形的中点
*
* @param rect
* @return
*/
public static Point getRectangleCenter(Rectangle rect) {
return new Point((int) rect.getCenterX(), (int) rect.getCenterY());
}
/**
* 获得矩形中心p0与p1的线段和矩形的交点
*
* @param rectangle
* @param p1
* @return
*/
public static Point getRectangleLineCrossPoint(Rectangle rectangle, Point p1, int grow) {
Rectangle rect = rectangle.getBounds();
rect.grow(grow, grow);
Point p0 = GeometryUtils.getRectangleCenter(rect);
if (p1.x == p0.x) {
if (p1.y < p0.y) {
return new Point(p0.x, rect.y);
}
return new Point(p0.x, rect.y + rect.height);
}
if (p1.y == p0.y) {
if (p1.x < p0.x) {
return new Point(rect.x, p0.y);
}
return new Point(rect.x + rect.width, p0.y);
}
double slope = GeometryUtils.getSlope(p0.x, p0.y, rect.x, rect.y);
double slopeLine = GeometryUtils.getSlope(p0.x, p0.y, p1.x, p1.y);
double yIntercep = GeometryUtils.getYIntercep(p0.x, p0.y, p1.x, p1.y);
if (Math.abs(slopeLine) > slope - 1e-2) {
if (p1.y < rect.y) {
return new Point((int) ((rect.y - yIntercep) / slopeLine), rect.y);
} else {
return new Point((int) ((rect.y + rect.height - yIntercep) / slopeLine), rect.y + rect.height);
}
}
if (p1.x < rect.x) {
return new Point(rect.x, (int) (slopeLine * rect.x + yIntercep));
} else {
return new Point(rect.x + rect.width, (int) (slopeLine * (rect.x + rect.width) + yIntercep));
}
}
}
测试
public static void main(String[] args) throws Exception {
JpdlModel jpdlModel = new JpdlModel (JpdlModel.class.getResource("loan.jpdl.xml").getPath());
ImageIO.write(new JpdlModelDrawer().draw(jpdlModel), "png", new File("c:/loan.png"));
}
对执行生成的图片与jbpm4.1插件生成的图片进行对比
可以看出,两者几乎差不多的,只是汉字比较模糊,不知道怎么设置...