在使用camunda的时候,如果我们不是通过客户端上传bpmn文件,而是通过代码生成所有的流程节点的话,会遇到一个问题,那就是生成的内容是不带Diagram属性的,也就是说,虽然有流程定义,但是无法渲染出图。
例如,我们要实现一个请假的流程 大于三天需要经过另外一个审批审批步骤。示意图如下
下面,我们通过代码来生成相关的结构
@Test
public void article_demo_test(){
ProcessBuilder processBuilder = Bpmn.createExecutableProcess();
Process process = processBuilder.getElement();
ModelInstance modelInstance = process.getModelInstance();
//region 开始事件
StartEvent startEvent = modelInstance.newInstance(StartEvent.class);
startEvent.setId("start");
startEvent.setName("start");
process.addChildElement(startEvent);
//endregion
//region 组长审批
UserTask userTask = modelInstance.newInstance(UserTask.class);
userTask.setId("leader-approve");
userTask.setName("组长审批");
process.addChildElement(userTask);
//endregion
//region 开始事件-组长审批连接线
SequenceFlow startToUserTaskFlow = modelInstance.newInstance(SequenceFlow.class);
startToUserTaskFlow.setId("start-leader-approve");
process.addChildElement(startToUserTaskFlow);
startToUserTaskFlow.setSource(startEvent);
startEvent.getOutgoing().add(startToUserTaskFlow);
startToUserTaskFlow.setTarget(userTask);
userTask.getIncoming().add(startToUserTaskFlow);
//endregion
//region 排他网关
ExclusiveGateway exclusiveGateway = modelInstance.newInstance(ExclusiveGateway.class);
exclusiveGateway.setId("gateway");
exclusiveGateway.setName("排他网关");
process.addChildElement(exclusiveGateway);
//endregion
//region 组长审批到排他网关连接线
SequenceFlow taskToGatewayFlow = modelInstance.newInstance(SequenceFlow.class);
startToUserTaskFlow.setId("task-gateway");
process.addChildElement(taskToGatewayFlow);
taskToGatewayFlow.setSource(userTask);
userTask.getOutgoing().add(taskToGatewayFlow);
taskToGatewayFlow.setTarget(exclusiveGateway);
exclusiveGateway.getIncoming().add(taskToGatewayFlow);
//endregion
//region 经理审批
UserTask managerTask = modelInstance.newInstance(UserTask.class);
managerTask.setId("manager-approve");
managerTask.setName("经理审批");
process.addChildElement(managerTask);
//endregion
//region 网关到经理审批连接线
SequenceFlow gatewayToManagerTaskFlow = modelInstance.newInstance(SequenceFlow.class);
startToUserTaskFlow.setId("gateway-manager-approve");
process.addChildElement(gatewayToManagerTaskFlow);
gatewayToManagerTaskFlow.setSource(exclusiveGateway);
exclusiveGateway.getOutgoing().add(gatewayToManagerTaskFlow);
gatewayToManagerTaskFlow.setTarget(managerTask);
managerTask.getIncoming().add(gatewayToManagerTaskFlow);
//endregion
//region 结束
EndEvent endEvent = modelInstance.newInstance(EndEvent.class);
endEvent.setId("end");
endEvent.setName("end");
process.addChildElement(endEvent);
//endregion
//region 网关到结束接线
SequenceFlow gatewayToEndFlow = modelInstance.newInstance(SequenceFlow.class);
gatewayToEndFlow.setId("gateway-end");
process.addChildElement(gatewayToEndFlow);
gatewayToEndFlow.setSource(exclusiveGateway);
exclusiveGateway.getOutgoing().add(gatewayToEndFlow);
gatewayToEndFlow.setTarget(endEvent);
endEvent.getIncoming().add(gatewayToEndFlow);
//endregion
//region 经理审批到结束接线
SequenceFlow managerTaskToEndFlow = modelInstance.newInstance(SequenceFlow.class);
managerTaskToEndFlow.setId("manager-approve-end");
process.addChildElement(managerTaskToEndFlow);
managerTaskToEndFlow.setSource(managerTask);
managerTask.getOutgoing().add(managerTaskToEndFlow);
managerTaskToEndFlow.setTarget(endEvent);
managerTask.getIncoming().add(managerTaskToEndFlow);
//endregion
BpmnModelInstance bpmnModelInstance = processBuilder.done();
String xml = Bpmn.convertToString(bpmnModelInstance);
log.info(xml);
}
生成的xml内容如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="definitions_c57912b1-59da-44c2-a621-2eebf997cc39" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="process_5b6570ec-f3f2-4572-bce3-8cf466f056de" isExecutable="true">
<startEvent id="start" name="start">
<outgoing>gateway-manager-approve</outgoing>
</startEvent>
<userTask id="leader-approve" name="组长审批">
<incoming>gateway-manager-approve</incoming>
<outgoing>sequenceFlow_51905a5f-844a-4a76-ab0a-3fcdd3e8594b</outgoing>
</userTask>
<sequenceFlow id="gateway-manager-approve" sourceRef="start" targetRef="leader-approve"/>
<exclusiveGateway id="gateway" name="排他网关">
<incoming>sequenceFlow_51905a5f-844a-4a76-ab0a-3fcdd3e8594b</incoming>
<outgoing>sequenceFlow_da7d27d4-d7e9-4c0f-b5a8-df8e470e2e6d</outgoing>
<outgoing>gateway-end</outgoing>
</exclusiveGateway>
<sequenceFlow id="sequenceFlow_51905a5f-844a-4a76-ab0a-3fcdd3e8594b" sourceRef="leader-approve" targetRef="gateway"/>
<userTask id="manager-approve" name="经理审批">
<incoming>sequenceFlow_da7d27d4-d7e9-4c0f-b5a8-df8e470e2e6d</incoming>
<incoming>manager-approve-end</incoming>
<outgoing>manager-approve-end</outgoing>
</userTask>
<sequenceFlow id="sequenceFlow_da7d27d4-d7e9-4c0f-b5a8-df8e470e2e6d" sourceRef="gateway" targetRef="manager-approve"/>
<endEvent id="end" name="end">
<incoming>gateway-end</incoming>
</endEvent>
<sequenceFlow id="gateway-end" sourceRef="gateway" targetRef="end"/>
<sequenceFlow id="manager-approve-end" sourceRef="manager-approve" targetRef="end"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_6262782c-5242-4b7b-ae9b-1e29eb30bd07">
<bpmndi:BPMNPlane bpmnElement="process_5b6570ec-f3f2-4572-bce3-8cf466f056de" id="BPMNPlane_a4cf711e-fa30-4a19-b649-e5b702b6d378"/>
</bpmndi:BPMNDiagram>
</definitions>
这个xml内容是没有问题的,可以正常提交,执行,但是在camunda的页面上流程图是无法展示的,就是因为没有定义Diagram。
那么,这个问题要怎么解决呢?找了很久,唯一能找到的就是使用Fluent,https://docs.camunda.org/manual/7.19/user-guide/model-api/bpmn-model-api/fluent-builder-api/ 但是,如果我们是通过自定义的结构生成的bpmnModel 那么这个方法就很不方便,反正最终是没有找到其他解决办法。 突然想起flowable里面是有一个flowable-bpmn-layout 的包,可以实现flowable的自动布局,考虑到两者都是从activiti基础上发展来的,总贵是有共同之处的,就试试看能否抄过来。
代码搬运
flowable-bpmn-layout里面的计算是依赖的jgraphx这个包,所以添加依赖
<dependency>
<groupId>org.tinyjee.jgraphx</groupId>
<artifactId>jgraphx</artifactId>
<version>3.4.1.3</version>
</dependency>
我们看到flowable-bpmn-layout 中其实就两个文件,直接全部copy过来,然后对其进行改造,发现BPMNLayout这个文件是不用动的,完全匹配,所以,我们改造的重点也就只有BpmnAutoLayout这个文件。以下是调整过的文件内容
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxEdgeStyle;
import com.mxgraph.view.mxGraph;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnEdge;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnPlane;
import org.camunda.bpm.model.bpmn.instance.bpmndi.BpmnShape;
import org.camunda.bpm.model.bpmn.instance.dc.Bounds;
import org.camunda.bpm.model.bpmn.instance.di.Waypoint;
import javax.swing.*;
import java.util.*;
/**
* @author Joram Barrez
*/
public class BpmnAutoLayout {
private static final String STYLE_EVENT = "styleEvent";
private static final String STYLE_GATEWAY = "styleGateway";
private static final String STYLE_SEQUENCEFLOW = "styleSequenceFlow";
private static final String STYLE_BOUNDARY_SEQUENCEFLOW = "styleBoundarySequenceFlow";
protected BpmnModelInstance bpmnModelInstance;
protected int eventSize = 30;
protected int gatewaySize = 40;
protected int taskWidth = 100;
protected int taskHeight = 60;
protected int subProcessMargin = 20;
protected mxGraph graph;
protected Object cellParent;
protected Map<String, Association> associations;
protected Map<String, TextAnnotation> textAnnotations;
protected Map<String, SequenceFlow> sequenceFlows;
protected List<BoundaryEvent> boundaryEvents;
protected Map<String, FlowElement> handledFlowElements;
protected Map<String, Artifact> handledArtifacts;
protected Map<String, Object> generatedVertices;
protected Map<String, Object> generatedSequenceFlowEdges;
protected Map<String, Object> generatedAssociationEdges;
protected Map<String, BpmnShape> locationMap = new LinkedHashMap<>();
protected Map<String, BpmnShape> labelLocationMap = new LinkedHashMap<>();
protected Map<String, List<BpmnEdge>> flowLocationMap = new LinkedHashMap<>();
public void addBpmnShape(String key, BpmnShape bpmnShape) {
locationMap.put(key, bpmnShape);
}
public void addFlowBpmnEdgeList(String key, List<BpmnEdge> graphicInfoList) {
flowLocationMap.put(key, graphicInfoList);
}
public BpmnAutoLayout(BpmnModelInstance bpmnModel) {
this.bpmnModelInstance = bpmnModel;
}
public void execute() {
// Reset any previous DI information
locationMap.clear();
flowLocationMap.clear();
// Generate DI for each process
for (Process process : bpmnModelInstance.getModelElementsByType(Process.class)) {
layout(process);
// Operations that can only be done after all elements have received DI
translateNestedSubprocesses(process);
}
}
protected BpmnPlane findBpmnPlane() {
Collection<BpmnPlane> planes = bpmnModelInstance.getModelElementsByType(BpmnPlane.class);
return planes.iterator().next();
}
protected void layout(Process flowElementsContainer) {
graph = new mxGraph();
cellParent = graph.getDefaultParent();
graph.getModel().beginUpdate();
// Subprocesses are handled in a new instance of BpmnAutoLayout, hence they instantiations of new maps here.
handledFlowElements = new HashMap<>();
handledArtifacts = new HashMap<>();
generatedVertices = new HashMap<>();
generatedSequenceFlowEdges = new HashMap<>();
generatedAssociationEdges = new HashMap<>();
associations = new HashMap<>(); // Associations are gathered and processed afterwards, because we must be sure we already found source and target
textAnnotations = new HashMap<>(); // Text Annotations are gathered and processed afterwards, because we must be sure we already found the parent.
sequenceFlows = new HashMap<>(); // Sequence flow are gathered and processed afterwards,because we must be sure we already found source and target
boundaryEvents = new ArrayList<>(); // Boundary events are gathered and processed afterwards, because we must be sure we have its parent
// Process all elements
for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
if (flowElement instanceof SequenceFlow) {
handleSequenceFlow((SequenceFlow) flowElement);
} else if (flowElement instanceof Event) {
handleEvent(flowElement);
} else if (flowElement instanceof Gateway) {
createGatewayVertex(flowElement);
} else if (flowElement instanceof Task || flowElement instanceof CallActivity) {
handleActivity(flowElement);
} else if (flowElement instanceof SubProcess) {
handleSubProcess(flowElement);
}
handledFlowElements.put(flowElement.getId(), flowElement);
}
// process artifacts
for (Artifact artifact : flowElementsContainer.getArtifacts()) {
if (artifact instanceof Association) {
handleAssociation((Association) artifact);
} else if (artifact instanceof TextAnnotation) {
handleTextAnnotation((TextAnnotation) artifact);
}
handledArtifacts.put(artifact.getId(), artifact);
}
// Process gathered elements
handleBoundaryEvents();
handleSequenceFlow();
handleAssociations();
// All elements are now put in the graph. Let's layout them!
CustomLayout layout = new CustomLayout(graph, SwingConstants.WEST);
layout.setIntraCellSpacing(100.0);
layout.setResizeParent(true);
layout.setFineTuning(true);
layout.setParentBorder(20);
layout.setMoveParent(true);
layout.setDisableEdgeStyle(false);
layout.setUseBoundingBox(true);
layout.execute(graph.getDefaultParent());
graph.getModel().endUpdate();
generateDiagramInterchangeElements();
}
protected void subLayout(SubProcess flowElementsContainer) {
graph = new mxGraph();
cellParent = graph.getDefaultParent();
graph.getModel().beginUpdate();
// Subprocesses are handled in a new instance of BpmnAutoLayout, hence they instantiations of new maps here.
handledFlowElements = new HashMap<>();
handledArtifacts = new HashMap<>();
generatedVertices = new HashMap<>();
generatedSequenceFlowEdges = new HashMap<>();
generatedAssociationEdges = new HashMap<>();
associations = new HashMap<>(); // Associations are gathered and processed afterwards, because we must be sure we already found source and target
textAnnotations = new HashMap<>(); // Text Annotations are gathered and processed afterwards, because we must be sure we already found the parent.
sequenceFlows = new HashMap<>(); // Sequence flow are gathered and processed afterwards,because we must be sure we already found source and target
boundaryEvents = new ArrayList<>(); // Boundary events are gathered and processed afterwards, because we must be sure we have its parent
// Process all elements
for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
if (flowElement instanceof SequenceFlow) {
handleSequenceFlow((SequenceFlow) flowElement);
} else if (flowElement instanceof Event) {
handleEvent(flowElement);
} else if (flowElement instanceof Gateway) {
createGatewayVertex(flowElement);
} else if (flowElement instanceof Task || flowElement instanceof CallActivity) {
handleActivity(flowElement);
} else if (flowElement instanceof SubProcess) {
handleSubProcess(flowElement);
}
handledFlowElements.put(flowElement.getId(), flowElement);
}
// process artifacts
for (Artifact artifact : flowElementsContainer.getArtifacts()) {
if (artifact instanceof Association) {
handleAssociation((Association) artifact);
} else if (artifact instanceof TextAnnotation) {
handleTextAnnotation((TextAnnotation) artifact);
}
handledArtifacts.put(artifact.getId(), artifact);
}
// Process gathered elements
handleBoundaryEvents();
handleSequenceFlow();
handleAssociations();
// All elements are now put in the graph. Let's layout them!
CustomLayout layout = new CustomLayout(graph, SwingConstants.WEST);
layout.setIntraCellSpacing(100.0);
layout.setResizeParent(true);
layout.setFineTuning(true);
layout.setParentBorder(20);
layout.setMoveParent(true);
layout.setDisableEdgeStyle(false);
layout.setUseBoundingBox(true);
layout.execute(graph.getDefaultParent());
graph.getModel().endUpdate();
generateDiagramInterchangeElements();
}
private void handleTextAnnotation(TextAnnotation artifact) {
ensureArtifactIdSet(artifact);
textAnnotations.put(artifact.getId(), artifact);
}
// BPMN element handling
protected void ensureSequenceFlowIdSet(SequenceFlow sequenceFlow) {
// We really must have ids for sequence flow to be able to generate
// stuff
if (sequenceFlow.getId() == null) {
sequenceFlow.setId("sequenceFlow-" + UUID.randomUUID());
}
}
protected void ensureArtifactIdSet(Artifact artifact) {
// We really must have ids for sequence flow to be able to generate stuff
if (artifact.getId() == null) {
artifact.setId("artifact-" + UUID.randomUUID());
}
}
protected void handleAssociation(Association association) {
ensureArtifactIdSet(association);
associations.put(association.getId(), association);
}
protected void handleSequenceFlow(SequenceFlow sequenceFlow) {
ensureSequenceFlowIdSet(sequenceFlow);
sequenceFlows.put(sequenceFlow.getId(), sequenceFlow);
}
protected void handleEvent(FlowElement flowElement) {
// Boundary events are an exception to the general way of drawing an event
if (flowElement instanceof BoundaryEvent) {
boundaryEvents.add((BoundaryEvent) flowElement);
} else {
createEventVertex(flowElement);
}
}
protected void handleActivity(FlowElement flowElement) {
Object activityVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, taskWidth, taskHeight);
generatedVertices.put(flowElement.getId(), activityVertex);
}
protected void handleSubProcess(FlowElement flowElement) {
BpmnAutoLayout bpmnAutoLayout = new BpmnAutoLayout(bpmnModelInstance);
bpmnAutoLayout.subLayout((SubProcess) flowElement);
double subProcessWidth = bpmnAutoLayout.getGraph().getView().getGraphBounds().getWidth();
double subProcessHeight = bpmnAutoLayout.getGraph().getView().getGraphBounds().getHeight();
Object subProcessVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, subProcessWidth + 2 * subProcessMargin, subProcessHeight + 2 * subProcessMargin);
generatedVertices.put(flowElement.getId(), subProcessVertex);
}
protected void handleBoundaryEvents() {
for (BoundaryEvent boundaryEvent : boundaryEvents) {
mxGeometry geometry = new mxGeometry(0.8, 1.0, eventSize, eventSize);
geometry.setOffset(new mxPoint(-(eventSize / 2), -(eventSize / 2)));
geometry.setRelative(true);
mxCell boundaryPort = new mxCell(null, geometry, "shape=ellipse;perimeter=ellipsePerimeter");
boundaryPort.setId("boundary-event-" + boundaryEvent.getId());
boundaryPort.setVertex(true);
Object portParent = null;
if (boundaryEvent.getAttachedTo() != null) {
portParent = generatedVertices.get(boundaryEvent.getAttachedTo().getId());
} else {
throw new RuntimeException("Could not generate DI: boundaryEvent '" + boundaryEvent.getId() + "' has no attachedToRef");
}
graph.addCell(boundaryPort, portParent);
generatedVertices.put(boundaryEvent.getId(), boundaryPort);
}
}
protected void handleSequenceFlow() {
Hashtable<String, Object> edgeStyle = new Hashtable<>();
edgeStyle.put(mxConstants.STYLE_ORTHOGONAL, true);
edgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.ElbowConnector);
edgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.0);
edgeStyle.put(mxConstants.STYLE_ENTRY_Y, 0.5);
graph.getStylesheet().putCellStyle(STYLE_SEQUENCEFLOW, edgeStyle);
Hashtable<String, Object> boundaryEdgeStyle = new Hashtable<>();
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.EntityRelation);
graph.getStylesheet().putCellStyle(STYLE_BOUNDARY_SEQUENCEFLOW, boundaryEdgeStyle);
for (SequenceFlow sequenceFlow : sequenceFlows.values()) {
Object sourceVertex = generatedVertices.get(sequenceFlow.getSource().getId());
Object targetVertex = generatedVertices.get(sequenceFlow.getTarget().getId());
String style = null;
if (handledFlowElements.get(sequenceFlow.getSource().getId()) instanceof BoundaryEvent) {
// Sequence flow out of boundary events are handled in a
// different way,
// to make them visually appealing for the eye of the dear end
// user.
style = STYLE_BOUNDARY_SEQUENCEFLOW;
} else {
style = STYLE_SEQUENCEFLOW;
}
Object sequenceFlowEdge = graph.insertEdge(cellParent, sequenceFlow.getId(), "", sourceVertex, targetVertex, style);
generatedSequenceFlowEdges.put(sequenceFlow.getId(), sequenceFlowEdge);
}
}
protected void handleAssociations() {
Hashtable<String, Object> edgeStyle = new Hashtable<>();
edgeStyle.put(mxConstants.STYLE_ORTHOGONAL, true);
edgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.ElbowConnector);
edgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.0);
edgeStyle.put(mxConstants.STYLE_ENTRY_Y, 0.5);
graph.getStylesheet().putCellStyle(STYLE_SEQUENCEFLOW, edgeStyle);
Hashtable<String, Object> boundaryEdgeStyle = new Hashtable<>();
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.EntityRelation);
graph.getStylesheet().putCellStyle(STYLE_BOUNDARY_SEQUENCEFLOW, boundaryEdgeStyle);
for (Association association : associations.values()) {
Object sourceVertex = generatedVertices.get(association.getSource().getId());
Object targetVertex = generatedVertices.get(association.getTarget().getId());
String style = null;
if (handledFlowElements.get(association.getSource().getId()) instanceof BoundaryEvent) {
// Sequence flow out of boundary events are handled in a different way,
// to make them visually appealing for the eye of the dear end user.
style = STYLE_BOUNDARY_SEQUENCEFLOW;
} else {
style = STYLE_SEQUENCEFLOW;
}
Object associationEdge = graph.insertEdge(cellParent, association.getId(), "", sourceVertex, targetVertex, style);
generatedAssociationEdges.put(association.getId(), associationEdge);
}
}
// Graph cell creation
protected void createEventVertex(FlowElement flowElement) {
// Add styling for events if needed
if (!graph.getStylesheet().getStyles().containsKey(STYLE_EVENT)) {
Hashtable<String, Object> eventStyle = new Hashtable<>();
eventStyle.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
graph.getStylesheet().putCellStyle(STYLE_EVENT, eventStyle);
}
// Add vertex representing event to graph
Object eventVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, eventSize, eventSize, STYLE_EVENT);
generatedVertices.put(flowElement.getId(), eventVertex);
}
protected void createGatewayVertex(FlowElement flowElement) {
// Add styling for gateways if needed
if (graph.getStylesheet().getStyles().containsKey(STYLE_GATEWAY)) {
Hashtable<String, Object> style = new Hashtable<>();
style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_RHOMBUS);
graph.getStylesheet().putCellStyle(STYLE_GATEWAY, style);
}
// Create gateway node
Object gatewayVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, gatewaySize, gatewaySize, STYLE_GATEWAY);
generatedVertices.put(flowElement.getId(), gatewayVertex);
}
// Diagram interchange generation
protected void generateDiagramInterchangeElements() {
generateActivityDiagramInterchangeElements();
generateSequenceFlowDiagramInterchangeElements();
generateAssociationDiagramInterchangeElements();
}
protected void generateActivityDiagramInterchangeElements() {
for (String flowElementId : generatedVertices.keySet()) {
Object vertex = generatedVertices.get(flowElementId);
mxCellState cellState = graph.getView().getState(vertex);
BpmnShape subProcessGraphicInfo = createDiagramInterchangeInformation(handledFlowElements.get(flowElementId), (int) cellState.getX(), (int) cellState.getY(), (int) cellState.getWidth(),
(int) cellState.getHeight());
// The DI for the elements of a subprocess are generated without knowledge of the rest of the graph
// So we must translate all it's elements with the x and y of the subprocess itself
if (handledFlowElements.get(flowElementId) instanceof SubProcess) {
// Always expanded when auto layouting
subProcessGraphicInfo.setExpanded(true);
}
}
}
protected void generateSequenceFlowDiagramInterchangeElements() {
for (String sequenceFlowId : generatedSequenceFlowEdges.keySet()) {
Object edge = generatedSequenceFlowEdges.get(sequenceFlowId);
List<mxPoint> points = graph.getView().getState(edge).getAbsolutePoints();
// JGraphX has this funny way of generating the outgoing sequence flow of a gateway
// Visually, we'd like them to originate from one of the corners of the rhombus,
// hence we force the starting point of the sequence flow to the closest rhombus corner point.
FlowElement sourceElement = handledFlowElements.get(sequenceFlows.get(sequenceFlowId).getSource().getId());
if (sourceElement instanceof Gateway && ((Gateway) sourceElement).getOutgoing().size() > 1) {
mxPoint startPoint = points.get(0);
Object gatewayVertex = generatedVertices.get(sourceElement.getId());
mxCellState gatewayState = graph.getView().getState(gatewayVertex);
mxPoint northPoint = new mxPoint(gatewayState.getX() + gatewayState.getWidth() / 2, gatewayState.getY());
mxPoint southPoint = new mxPoint(gatewayState.getX() + gatewayState.getWidth() / 2, gatewayState.getY() + gatewayState.getHeight());
mxPoint eastPoint = new mxPoint(gatewayState.getX() + gatewayState.getWidth(), gatewayState.getY() + gatewayState.getHeight() / 2);
mxPoint westPoint = new mxPoint(gatewayState.getX(), gatewayState.getY() + gatewayState.getHeight() / 2);
double closestDistance = Double.MAX_VALUE;
mxPoint closestPoint = null;
for (mxPoint rhombusPoint : Arrays.asList(northPoint, southPoint, eastPoint, westPoint)) {
double distance = euclidianDistance(startPoint, rhombusPoint);
if (distance < closestDistance) {
closestDistance = distance;
closestPoint = rhombusPoint;
}
}
startPoint.setX(closestPoint.getX());
startPoint.setY(closestPoint.getY());
// We also need to move the second point.
// Since we know the layout is from left to right, this is not a
// problem
if (points.size() > 1) {
mxPoint nextPoint = points.get(1);
nextPoint.setY(closestPoint.getY());
}
}
createDiagramInterchangeInformation(handledFlowElements.get(sequenceFlowId), optimizeEdgePoints(points));
}
}
protected void generateAssociationDiagramInterchangeElements() {
for (String associationId : generatedAssociationEdges.keySet()) {
Object edge = generatedAssociationEdges.get(associationId);
List<mxPoint> points = graph.getView().getState(edge).getAbsolutePoints();
createDiagramInterchangeInformation(handledArtifacts.get(associationId), optimizeEdgePoints(points));
}
}
protected double euclidianDistance(mxPoint point1, mxPoint point2) {
return Math.sqrt(((point2.getX() - point1.getX()) * (point2.getX() - point1.getX()) + (point2.getY() - point1.getY()) * (point2.getY() - point1.getY())));
}
// JGraphX sometime generates points that visually are not really necessary.
// This method will remove any such points.
protected List<mxPoint> optimizeEdgePoints(List<mxPoint> unoptimizedPointsList) {
List<mxPoint> optimizedPointsList = new ArrayList<>();
for (int i = 0; i < unoptimizedPointsList.size(); i++) {
boolean keepPoint = true;
mxPoint currentPoint = unoptimizedPointsList.get(i);
// When three points are on the same x-axis with same y value, the
// middle point can be removed
if (i > 0 && i != unoptimizedPointsList.size() - 1) {
mxPoint previousPoint = unoptimizedPointsList.get(i - 1);
mxPoint nextPoint = unoptimizedPointsList.get(i + 1);
if (currentPoint.getX() >= previousPoint.getX() && currentPoint.getX() <= nextPoint.getX() && currentPoint.getY() == previousPoint.getY() && currentPoint.getY() == nextPoint.getY()) {
keepPoint = false;
} else if (currentPoint.getY() >= previousPoint.getY() && currentPoint.getY() <= nextPoint.getY() && currentPoint.getX() == previousPoint.getX() && currentPoint.getX() == nextPoint.getX()) {
keepPoint = false;
}
}
if (keepPoint) {
optimizedPointsList.add(currentPoint);
}
}
return optimizedPointsList;
}
protected BpmnShape createDiagramInterchangeInformation(FlowElement flowElement, int x, int y, int width, int height) {
BpmnPlane bpmnPlane = findBpmnPlane();
BpmnShape bpmnShape = bpmnModelInstance.newInstance(BpmnShape.class);
bpmnShape.setBpmnElement(flowElement);
Bounds nodeBounds = bpmnModelInstance.newInstance(Bounds.class);
nodeBounds.setX(x);
nodeBounds.setY(y);
nodeBounds.setWidth(width);
nodeBounds.setHeight(height);
bpmnShape.addChildElement(nodeBounds);
bpmnPlane.addChildElement(bpmnShape);
return bpmnShape;
}
protected void createDiagramInterchangeInformation(BaseElement element, List<mxPoint> waypoints) {
BpmnPlane bpmnPlane = findBpmnPlane();
BpmnEdge bpmnEdge = bpmnModelInstance.newInstance(BpmnEdge.class);
bpmnEdge.setBpmnElement(element);
List<BpmnEdge> bpmnEdgeForWaypoints = new ArrayList<>();
for (mxPoint waypoint : waypoints) {
Waypoint waypoint1 = bpmnModelInstance.newInstance(Waypoint.class);
waypoint1.setX(waypoint.getX());
waypoint1.setY(waypoint.getY());
bpmnEdge.addChildElement(waypoint1);
bpmnPlane.addChildElement(bpmnEdge);
bpmnEdgeForWaypoints.add(bpmnEdge);
}
addFlowBpmnEdgeList(element.getId(), bpmnEdgeForWaypoints);
}
/**
* Since subprocesses are autolayouted independently (see {@link #handleSubProcess(FlowElement)}), the elements have x and y coordinates relative to the bounds of the subprocess (thinking the
* subprocess is on (0,0). This however, does not work for nested subprocesses, as they need to take in account the x and y coordinates for each of the parent subproceses.
* <p>
* This method is to be called after fully layouting one process, since ALL elements need to have x and y.
*/
protected void translateNestedSubprocesses(Process process) {
for (FlowElement flowElement : process.getFlowElements()) {
if (flowElement instanceof SubProcess) {
translateNestedSubprocessElements((SubProcess) flowElement);
}
}
}
protected void translateNestedSubprocessElements(SubProcess subProcess) {
BpmnShape subProcessGraphicInfo = locationMap.get(subProcess.getId());
Bounds bounds = subProcessGraphicInfo.getBounds();
double subProcessX = bounds.getX();
double subProcessY = bounds.getY();
List<SubProcess> nestedSubProcesses = new ArrayList<>();
for (FlowElement flowElement : subProcess.getFlowElements()) {
if (flowElement instanceof SequenceFlow) {
List<BpmnEdge> graphicInfos = flowLocationMap.get(flowElement.getId());
for (BpmnEdge bpmnEdge : graphicInfos) {
bpmnEdge.getWaypoints().forEach(waypoint -> {
waypoint.setX(waypoint.getX() + subProcessX + subProcessMargin);
waypoint.setY(waypoint.getY() + subProcessY + subProcessMargin);
});
}
} else if (!(flowElement instanceof DataObject)) {
// Regular element
BpmnShape graphicInfo = locationMap.get(flowElement.getId());
Bounds bounds1 = graphicInfo.getBounds();
bounds1.setX(bounds1.getX() + subProcessX + subProcessMargin);
bounds1.setY(bounds1.getY() + subProcessY + subProcessMargin);
}
if (flowElement instanceof SubProcess) {
nestedSubProcesses.add((SubProcess) flowElement);
}
}
// Continue for next level of nested subprocesses
for (SubProcess nestedSubProcess : nestedSubProcesses) {
translateNestedSubprocessElements(nestedSubProcess);
}
}
// Getters and Setters
public mxGraph getGraph() {
return graph;
}
public void setGraph(mxGraph graph) {
this.graph = graph;
}
public int getEventSize() {
return eventSize;
}
public void setEventSize(int eventSize) {
this.eventSize = eventSize;
}
public int getGatewaySize() {
return gatewaySize;
}
public void setGatewaySize(int gatewaySize) {
this.gatewaySize = gatewaySize;
}
public int getTaskWidth() {
return taskWidth;
}
public void setTaskWidth(int taskWidth) {
this.taskWidth = taskWidth;
}
public int getTaskHeight() {
return taskHeight;
}
public void setTaskHeight(int taskHeight) {
this.taskHeight = taskHeight;
}
public int getSubProcessMargin() {
return subProcessMargin;
}
public void setSubProcessMargin(int subProcessMargin) {
this.subProcessMargin = subProcessMargin;
}
// Due to a bug (see
// http://forum.jgraph.com/questions/5952/mxhierarchicallayout-not-correct-when-using-child-vertex)
// We must extend the default hierarchical layout to tweak it a bit (see url
// link) otherwise the layouting crashes.
//
// Verify again with a later release if fixed (ie the mxHierarchicalLayout can be used directly)
static class CustomLayout extends mxHierarchicalLayout {
public CustomLayout(mxGraph graph, int orientation) {
super(graph, orientation);
this.traverseAncestors = false;
}
}
}
这里着重注意的是 Process,SubProcess两个在Flowable中和Camunda中的关系是不一样的,另外,Flowable中GraphicInfo和Camunda中BpmnShape,BpmnEdge的概念,正常直接复制文件就可以使用,如果想自己去优化,就需要把相关概念梳理清楚。
在转换成xml 的代码之前加上
new BpmnAutoLayout(bpmnModelInstance).execute();
执行打印结构如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="definitions_1c432cbd-5224-4423-84cf-8e6e0d38f8cc" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="process_ce5eca7a-b752-411c-add6-c46c6d85defb" isExecutable="true">
<startEvent id="start" name="start">
<outgoing>gateway-manager-approve</outgoing>
</startEvent>
<userTask id="leader-approve" name="组长审批">
<incoming>gateway-manager-approve</incoming>
<outgoing>sequenceFlow_4ef9930d-200b-46c1-ab55-733ffe15a64a</outgoing>
</userTask>
<sequenceFlow id="gateway-manager-approve" sourceRef="start" targetRef="leader-approve"/>
<exclusiveGateway id="gateway" name="排他网关">
<incoming>sequenceFlow_4ef9930d-200b-46c1-ab55-733ffe15a64a</incoming>
<outgoing>sequenceFlow_b2c4e3ef-6fcb-4130-82d7-88332968995d</outgoing>
<outgoing>gateway-end</outgoing>
</exclusiveGateway>
<sequenceFlow id="sequenceFlow_4ef9930d-200b-46c1-ab55-733ffe15a64a" sourceRef="leader-approve" targetRef="gateway"/>
<userTask id="manager-approve" name="经理审批">
<incoming>sequenceFlow_b2c4e3ef-6fcb-4130-82d7-88332968995d</incoming>
<incoming>manager-approve-end</incoming>
<outgoing>manager-approve-end</outgoing>
</userTask>
<sequenceFlow id="sequenceFlow_b2c4e3ef-6fcb-4130-82d7-88332968995d" sourceRef="gateway" targetRef="manager-approve"/>
<endEvent id="end" name="end">
<incoming>gateway-end</incoming>
</endEvent>
<sequenceFlow id="gateway-end" sourceRef="gateway" targetRef="end"/>
<sequenceFlow id="manager-approve-end" sourceRef="manager-approve" targetRef="end"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_f15257fc-b8f3-4acc-b8f8-57d09d4b9ff0">
<bpmndi:BPMNPlane bpmnElement="process_ce5eca7a-b752-411c-add6-c46c6d85defb" id="BPMNPlane_73260324-99fe-42ce-895b-8fa48418da61">
<bpmndi:BPMNShape bpmnElement="leader-approve" id="BPMNShape_e3927c5a-81f3-4ade-aea0-c221785d09be">
<dc:Bounds height="60.0" width="100.0" x="80.0" y="59.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="manager-approve" id="BPMNShape_a026b14e-29bf-41c8-b598-f089bd9ac8ea">
<dc:Bounds height="60.0" width="100.0" x="320.0" y="0.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_92e89ec4-1c08-4bd3-93f8-a82f28a84945">
<dc:Bounds height="30.0" width="30.0" x="0.0" y="74.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_94cf13ea-37ae-48b3-a07f-00dbe04a15a4">
<dc:Bounds height="30.0" width="30.0" x="470.0" y="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="gateway" id="BPMNShape_82d2ac56-907e-4044-94ca-490083c36df9">
<dc:Bounds height="40.0" width="40.0" x="230.0" y="72.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_4ef9930d-200b-46c1-ab55-733ffe15a64a" id="BPMNEdge_fbb3b8ad-44e3-44a9-838c-534e984671f1">
<di:waypoint x="180.0" y="89.0"/>
<di:waypoint x="192.0" y="89.0"/>
<di:waypoint x="192.0" y="92.0"/>
<di:waypoint x="230.0" y="92.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_b2c4e3ef-6fcb-4130-82d7-88332968995d" id="BPMNEdge_f0bc66db-6fdb-43da-acc1-ed87c601b2f9">
<di:waypoint x="270.0" y="92.0"/>
<di:waypoint x="282.0" y="92.0"/>
<di:waypoint x="282.0" y="30.000000000000007"/>
<di:waypoint x="320.0" y="30.000000000000007"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="gateway-end" id="BPMNEdge_8fe3a8a7-ce34-4f4c-82f4-6a0b8d3f7f70">
<di:waypoint x="270.0" y="92.0"/>
<di:waypoint x="282.0" y="92.0"/>
<di:waypoint x="282.0" y="95.0"/>
<di:waypoint x="470.0" y="95.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="gateway-manager-approve" id="BPMNEdge_234a0da9-092f-402f-ae36-89e8adf6c013">
<di:waypoint x="30.0" y="89.0"/>
<di:waypoint x="80.0" y="89.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="manager-approve-end" id="BPMNEdge_d31fc96c-2e45-46ee-a84b-9b9dfe48a9d6">
<di:waypoint x="420.0" y="30.0"/>
<di:waypoint x="432.0" y="30.0"/>
<di:waypoint x="432.0" y="95.0"/>
<di:waypoint x="470.0" y="95.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
这个时候已经有了Diagram的内容了
把这段xml 复制到Camunda Modeler中,可以看到流程图可以展示了

另外要注意,创建Process 要使用Bpmn.createExecutableProcess() 否则会缺失BpmnPanel