Contorllor
/**
* 根据流程定义id查看流程图
*
* @param processDefinitionId
* @return
*/
@GetMapping(value = "/image/{processDefinitionId}")
@ApiOperation(value = "根据流程定义id查看流程图", notes = "根据流程定义id,查询返回不带节点流转信息的图片")
public void image(@PathVariable("processDefinitionId") String processDefinitionId, HttpServletResponse response) {
try (OutputStream out = response.getOutputStream();
InputStream is = imageService.getFlowImgByProcInstId(processDefinitionId, null, false)) {
if (null != is) {
BufferedImage image = ImageIO.read(is);
response.setContentType("image/png");
ImageIO.write(image, "png", out);
}
} catch (Exception ex) {
log.error("查看流程图失败", ex);
}
}
Service
package com.ruifu.act.service;
import java.io.InputStream;
/**
* @author Administrator
*/
public interface ImageService {
/**
* 获取指定流程对应的流程图image
*
* @param procDefId 流程模版定义id
* @param procInstId 流程实例id
* @param showHistory 是否显示流程图历史节点渲染.<br/>true:显示历史节点,使用procInstId。<br/>false:不显示历史节点,直接使用procDefId。
* @return
* @throws Exception
*/
InputStream getFlowImgByProcInstId(String procDefId, String procInstId, boolean showHistory) throws Exception;
}
根据流程实例Id,获取实时流程图片 实现类
package com.ruifu.act.service.impl;
import com.ruifu.act.service.ImageService;
import com.ruifu.act.utils.CustomProcessDiagramGenerator;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.runtime.Execution;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 根据流程实例Id,获取实时流程图片实现类
*
* @author liuxz
*/
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class ImageServiceImpl implements ImageService {
@Autowired
private RepositoryService repositoryService;
@Autowired
private HistoryService historyService;
@Autowired
private RuntimeService runtimeService;
/**
* 根据流程实例Id,获取实时流程图片
*
* @param procDefId 流程模版定义id
* @param procInstId 流程实例id
* @param showHistory 是否显示流程图历史节点渲染.<br/>true:显示历史节点,使用procInstId。<br/>false:不显示历史节点,直接使用procDefId。
* @return
* @throws Exception
*/
@Override
public InputStream getFlowImgByProcInstId(String procDefId, String procInstId, boolean showHistory) throws Exception {
InputStream imageStream = null;
BpmnModel bpmnModel = null;
List<String> highLightedActivitiIdList = new ArrayList<>();
List<String> runningActivitiIdList = new ArrayList<>();
List<String> highLightedFlowIds = new ArrayList<>();
if (showHistory && StringUtils.isNotEmpty(procInstId)) {
// 通过流程实例ID获取历史流程实例
HistoricProcessInstance historicProcessInstance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(procInstId)
.singleResult();
if (null != historicProcessInstance) {
// 获取流程定义Model对象
bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
//如果需要展示历史节点记录,则需要查询出对应的历史节点信息
if (showHistory) {
// 通过流程实例ID获取流程中已经执行的节点,按照执行先后顺序排序
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(procInstId)
.orderByHistoricActivityInstanceId()
.asc().list();
// 将已经执行的节点ID放入高亮显示节点集合
for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
highLightedActivitiIdList.add(historicActivityInstance.getActivityId());
}
// 通过流程实例ID获取流程中正在执行的节点
List<Execution> runningActivityInstanceList = runtimeService.createExecutionQuery().
processInstanceId(procInstId).list();
for (Execution execution : runningActivityInstanceList) {
if (StringUtils.isNotEmpty(execution.getActivityId())) {
runningActivitiIdList.add(execution.getActivityId());
}
}
// 获取已流经的流程线,需要高亮显示高亮流程已发生流转的线id集合
highLightedFlowIds.addAll(getHighLightedFlows(bpmnModel, historicActivityInstanceList));
}
}
} else if (!showHistory && StringUtils.isNotEmpty(procDefId)) {
bpmnModel = repositoryService.getBpmnModel(procDefId);
}
try {
if (null != bpmnModel) {
// 定义流程画布生成器
CustomProcessDiagramGenerator processDiagramGenerator = new CustomProcessDiagramGenerator();
// 使用默认配置获得流程图表生成器,并生成追踪图片字符流
imageStream = processDiagramGenerator.generateDiagramCustom(bpmnModel, "png",
highLightedActivitiIdList, runningActivitiIdList, highLightedFlowIds,
"宋体", "黑体", "黑体",
null, 2.0);
}
} catch (Exception e) {
log.error("通过流程实例ID[{}]获取流程图时出现异常!", procInstId, e);
throw new Exception("通过流程实例ID" + procInstId + "获取流程图时出现异常!", e);
}
return imageStream;
}
/**
* 获取已流经的流程线,需要高亮显示高亮流程已发生流转的线id集合
*
* @param bpmnModel
* @param historicActivityInstanceList
* @return
*/
private List<String> getHighLightedFlows(BpmnModel bpmnModel,
List<HistoricActivityInstance> historicActivityInstanceList) {
// 已流经的流程线,需要高亮显示
List<String> highLightedFlowIdList = new ArrayList<>();
// 全部活动节点
List<FlowNode> allHistoricActivityNodeList = new ArrayList<>();
// 已完成的历史活动节点
List<HistoricActivityInstance> finishedActivityInstanceList = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
// 获取流程节点
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance
.getActivityId(), true);
allHistoricActivityNodeList.add(flowNode);
// 结束时间不为空,当前节点则已经完成
if (historicActivityInstance.getEndTime() != null) {
finishedActivityInstanceList.add(historicActivityInstance);
}
}
FlowNode currentFlowNode;
FlowNode targetFlowNode;
HistoricActivityInstance currentActivityInstance;
// 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
for (int k = 0; k < finishedActivityInstanceList.size(); k++) {
currentActivityInstance = finishedActivityInstanceList.get(k);
// 获得当前活动对应的节点信息及outgoingFlows信息
currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance
.getActivityId(), true);
// 当前节点的所有流出线
List<SequenceFlow> outgoingFlowList = currentFlowNode.getOutgoingFlows();
/**
* 遍历outgoingFlows并找到已流转的 满足如下条件认为已流转:
* 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
* 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
* (第2点有问题,有过驳回的,会只绘制驳回的流程线,通过走向下一级的流程线没有高亮显示)
*/
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(
currentActivityInstance.getActivityType())) {
// 遍历历史活动节点,找到匹配流程目标节点的
for (SequenceFlow outgoingFlow : outgoingFlowList) {
// 获取当前节点流程线对应的下级节点
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(outgoingFlow.getTargetRef(),
true);
// 如果下级节点包含在所有历史节点中,则将当前节点的流出线高亮显示
if (allHistoricActivityNodeList.contains(targetFlowNode)) {
highLightedFlowIdList.add(outgoingFlow.getId());
}
}
} else {
/**
* 2、当前节点不是并行网关或兼容网关
* 【已解决-问题】如果当前节点有驳回功能,驳回到申请节点,
* 则因为申请节点在历史节点中,导致当前节点驳回到申请节点的流程线被高亮显示,但实际并没有进行驳回操作
*/
// 当前节点ID
String currentActivityId = currentActivityInstance.getActivityId();
int size = historicActivityInstanceList.size();
boolean ifStartFind = false;
boolean ifFinded = false;
HistoricActivityInstance historicActivityInstance;
// 循环当前节点的所有流出线
// 循环所有历史节点
//log.info("【开始】-匹配当前节点-ActivityId=【{}】需要高亮显示的流出线", currentActivityId);
//log.info("循环历史节点");
for (int i = 0; i < historicActivityInstanceList.size(); i++) {
// // 如果当前节点流程线对应的下级节点在历史节点中,则该条流程线进行高亮显示(【问题】有驳回流程线时,即使没有进行驳回操作,因为申请节点在历史节点中,也会将驳回流程线高亮显示-_-||)
// if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
// Map<String, Object> map = new HashMap<>();
// map.put("highLightedFlowId", sequenceFlow.getId());
// map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
// tempMapList.add(map);
// // highLightedFlowIdList.add(sequenceFlow.getId());
// }
// 历史节点
historicActivityInstance = historicActivityInstanceList.get(i);
//log.info("第【{}/{}】个历史节点-ActivityId=[{}]", i + 1, size, historicActivityInstance.getActivityId());
// 如果循环历史节点中的id等于当前节点id,从当前历史节点继续先后查找是否有当前节点流程线等于的节点
// 历史节点的序号需要大于等于已完成历史节点的序号,防止驳回重审一个节点经过两次是只取第一次的流出线高亮显示,第二次的不显示
if (i >= k && historicActivityInstance.getActivityId().equals(currentActivityId)) {
//log.info("第[{}]个历史节点和当前节点一致-ActivityId=[{}]", i + 1, historicActivityInstance.getActivityId());
ifStartFind = true;
// 跳过当前节点继续查找下一个节点
continue;
}
if (ifStartFind) {
//log.info("[开始]-循环当前节点-ActivityId=【{}】的所有流出线", currentActivityId);
ifFinded = false;
for (SequenceFlow sequenceFlow : outgoingFlowList) {
// 如果当前节点流程线对应的下级节点在其后面的历史节点中,则该条流程线进行高亮显示
// 【问题】
//log.info("当前流出线的下级节点=[{}]", sequenceFlow.getTargetRef());
if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
//log.info("当前节点[{}]需高亮显示的流出线=[{}]", currentActivityId, sequenceFlow.getId());
highLightedFlowIdList.add(sequenceFlow.getId());
// 暂时默认找到离当前节点最近的下一级节点即退出循环,否则有多条流出线时将全部被高亮显示
ifFinded = true;
break;
}
}
//log.info("[完成]-循环当前节点-ActivityId=【{}】的所有流出线", currentActivityId);
}
if (ifFinded) {
// 暂时默认找到离当前节点最近的下一级节点即退出历史节点循环,否则有多条流出线时将全部被高亮显示
break;
}
}
//log.info("【完成】-匹配当前节点-ActivityId=【{}】需要高亮显示的流出线", currentActivityId);
}
}
return highLightedFlowIdList;
}
}
定义流程画布生成器
package com.ruifu.act.utils;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.image.ProcessDiagramGenerator;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.*;
/**
* Class to generate an image based the diagram interchange information in a BPMN 2.0 process.
*
* @author Joram Barrez
* @author Tijs Rademakers
*/
@Slf4j
public class CustomProcessDiagramGenerator implements ProcessDiagramGenerator {
protected Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions = new HashMap<Class<? extends BaseElement>, ActivityDrawInstruction>();
protected Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions = new HashMap<Class<? extends BaseElement>, ArtifactDrawInstruction>();
public CustomProcessDiagramGenerator() {
this(1.0);
}
/**
* The instructions on how to draw a certain construct is
* created statically and stored in a map for performance.
*/
public CustomProcessDiagramGenerator(final double scaleFactor) {
// start event
activityDrawInstructions.put(StartEvent.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
StartEvent startEvent = (StartEvent) flowNode;
if (startEvent.getEventDefinitions() != null && !startEvent.getEventDefinitions().isEmpty()) {
EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0);
if (eventDefinition instanceof TimerEventDefinition) {
processDiagramCanvas.drawTimerStartEvent(graphicInfo, scaleFactor);
} else if (eventDefinition instanceof ErrorEventDefinition) {
processDiagramCanvas.drawErrorStartEvent(graphicInfo, scaleFactor);
} else if (eventDefinition instanceof SignalEventDefinition) {
processDiagramCanvas.drawSignalStartEvent(graphicInfo, scaleFactor);
} else if (eventDefinition instanceof MessageEventDefinition) {
processDiagramCanvas.drawMessageStartEvent(graphicInfo, scaleFactor);
} else {
processDiagramCanvas.drawNoneStartEvent(graphicInfo);
}
} else {
processDiagramCanvas.drawNoneStartEvent(graphicInfo);
}
}
});
// signal catch
activityDrawInstructions.put(IntermediateCatchEvent.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowNode;
if (intermediateCatchEvent.getEventDefinitions() != null && !intermediateCatchEvent
.getEventDefinitions().isEmpty()) {
if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, true,
scaleFactor);
} else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) {
processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
} else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) {
processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, true,
scaleFactor);
}
}
}
});
// signal throw
activityDrawInstructions.put(ThrowEvent.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
ThrowEvent throwEvent = (ThrowEvent) flowNode;
if (throwEvent.getEventDefinitions() != null && !throwEvent.getEventDefinitions().isEmpty()) {
if (throwEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
processDiagramCanvas.drawThrowingSignalEvent(graphicInfo, scaleFactor);
} else if (throwEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
processDiagramCanvas.drawThrowingCompensateEvent(graphicInfo, scaleFactor);
} else {
processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor);
}
} else {
processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor);
}
}
});
// end event
activityDrawInstructions.put(EndEvent.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
EndEvent endEvent = (EndEvent) flowNode;
if (endEvent.getEventDefinitions() != null && !endEvent.getEventDefinitions().isEmpty()) {
if (endEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) {
processDiagramCanvas.drawErrorEndEvent(flowNode.getName(), graphicInfo, scaleFactor);
} else {
processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor);
}
} else {
processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor);
}
}
});
// task
activityDrawInstructions.put(Task.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawTask(flowNode.getName(), graphicInfo);
}
});
// user task
activityDrawInstructions.put(UserTask.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawUserTask(flowNode.getName(), graphicInfo, scaleFactor);
}
});
// script task
activityDrawInstructions.put(ScriptTask.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawScriptTask(flowNode.getName(), graphicInfo, scaleFactor);
}
});
// service task
activityDrawInstructions.put(ServiceTask.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
ServiceTask serviceTask = (ServiceTask) flowNode;
if ("camel".equalsIgnoreCase(serviceTask.getType())) {
processDiagramCanvas.drawCamelTask(serviceTask.getName(), graphicInfo, scaleFactor);
} else if ("mule".equalsIgnoreCase(serviceTask.getType())) {
processDiagramCanvas.drawMuleTask(serviceTask.getName(), graphicInfo, scaleFactor);
} else {
processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor);
}
}
});
// receive task
activityDrawInstructions.put(ReceiveTask.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawReceiveTask(flowNode.getName(), graphicInfo, scaleFactor);
}
});
// send task
activityDrawInstructions.put(SendTask.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawSendTask(flowNode.getName(), graphicInfo, scaleFactor);
}
});
// manual task
activityDrawInstructions.put(ManualTask.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawManualTask(flowNode.getName(), graphicInfo, scaleFactor);
}
});
// businessRuleTask task
activityDrawInstructions.put(BusinessRuleTask.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawBusinessRuleTask(flowNode.getName(), graphicInfo, scaleFactor);
}
});
// exclusive gateway
activityDrawInstructions.put(ExclusiveGateway.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawExclusiveGateway(graphicInfo, scaleFactor);
}
});
// inclusive gateway
activityDrawInstructions.put(InclusiveGateway.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawInclusiveGateway(graphicInfo, scaleFactor);
}
});
// parallel gateway
activityDrawInstructions.put(ParallelGateway.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawParallelGateway(graphicInfo, scaleFactor);
}
});
// event based gateway
activityDrawInstructions.put(EventGateway.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawEventBasedGateway(graphicInfo, scaleFactor);
}
});
// Boundary timer
activityDrawInstructions.put(BoundaryEvent.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
BoundaryEvent boundaryEvent = (BoundaryEvent) flowNode;
if (boundaryEvent.getEventDefinitions() != null && !boundaryEvent.getEventDefinitions().isEmpty()) {
if (boundaryEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) {
processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, boundaryEvent
.isCancelActivity(), scaleFactor);
} else if (boundaryEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) {
processDiagramCanvas.drawCatchingErrorEvent(graphicInfo, boundaryEvent.isCancelActivity(),
scaleFactor);
} else if (boundaryEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, boundaryEvent
.isCancelActivity(), scaleFactor);
} else if (boundaryEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) {
processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, boundaryEvent
.isCancelActivity(), scaleFactor);
} else if (boundaryEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
processDiagramCanvas.drawCatchingCompensateEvent(graphicInfo, boundaryEvent.isCancelActivity(),
scaleFactor);
}
}
}
});
// subprocess
activityDrawInstructions.put(SubProcess.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false);
} else {
processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
}
}
});
// Event subprocess
activityDrawInstructions.put(EventSubProcess.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, true);
} else {
processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor);
}
}
});
// call activity
activityDrawInstructions.put(CallActivity.class, new ActivityDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
processDiagramCanvas.drawCollapsedCallActivity(flowNode.getName(), graphicInfo);
}
});
// text annotation
artifactDrawInstructions.put(TextAnnotation.class, new ArtifactDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
TextAnnotation textAnnotation = (TextAnnotation) artifact;
processDiagramCanvas.drawTextAnnotation(textAnnotation.getText(), graphicInfo);
}
});
// association
artifactDrawInstructions.put(Association.class, new ArtifactDrawInstruction() {
@Override
public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
Association association = (Association) artifact;
String sourceRef = association.getSourceRef();
String targetRef = association.getTargetRef();
// source and target can be instance of FlowElement or Artifact
BaseElement sourceElement = bpmnModel.getFlowElement(sourceRef);
BaseElement targetElement = bpmnModel.getFlowElement(targetRef);
if (sourceElement == null) {
sourceElement = bpmnModel.getArtifact(sourceRef);
}
if (targetElement == null) {
targetElement = bpmnModel.getArtifact(targetRef);
}
List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement,
targetElement, graphicInfoList);
int[] xPoints = new int[graphicInfoList.size()];
int[] yPoints = new int[graphicInfoList.size()];
for (int i = 1; i < graphicInfoList.size(); i++) {
GraphicInfo graphicInfo = graphicInfoList.get(i);
GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
if (i == 1) {
xPoints[0] = (int) previousGraphicInfo.getX();
yPoints[0] = (int) previousGraphicInfo.getY();
}
xPoints[i] = (int) graphicInfo.getX();
yPoints[i] = (int) graphicInfo.getY();
}
AssociationDirection associationDirection = association.getAssociationDirection();
processDiagramCanvas.drawAssociation(xPoints, yPoints, associationDirection, false, scaleFactor);
}
});
}
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> highLightedFlows, String activityFontName, String labelFontName, ClassLoader customClassLoader,
double scaleFactor) {
return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, new ArrayList<String>(),
highLightedFlows, activityFontName, labelFontName, customClassLoader, scaleFactor).generateImage(
imageType);
}
@Override
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> highLightedFlows) {
return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, 1.0);
}
@Override
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> highLightedFlows, double scaleFactor) {
return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null,
scaleFactor);
}
@Override
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities) {
return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.<String>emptyList());
}
@Override
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
double scaleFactor) {
return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.<String>emptyList(),
scaleFactor);
}
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
String labelFontName, ClassLoader customClassLoader) {
return generateDiagram(bpmnModel, imageType, Collections.<String>emptyList(), Collections.<String>emptyList(),
activityFontName, labelFontName, customClassLoader, 1.0);
}
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
String labelFontName, ClassLoader customClassLoader, double scaleFactor) {
return generateDiagram(bpmnModel, imageType, Collections.<String>emptyList(), Collections.<String>emptyList(),
activityFontName, labelFontName, customClassLoader, scaleFactor);
}
@Override
public InputStream generatePngDiagram(BpmnModel bpmnModel) {
return generatePngDiagram(bpmnModel, 1.0);
}
@Override
public InputStream generatePngDiagram(BpmnModel bpmnModel, double scaleFactor) {
return generateDiagram(bpmnModel, "png", Collections.<String>emptyList(), Collections.<String>emptyList(),
scaleFactor);
}
@Override
public InputStream generateJpgDiagram(BpmnModel bpmnModel) {
return generateJpgDiagram(bpmnModel, 1.0);
}
@Override
public InputStream generateJpgDiagram(BpmnModel bpmnModel, double scaleFactor) {
return generateDiagram(bpmnModel, "jpg", Collections.<String>emptyList(), Collections.<String>emptyList());
}
public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> highLightedFlows, String activityFontName, String labelFontName, ClassLoader customClassLoader,
double scaleFactor) {
return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, new ArrayList<String>(),
highLightedFlows, activityFontName, labelFontName, customClassLoader, scaleFactor)
.generateBufferedImage(imageType);
}
public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> highLightedFlows, double scaleFactor) {
return generateImage(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null,
scaleFactor);
}
@Override
public BufferedImage generatePngImage(BpmnModel bpmnModel, double scaleFactor) {
return generateImage(bpmnModel, "png", Collections.<String>emptyList(), Collections.<String>emptyList(),
scaleFactor);
}
protected CustomProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType,
List<String> highLightedActivities, List<String> runningActivitiIdList, List<String> highLightedFlows,
String activityFontName, String labelFontName, ClassLoader customClassLoader, double scaleFactor) {
CustomProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType,
activityFontName, labelFontName, customClassLoader);
// Draw pool shape, if process is participant in collaboration
for (Pool pool : bpmnModel.getPools()) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
processDiagramCanvas.drawPoolOrLane(pool.getName(), graphicInfo);
}
// Draw lanes
for (Process process : bpmnModel.getProcesses()) {
for (Lane lane : process.getLanes()) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId());
processDiagramCanvas.drawPoolOrLane(lane.getName(), graphicInfo);
}
}
// Draw activities and their sequence-flows
/**
* 绘制流程图上的所有节点和流程线,对高亮显示的节点和流程线进行特殊处理
*/
for (FlowNode flowNode : bpmnModel.getProcesses().get(0).findFlowElementsOfType(FlowNode.class)) {
drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, runningActivitiIdList,
highLightedFlows, scaleFactor);
}
// Draw artifacts
for (Process process : bpmnModel.getProcesses()) {
for (Artifact artifact : process.getArtifacts()) {
drawArtifact(processDiagramCanvas, bpmnModel, artifact);
}
}
return processDiagramCanvas;
}
/**
* Desc: 绘制流程图上的所有节点和流程线,对高亮显示的节点和流程线进行特殊处理
*
* @param processDiagramCanvas
* @param bpmnModel
* @param flowNode
* @param highLightedActivities
* @param highLightedFlows
* @param scaleFactor
* @author Fuxs
*/
protected void drawActivity(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode,
List<String> highLightedActivities, List<String> runningActivitiIdList, List<String> highLightedFlows,
double scaleFactor) {
ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass());
if (drawInstruction != null) {
drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode);
// Gather info on the multi instance marker
boolean multiInstanceSequential = false, multiInstanceParallel = false, collapsed = false;
if (flowNode instanceof Activity) {
Activity activity = (Activity) flowNode;
MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics();
if (multiInstanceLoopCharacteristics != null) {
multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential();
multiInstanceParallel = !multiInstanceSequential;
}
}
// Gather info on the collapsed marker
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
if (flowNode instanceof SubProcess) {
collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded();
} else if (flowNode instanceof CallActivity) {
collapsed = true;
}
if (scaleFactor == 1.0) {
// Actually draw the markers
processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(),
(int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), multiInstanceSequential,
multiInstanceParallel, collapsed);
}
// Draw highlighted activities
if (highLightedActivities.contains(flowNode.getId())) {
/**
* 如果节点为当前正在处理中的节点,则红色高亮显示
*/
if (runningActivitiIdList.contains(flowNode.getId())) {
log.debug("[绘制]-当前正在处理中的节点-红色高亮显示节点[{}-{}]", flowNode.getId(), flowNode.getName());
drawRunningActivitiHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
} else {
log.debug("[绘制]-高亮显示节点[{}-{}]", flowNode.getId(), flowNode.getName());
drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
}
}
}
// Outgoing transitions of activity
/**
* 绘制当前节点的流程线
*/
for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId()));
String defaultFlow = null;
if (flowNode instanceof Activity) {
defaultFlow = ((Activity) flowNode).getDefaultFlow();
} else if (flowNode instanceof Gateway) {
defaultFlow = ((Gateway) flowNode).getDefaultFlow();
}
boolean isDefault = false;
if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) {
isDefault = true;
}
boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null
&& !(flowNode instanceof Gateway);
String sourceRef = sequenceFlow.getSourceRef();
String targetRef = sequenceFlow.getTargetRef();
FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef);
FlowElement targetElement = bpmnModel.getFlowElement(targetRef);
List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
if (graphicInfoList != null && graphicInfoList.size() > 0) {
graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement,
targetElement, graphicInfoList);
int[] xPoints = new int[graphicInfoList.size()];
int[] yPoints = new int[graphicInfoList.size()];
for (int i = 1; i < graphicInfoList.size(); i++) {
GraphicInfo graphicInfo = graphicInfoList.get(i);
GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
if (i == 1) {
xPoints[0] = (int) previousGraphicInfo.getX();
yPoints[0] = (int) previousGraphicInfo.getY();
}
xPoints[i] = (int) graphicInfo.getX();
yPoints[i] = (int) graphicInfo.getY();
}
processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault,
highLighted, scaleFactor);
// Draw sequenceflow label
/**
* 绘制流程线名称
*/
GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId());
// if (labelGraphicInfo != null) {
GraphicInfo lineCenter = getLineCenter(graphicInfoList);
processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, true);
// }
}
}
// Nested elements
if (flowNode instanceof FlowElementsContainer) {
for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) {
if (nestedFlowElement instanceof FlowNode) {
drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement, highLightedActivities,
runningActivitiIdList, highLightedFlows, scaleFactor);
}
}
}
}
/**
* This method makes coordinates of connection flow better.
*
* @param processDiagramCanvas
* @param bpmnModel
* @param sourceElement
* @param targetElement
* @param graphicInfoList
* @return
*/
protected static List<GraphicInfo> connectionPerfectionizer(CustomProcessDiagramCanvas processDiagramCanvas,
BpmnModel bpmnModel, BaseElement sourceElement, BaseElement targetElement,
List<GraphicInfo> graphicInfoList) {
GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId());
GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement.getId());
CustomProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement);
CustomProcessDiagramCanvas.SHAPE_TYPE targetShapeType = getShapeType(targetElement);
return processDiagramCanvas.connectionPerfectionizer(sourceShapeType, targetShapeType, sourceGraphicInfo,
targetGraphicInfo, graphicInfoList);
}
/**
* This method returns shape type of base element.<br>
* Each element can be presented as rectangle, rhombus, or ellipse.
*
* @param baseElement
* @return CustomProcessDiagramCanvas.SHAPE_TYPE
*/
protected static CustomProcessDiagramCanvas.SHAPE_TYPE getShapeType(BaseElement baseElement) {
if (baseElement instanceof Task || baseElement instanceof Activity || baseElement instanceof TextAnnotation) {
return CustomProcessDiagramCanvas.SHAPE_TYPE.Rectangle;
} else if (baseElement instanceof Gateway) {
return CustomProcessDiagramCanvas.SHAPE_TYPE.Rhombus;
} else if (baseElement instanceof Event) {
return CustomProcessDiagramCanvas.SHAPE_TYPE.Ellipse;
} else {
// unknown source element, just do not correct coordinates
}
return null;
}
protected static GraphicInfo getLineCenter(List<GraphicInfo> graphicInfoList) {
GraphicInfo gi = new GraphicInfo();
int[] xPoints = new int[graphicInfoList.size()];
int[] yPoints = new int[graphicInfoList.size()];
double length = 0;
double[] lengths = new double[graphicInfoList.size()];
lengths[0] = 0;
double m;
for (int i = 1; i < graphicInfoList.size(); i++) {
GraphicInfo graphicInfo = graphicInfoList.get(i);
GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
if (i == 1) {
xPoints[0] = (int) previousGraphicInfo.getX();
yPoints[0] = (int) previousGraphicInfo.getY();
}
xPoints[i] = (int) graphicInfo.getX();
yPoints[i] = (int) graphicInfo.getY();
length += Math.sqrt(Math.pow((int) graphicInfo.getX() - (int) previousGraphicInfo.getX(), 2) + Math.pow(
(int) graphicInfo.getY() - (int) previousGraphicInfo.getY(), 2));
lengths[i] = length;
}
m = length / 2;
int p1 = 0, p2 = 1;
for (int i = 1; i < lengths.length; i++) {
double len = lengths[i];
p1 = i - 1;
p2 = i;
if (len > m) {
break;
}
}
GraphicInfo graphicInfo1 = graphicInfoList.get(p1);
GraphicInfo graphicInfo2 = graphicInfoList.get(p2);
double AB = (int) graphicInfo2.getX() - (int) graphicInfo1.getX();
double OA = (int) graphicInfo2.getY() - (int) graphicInfo1.getY();
double OB = lengths[p2] - lengths[p1];
double ob = m - lengths[p1];
double ab = AB * ob / OB;
double oa = OA * ob / OB;
double mx = graphicInfo1.getX() + ab;
double my = graphicInfo1.getY() + oa;
gi.setX(mx);
gi.setY(my);
return gi;
}
protected void drawArtifact(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel,
Artifact artifact) {
ArtifactDrawInstruction drawInstruction = artifactDrawInstructions.get(artifact.getClass());
if (drawInstruction != null) {
drawInstruction.draw(processDiagramCanvas, bpmnModel, artifact);
}
}
private static void drawHighLight(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo
.getWidth(), (int) graphicInfo.getHeight());
}
/**
* Desc:绘制正在执行中的节点红色高亮显示
*
* @param processDiagramCanvas
* @param graphicInfo
* @author Fuxs
*/
private static void drawRunningActivitiHighLight(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
processDiagramCanvas.drawRunningActivitiHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo
.getWidth(), (int) graphicInfo.getHeight());
}
protected static CustomProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType,
String activityFontName, String labelFontName, ClassLoader customClassLoader) {
// We need to calculate maximum values to know how big the image will be in its entirety
double minX = Double.MAX_VALUE;
double maxX = 0;
double minY = Double.MAX_VALUE;
double maxY = 0;
for (Pool pool : bpmnModel.getPools()) {
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
minX = graphicInfo.getX();
maxX = graphicInfo.getX() + graphicInfo.getWidth();
minY = graphicInfo.getY();
maxY = graphicInfo.getY() + graphicInfo.getHeight();
}
List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
for (FlowNode flowNode : flowNodes) {
GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
// width
if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
}
if (flowNodeGraphicInfo.getX() < minX) {
minX = flowNodeGraphicInfo.getX();
}
// height
if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
}
if (flowNodeGraphicInfo.getY() < minY) {
minY = flowNodeGraphicInfo.getY();
}
for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
if (graphicInfoList != null) {
for (GraphicInfo graphicInfo : graphicInfoList) {
// width
if (graphicInfo.getX() > maxX) {
maxX = graphicInfo.getX();
}
if (graphicInfo.getX() < minX) {
minX = graphicInfo.getX();
}
// height
if (graphicInfo.getY() > maxY) {
maxY = graphicInfo.getY();
}
if (graphicInfo.getY() < minY) {
minY = graphicInfo.getY();
}
}
}
}
}
List<Artifact> artifacts = gatherAllArtifacts(bpmnModel);
for (Artifact artifact : artifacts) {
GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
if (artifactGraphicInfo != null) {
// width
if (artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) {
maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth();
}
if (artifactGraphicInfo.getX() < minX) {
minX = artifactGraphicInfo.getX();
}
// height
if (artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) {
maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight();
}
if (artifactGraphicInfo.getY() < minY) {
minY = artifactGraphicInfo.getY();
}
}
List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
if (graphicInfoList != null) {
for (GraphicInfo graphicInfo : graphicInfoList) {
// width
if (graphicInfo.getX() > maxX) {
maxX = graphicInfo.getX();
}
if (graphicInfo.getX() < minX) {
minX = graphicInfo.getX();
}
// height
if (graphicInfo.getY() > maxY) {
maxY = graphicInfo.getY();
}
if (graphicInfo.getY() < minY) {
minY = graphicInfo.getY();
}
}
}
}
int nrOfLanes = 0;
for (Process process : bpmnModel.getProcesses()) {
for (Lane l : process.getLanes()) {
nrOfLanes++;
GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
// // width
if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
maxX = graphicInfo.getX() + graphicInfo.getWidth();
}
if (graphicInfo.getX() < minX) {
minX = graphicInfo.getX();
}
// height
if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
maxY = graphicInfo.getY() + graphicInfo.getHeight();
}
if (graphicInfo.getY() < minY) {
minY = graphicInfo.getY();
}
}
}
// Special case, see http://jira.codehaus.org/browse/ACT-1431
if (flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) {
// Nothing to show
minX = 0;
minY = 0;
}
return new CustomProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, imageType,
activityFontName, labelFontName, customClassLoader);
}
protected static List<Artifact> gatherAllArtifacts(BpmnModel bpmnModel) {
List<Artifact> artifacts = new ArrayList<Artifact>();
for (Process process : bpmnModel.getProcesses()) {
artifacts.addAll(process.getArtifacts());
}
return artifacts;
}
protected static List<FlowNode> gatherAllFlowNodes(BpmnModel bpmnModel) {
List<FlowNode> flowNodes = new ArrayList<FlowNode>();
for (Process process : bpmnModel.getProcesses()) {
flowNodes.addAll(gatherAllFlowNodes(process));
}
return flowNodes;
}
protected static List<FlowNode> gatherAllFlowNodes(FlowElementsContainer flowElementsContainer) {
List<FlowNode> flowNodes = new ArrayList<FlowNode>();
for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
if (flowElement instanceof FlowNode) {
flowNodes.add((FlowNode) flowElement);
}
if (flowElement instanceof FlowElementsContainer) {
flowNodes.addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement));
}
}
return flowNodes;
}
public Map<Class<? extends BaseElement>, ActivityDrawInstruction> getActivityDrawInstructions() {
return activityDrawInstructions;
}
public void setActivityDrawInstructions(
Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions) {
this.activityDrawInstructions = activityDrawInstructions;
}
public Map<Class<? extends BaseElement>, ArtifactDrawInstruction> getArtifactDrawInstructions() {
return artifactDrawInstructions;
}
public void setArtifactDrawInstructions(
Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions) {
this.artifactDrawInstructions = artifactDrawInstructions;
}
protected interface ActivityDrawInstruction {
void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode);
}
protected interface ArtifactDrawInstruction {
void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact);
}
/**
* Desc: 自定义生成Diagram方法,添加对正在执行中的节点红色高亮显示
*
* @param bpmnModel
* @param imageType
* @param highLightedActivities
* @param runningActivitiIdList
* @param highLightedFlows
* @param activityFontName
* @param labelFontName
* @param annotationFontName
* @param customClassLoader
* @param scaleFactor
* @return
* @author Fuxs
*/
public InputStream generateDiagramCustom(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> runningActivitiIdList, List<String> highLightedFlows, String activityFontName,
String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor) {
// TODO Auto-generated method stub
return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, runningActivitiIdList,
highLightedFlows, activityFontName, labelFontName, customClassLoader, scaleFactor).generateImage(
imageType);
}
@Override
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
return generateDiagram(bpmnModel, imageType, Collections.<String>emptyList(), Collections.<String>emptyList(),
activityFontName, labelFontName, customClassLoader, 1.0);
}
@Override
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor) {
// TODO Auto-generated method stub
return generateDiagram(bpmnModel, imageType, Collections.<String>emptyList(), Collections.<String>emptyList(),
activityFontName, labelFontName, customClassLoader, scaleFactor);
}
@Override
public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName,
ClassLoader customClassLoader, double scaleFactor) {
// TODO Auto-generated method stub
return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, new ArrayList<String>(),
highLightedFlows, activityFontName, labelFontName, customClassLoader, scaleFactor).generateImage(
imageType);
}
}
自定义流程图画布
package com.ruifu.act.utils;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.engine.ActivitiException;
import org.activiti.image.exception.ActivitiImageException;
import org.activiti.image.util.ReflectUtil;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;
/**
* activiti6流程图绘制
*
* @author liuxz
*/
@Slf4j
public class CustomProcessDiagramCanvas {
public enum SHAPE_TYPE {
Rectangle, Rhombus, Ellipse
}
protected static final int ARROW_WIDTH = 5;
protected static final int CONDITIONAL_INDICATOR_WIDTH = 16;
protected static final int DEFAULT_INDICATOR_WIDTH = 10;
protected static final int MARKER_WIDTH = 12;
protected static final int FONT_SIZE = 12;
protected static final int FONT_SPACING = 2;
protected static final int TEXT_PADDING = 3;
protected static final int ANNOTATION_TEXT_PADDING = 7;
protected static final int LINE_HEIGHT = FONT_SIZE + FONT_SPACING;
/**
* Colors
*/
protected static Color TASK_BOX_COLOR = new Color(249, 249, 249);
protected static Color SUBPROCESS_BOX_COLOR = new Color(255, 255, 255);
protected static Color EVENT_COLOR = new Color(255, 255, 255);
protected static Color CONNECTION_COLOR = new Color(88, 88, 88);
protected static Color CONDITIONAL_INDICATOR_COLOR = new Color(255, 255, 255);
protected static Color RUNNING_HIGHLIGHT_COLOR = Color.RED;
protected static Color HIGHLIGHT_COLOR = Color.GREEN;
protected static Color LABEL_COLOR = new Color(112, 146, 190);
// protected static Color LABEL_COLOR = Color.blue;
protected static Color TASK_BORDER_COLOR = new Color(187, 187, 187);
protected static Color EVENT_BORDER_COLOR = new Color(88, 88, 88);
protected static Color SUBPROCESS_BORDER_COLOR = new Color(0, 0, 0);
// Fonts
protected static Font LABEL_FONT = new Font("微软雅黑", Font.ITALIC, 11);
protected static Font ANNOTATION_FONT = new Font("Arial", Font.PLAIN, FONT_SIZE);
protected static Font TASK_FONT = new Font("Arial", Font.PLAIN, FONT_SIZE);
// Strokes
//TODO 边框宽度修改
//protected static Stroke THICK_TASK_BORDER_STROKE = new BasicStroke(3.0f);
protected static Stroke THICK_TASK_BORDER_STROKE = new BasicStroke(2.0f);
protected static Stroke GATEWAY_TYPE_STROKE = new BasicStroke(3.0f);
protected static Stroke END_EVENT_STROKE = new BasicStroke(3.0f);
protected static Stroke MULTI_INSTANCE_STROKE = new BasicStroke(1.3f);
protected static Stroke EVENT_SUBPROCESS_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[]{1.0f}, 0.0f);
protected static Stroke NON_INTERRUPTING_EVENT_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[]{4.0f, 3.0f}, 0.0f);
protected static Stroke HIGHLIGHT_FLOW_STROKE = new BasicStroke(1.3f);
protected static Stroke ANNOTATION_STROKE = new BasicStroke(2.0f);
protected static Stroke ASSOCIATION_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[]{2.0f, 2.0f}, 0.0f);
// icons
protected static int ICON_PADDING = 5;
protected static BufferedImage USERTASK_IMAGE;
protected static BufferedImage SCRIPTTASK_IMAGE;
protected static BufferedImage SERVICETASK_IMAGE;
protected static BufferedImage RECEIVETASK_IMAGE;
protected static BufferedImage SENDTASK_IMAGE;
protected static BufferedImage MANUALTASK_IMAGE;
protected static BufferedImage BUSINESS_RULE_TASK_IMAGE;
protected static BufferedImage SHELL_TASK_IMAGE;
protected static BufferedImage MULE_TASK_IMAGE;
protected static BufferedImage CAMEL_TASK_IMAGE;
protected static BufferedImage TIMER_IMAGE;
protected static BufferedImage COMPENSATE_THROW_IMAGE;
protected static BufferedImage COMPENSATE_CATCH_IMAGE;
protected static BufferedImage ERROR_THROW_IMAGE;
protected static BufferedImage ERROR_CATCH_IMAGE;
protected static BufferedImage MESSAGE_THROW_IMAGE;
protected static BufferedImage MESSAGE_CATCH_IMAGE;
protected static BufferedImage SIGNAL_CATCH_IMAGE;
protected static BufferedImage SIGNAL_THROW_IMAGE;
protected int canvasWidth = -1;
protected int canvasHeight = -1;
protected int minX = -1;
protected int minY = -1;
protected BufferedImage processDiagram;
protected Graphics2D g;
protected FontMetrics fontMetrics;
protected boolean closed;
protected ClassLoader customClassLoader;
protected String activityFontName = "Arial";
protected String labelFontName = "Arial";
/**
* Creates an empty canvas with given width and height. Allows to specify minimal boundaries on the left and upper side of the canvas. This is useful for diagrams that have
* white space there. Everything beneath these minimum values will be cropped. It's also possible to pass a specific font name and a class loader for the icon images.
*/
public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, ClassLoader customClassLoader) {
this.canvasWidth = width;
this.canvasHeight = height;
this.minX = minX;
this.minY = minY;
if (activityFontName != null) {
this.activityFontName = activityFontName;
}
if (labelFontName != null) {
this.labelFontName = labelFontName;
}
this.customClassLoader = customClassLoader;
initialize(imageType);
}
/**
* Creates an empty canvas with given width and height. Allows to specify minimal boundaries on the left and upper side of the canvas. This is useful for diagrams that have
* white space there (eg Signavio). Everything beneath these minimum values will be cropped.
*
* @param minX Hint that will be used when generating the image. Parts that fall below minX on the horizontal scale will be cropped.
* @param minY Hint that will be used when generating the image. Parts that fall below minX on the horizontal scale will be cropped.
*/
public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
this.canvasWidth = width;
this.canvasHeight = height;
this.minX = minX;
this.minY = minY;
initialize(imageType);
}
public void initialize(String imageType) {
if ("png".equalsIgnoreCase(imageType)) {
this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
} else {
this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);
}
this.g = processDiagram.createGraphics();
if ("png".equalsIgnoreCase(imageType) == false) {
this.g.setBackground(new Color(255, 255, 255, 0));
this.g.clearRect(0, 0, canvasWidth, canvasHeight);
}
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setPaint(Color.black);
Font font = new Font(activityFontName, Font.BOLD, FONT_SIZE);
g.setFont(font);
this.fontMetrics = g.getFontMetrics();
// LABEL_FONT = new Font(labelFontName, Font.ITALIC, 10);
try {
USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/userTask.png", customClassLoader));
SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/scriptTask.png", customClassLoader));
SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/serviceTask.png", customClassLoader));
RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/receiveTask.png", customClassLoader));
SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/sendTask.png", customClassLoader));
MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/manualTask.png", customClassLoader));
BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/businessRuleTask.png", customClassLoader));
SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/shellTask.png", customClassLoader));
CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/camelTask.png", customClassLoader));
MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/muleTask.png", customClassLoader));
TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/timer.png", customClassLoader));
COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/compensate-throw.png", customClassLoader));
COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/compensate.png", customClassLoader));
ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/error-throw.png", customClassLoader));
ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/error.png", customClassLoader));
MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/message-throw.png", customClassLoader));
MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/message.png", customClassLoader));
SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/signal-throw.png", customClassLoader));
SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/signal.png", customClassLoader));
} catch (IOException e) {
log.warn("Could not load image for process diagram creation: {}", e.getMessage());
}
}
/**
* Generates an image of what currently is drawn on the canvas. Throws an {@link ActivitiException} when {@link #close()} is already called.
*/
public InputStream generateImage(String imageType) {
if (closed) {
throw new ActivitiImageException("ProcessDiagramGenerator already closed");
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
// Try to remove white space
minX = (minX <= 5) ? 5 : minX;
minY = (minY <= 5) ? 5 : minY;
BufferedImage imageToSerialize = processDiagram;
if (minX >= 0 && minY >= 0) {
imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5);
}
ImageIO.write(imageToSerialize, imageType, out);
} catch (IOException e) {
throw new ActivitiImageException("Error while generating process image", e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException ignore) {
// Exception is silently ignored
}
}
return new ByteArrayInputStream(out.toByteArray());
}
/**
* Generates an image of what currently is drawn on the canvas. Throws an {@link ActivitiException} when {@link #close()} is already called.
*/
public BufferedImage generateBufferedImage(String imageType) {
if (closed) {
throw new ActivitiImageException("ProcessDiagramGenerator already closed");
}
// Try to remove white space
minX = (minX <= 5) ? 5 : minX;
minY = (minY <= 5) ? 5 : minY;
BufferedImage imageToSerialize = processDiagram;
if (minX >= 0 && minY >= 0) {
imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5);
}
return imageToSerialize;
}
/**
* Closes the canvas which dissallows further drawing and releases graphical resources.
*/
public void close() {
g.dispose();
closed = true;
}
public void drawNoneStartEvent(GraphicInfo graphicInfo) {
drawStartEvent(graphicInfo, null, 1.0);
}
public void drawTimerStartEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawStartEvent(graphicInfo, TIMER_IMAGE, scaleFactor);
}
public void drawSignalStartEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawStartEvent(graphicInfo, SIGNAL_CATCH_IMAGE, scaleFactor);
}
public void drawMessageStartEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawStartEvent(graphicInfo, MESSAGE_CATCH_IMAGE, scaleFactor);
}
public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) {
Paint originalPaint = g.getPaint();
g.setPaint(EVENT_COLOR);
Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight());
g.fill(circle);
g.setPaint(EVENT_BORDER_COLOR);
g.draw(circle);
g.setPaint(originalPaint);
if (image != null) {
// calculate coordinates to center image
int imageX = (int) Math.round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (image.getWidth() / 2 * scaleFactor));
int imageY = (int) Math.round(graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (image.getHeight() / 2 * scaleFactor));
g.drawImage(image, imageX, imageY, (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), null);
}
}
public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
g.setPaint(EVENT_COLOR);
Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight());
g.fill(circle);
g.setPaint(EVENT_BORDER_COLOR);
if (scaleFactor == 1.0) {
g.setStroke(END_EVENT_STROKE);
} else {
g.setStroke(new BasicStroke(2.0f));
}
g.draw(circle);
g.setStroke(originalStroke);
g.setPaint(originalPaint);
}
public void drawErrorEndEvent(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawErrorEndEvent(graphicInfo, scaleFactor);
if (scaleFactor == 1.0) {
drawLabel(name, graphicInfo);
}
}
public void drawErrorEndEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawNoneEndEvent(graphicInfo, scaleFactor);
g.drawImage(ERROR_THROW_IMAGE, (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4)), (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4)),
(int) (ERROR_THROW_IMAGE.getWidth() / scaleFactor), (int) (ERROR_THROW_IMAGE.getHeight() / scaleFactor), null);
}
public void drawErrorStartEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawNoneStartEvent(graphicInfo);
g.drawImage(ERROR_CATCH_IMAGE, (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4)), (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4)),
(int) (ERROR_CATCH_IMAGE.getWidth() / scaleFactor), (int) (ERROR_CATCH_IMAGE.getHeight() / scaleFactor), null);
}
public void drawCatchingEvent(GraphicInfo graphicInfo, boolean isInterrupting, BufferedImage image, String eventType, double scaleFactor) {
// event circles
Ellipse2D outerCircle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight());
int innerCircleSize = (int) (4 / scaleFactor);
if (innerCircleSize == 0) {
innerCircleSize = 1;
}
int innerCircleX = (int) graphicInfo.getX() + innerCircleSize;
int innerCircleY = (int) graphicInfo.getY() + innerCircleSize;
int innerCircleWidth = (int) graphicInfo.getWidth() - (2 * innerCircleSize);
int innerCircleHeight = (int) graphicInfo.getHeight() - (2 * innerCircleSize);
Ellipse2D innerCircle = new Ellipse2D.Double(innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight);
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
g.setPaint(EVENT_COLOR);
g.fill(outerCircle);
g.setPaint(EVENT_BORDER_COLOR);
if (isInterrupting == false) {
g.setStroke(NON_INTERRUPTING_EVENT_STROKE);
}
g.draw(outerCircle);
g.setStroke(originalStroke);
g.setPaint(originalPaint);
g.draw(innerCircle);
if (image != null) {
// calculate coordinates to center image
int imageX = (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (image.getWidth() / 2 * scaleFactor));
int imageY = (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (image.getHeight() / 2 * scaleFactor));
if (scaleFactor == 1.0 && "timer".equals(eventType)) {
// move image one pixel to center timer image
imageX++;
imageY++;
}
g.drawImage(image, imageX, imageY, (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), null);
}
}
public void drawCatchingCompensateEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingCompensateEvent(graphicInfo, isInterrupting, scaleFactor);
drawLabel(name, graphicInfo);
}
public void drawCatchingCompensateEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingEvent(graphicInfo, isInterrupting, COMPENSATE_CATCH_IMAGE, "compensate", scaleFactor);
}
public void drawCatchingTimerEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingTimerEvent(graphicInfo, isInterrupting, scaleFactor);
drawLabel(name, graphicInfo);
}
public void drawCatchingTimerEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingEvent(graphicInfo, isInterrupting, TIMER_IMAGE, "timer", scaleFactor);
}
public void drawCatchingErrorEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingErrorEvent(graphicInfo, isInterrupting, scaleFactor);
drawLabel(name, graphicInfo);
}
public void drawCatchingErrorEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingEvent(graphicInfo, isInterrupting, ERROR_CATCH_IMAGE, "error", scaleFactor);
}
public void drawCatchingSignalEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingSignalEvent(graphicInfo, isInterrupting, scaleFactor);
drawLabel(name, graphicInfo);
}
public void drawCatchingSignalEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingEvent(graphicInfo, isInterrupting, SIGNAL_CATCH_IMAGE, "signal", scaleFactor);
}
public void drawCatchingMessageEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingEvent(graphicInfo, isInterrupting, MESSAGE_CATCH_IMAGE, "message", scaleFactor);
}
public void drawCatchingMessageEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
drawCatchingEvent(graphicInfo, isInterrupting, MESSAGE_CATCH_IMAGE, "message", scaleFactor);
drawLabel(name, graphicInfo);
}
public void drawThrowingCompensateEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawCatchingEvent(graphicInfo, true, COMPENSATE_THROW_IMAGE, "compensate", scaleFactor);
}
public void drawThrowingSignalEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawCatchingEvent(graphicInfo, true, SIGNAL_THROW_IMAGE, "signal", scaleFactor);
}
public void drawThrowingNoneEvent(GraphicInfo graphicInfo, double scaleFactor) {
drawCatchingEvent(graphicInfo, true, null, "none", scaleFactor);
}
public void drawSequenceflow(int srcX, int srcY, int targetX, int targetY, boolean conditional, double scaleFactor) {
drawSequenceflow(srcX, srcY, targetX, targetY, conditional, false, scaleFactor);
}
public void drawSequenceflow(int srcX, int srcY, int targetX, int targetY, boolean conditional, boolean highLighted, double scaleFactor) {
Paint originalPaint = g.getPaint();
if (highLighted) {
g.setPaint(HIGHLIGHT_COLOR);
}
Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY);
g.draw(line);
drawArrowHead(line, scaleFactor);
if (conditional) {
drawConditionalSequenceFlowIndicator(line, scaleFactor);
}
if (highLighted) {
g.setPaint(originalPaint);
}
}
public void drawAssociation(int[] xPoints, int[] yPoints, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
boolean conditional = false, isDefault = false;
drawConnection(xPoints, yPoints, conditional, isDefault, "association", associationDirection, highLighted, scaleFactor);
}
public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, boolean highLighted, double scaleFactor) {
drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted, scaleFactor);
}
public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, AssociationDirection associationDirection,
boolean highLighted, double scaleFactor) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
g.setPaint(CONNECTION_COLOR);
if ("association".equals(connectionType)) {
g.setStroke(ASSOCIATION_STROKE);
} else if (highLighted) {
g.setPaint(HIGHLIGHT_COLOR);
g.setStroke(HIGHLIGHT_FLOW_STROKE);
}
for (int i = 1; i < xPoints.length; i++) {
Integer sourceX = xPoints[i - 1];
Integer sourceY = yPoints[i - 1];
Integer targetX = xPoints[i];
Integer targetY = yPoints[i];
Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
g.draw(line);
}
if (isDefault) {
Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
drawDefaultSequenceFlowIndicator(line, scaleFactor);
}
if (conditional) {
Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
drawConditionalSequenceFlowIndicator(line, scaleFactor);
}
if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);
drawArrowHead(line, scaleFactor);
}
if (associationDirection.equals(AssociationDirection.BOTH)) {
Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
drawArrowHead(line, scaleFactor);
}
g.setPaint(originalPaint);
g.setStroke(originalStroke);
}
public void drawSequenceflowWithoutArrow(int srcX, int srcY, int targetX, int targetY, boolean conditional, double scaleFactor) {
drawSequenceflowWithoutArrow(srcX, srcY, targetX, targetY, conditional, false, scaleFactor);
}
public void drawSequenceflowWithoutArrow(int srcX, int srcY, int targetX, int targetY, boolean conditional, boolean highLighted, double scaleFactor) {
Paint originalPaint = g.getPaint();
if (highLighted) {
g.setPaint(HIGHLIGHT_COLOR);
}
Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY);
g.draw(line);
if (conditional) {
drawConditionalSequenceFlowIndicator(line, scaleFactor);
}
if (highLighted) {
g.setPaint(originalPaint);
}
}
public void drawArrowHead(Line2D.Double line, double scaleFactor) {
int doubleArrowWidth = (int) (2 * ARROW_WIDTH / scaleFactor);
if (doubleArrowWidth == 0) {
doubleArrowWidth = 2;
}
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0, 0);
int arrowHeadPoint = (int) (-ARROW_WIDTH / scaleFactor);
if (arrowHeadPoint == 0) {
arrowHeadPoint = -1;
}
arrowHead.addPoint(arrowHeadPoint, -doubleArrowWidth);
arrowHeadPoint = (int) (ARROW_WIDTH / scaleFactor);
if (arrowHeadPoint == 0) {
arrowHeadPoint = 1;
}
arrowHead.addPoint(arrowHeadPoint, -doubleArrowWidth);
AffineTransform transformation = new AffineTransform();
transformation.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
transformation.translate(line.x2, line.y2);
transformation.rotate((angle - Math.PI / 2d));
AffineTransform originalTransformation = g.getTransform();
g.setTransform(transformation);
g.fill(arrowHead);
g.setTransform(originalTransformation);
}
public void drawDefaultSequenceFlowIndicator(Line2D.Double line, double scaleFactor) {
double length = DEFAULT_INDICATOR_WIDTH / scaleFactor, halfOfLength = length / 2, f = 8;
Line2D.Double defaultIndicator = new Line2D.Double(-halfOfLength, 0, halfOfLength, 0);
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
double dx = f * Math.cos(angle), dy = f * Math.sin(angle), x1 = line.x1 + dx, y1 = line.y1 + dy;
AffineTransform transformation = new AffineTransform();
transformation.setToIdentity();
transformation.translate(x1, y1);
transformation.rotate((angle - 3 * Math.PI / 4));
AffineTransform originalTransformation = g.getTransform();
g.setTransform(transformation);
g.draw(defaultIndicator);
g.setTransform(originalTransformation);
}
public void drawConditionalSequenceFlowIndicator(Line2D.Double line, double scaleFactor) {
if (scaleFactor > 1.0) {
return;
}
int horizontal = (int) (CONDITIONAL_INDICATOR_WIDTH * 0.7);
int halfOfHorizontal = horizontal / 2;
int halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2;
Polygon conditionalIndicator = new Polygon();
conditionalIndicator.addPoint(0, 0);
conditionalIndicator.addPoint(-halfOfHorizontal, halfOfVertical);
conditionalIndicator.addPoint(0, CONDITIONAL_INDICATOR_WIDTH);
conditionalIndicator.addPoint(halfOfHorizontal, halfOfVertical);
AffineTransform transformation = new AffineTransform();
transformation.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
transformation.translate(line.x1, line.y1);
transformation.rotate((angle - Math.PI / 2d));
AffineTransform originalTransformation = g.getTransform();
g.setTransform(transformation);
g.draw(conditionalIndicator);
Paint originalPaint = g.getPaint();
g.setPaint(CONDITIONAL_INDICATOR_COLOR);
g.fill(conditionalIndicator);
g.setPaint(originalPaint);
g.setTransform(originalTransformation);
}
public void drawTask(BufferedImage icon, String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(name, graphicInfo);
g.drawImage(icon, (int) (graphicInfo.getX() + ICON_PADDING / scaleFactor), (int) (graphicInfo.getY() + ICON_PADDING / scaleFactor), (int) (icon.getWidth() / scaleFactor),
(int) (icon.getHeight() / scaleFactor), null);
}
public void drawTask(String name, GraphicInfo graphicInfo) {
drawTask(name, graphicInfo, false);
}
public void drawPoolOrLane(String name, GraphicInfo graphicInfo) {
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
g.drawRect(x, y, width, height);
// Add the name as text, vertical
if (name != null && name.length() > 0) {
// Include some padding
int availableTextSpace = height - 6;
// Create rotation for derived font
AffineTransform transformation = new AffineTransform();
transformation.setToIdentity();
transformation.rotate(270 * Math.PI / 180);
Font currentFont = g.getFont();
Font theDerivedFont = currentFont.deriveFont(transformation);
g.setFont(theDerivedFont);
String truncated = fitTextToWidth(name, availableTextSpace);
int realWidth = fontMetrics.stringWidth(truncated);
g.drawString(truncated, x + 2 + fontMetrics.getHeight(), 3 + y + availableTextSpace - (availableTextSpace - realWidth) / 2);
g.setFont(currentFont);
}
}
protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder) {
Paint originalPaint = g.getPaint();
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
// Create a new gradient paint for every task box, gradient depends on x and y and is not relative
g.setPaint(TASK_BOX_COLOR);
int arcR = 6;
if (thickBorder) {
arcR = 3;
}
// shape
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR);
g.fill(rect);
g.setPaint(TASK_BORDER_COLOR);
if (thickBorder) {
Stroke originalStroke = g.getStroke();
g.setStroke(THICK_TASK_BORDER_STROKE);
g.draw(rect);
g.setStroke(originalStroke);
} else {
g.draw(rect);
}
g.setPaint(originalPaint);
// text
if (name != null && name.length() > 0) {
int boxWidth = width - (2 * TEXT_PADDING);
int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
int boxX = x + width / 2 - boxWidth / 2;
int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2;
drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight);
}
}
protected void drawMultilineCentredText(String text, int x, int y, int boxWidth, int boxHeight) {
drawMultilineText(text, x, y, boxWidth, boxHeight, true);
}
protected void drawMultilineAnnotationText(String text, int x, int y, int boxWidth, int boxHeight) {
drawMultilineText(text, x, y, boxWidth, boxHeight, false);
}
protected void drawMultilineText(String text, int x, int y, int boxWidth, int boxHeight, boolean centered) {
// Create an attributed string based in input text
AttributedString attributedString = new AttributedString(text);
attributedString.addAttribute(TextAttribute.FONT, g.getFont());
attributedString.addAttribute(TextAttribute.FOREGROUND, Color.black);
AttributedCharacterIterator characterIterator = attributedString.getIterator();
int currentHeight = 0;
// Prepare a list of lines of text we'll be drawing
List<TextLayout> layouts = new ArrayList<TextLayout>();
String lastLine = null;
LineBreakMeasurer measurer = new LineBreakMeasurer(characterIterator, g.getFontRenderContext());
TextLayout layout = null;
while (measurer.getPosition() < characterIterator.getEndIndex() && currentHeight <= boxHeight) {
int previousPosition = measurer.getPosition();
// Request next layout
layout = measurer.nextLayout(boxWidth);
int height = ((Float) (layout.getDescent() + layout.getAscent() + layout.getLeading())).intValue();
if (currentHeight + height > boxHeight) {
// The line we're about to add should NOT be added anymore, append three dots to previous one instead
// to indicate more text is truncated
if (!layouts.isEmpty()) {
layouts.remove(layouts.size() - 1);
if (lastLine.length() >= 4) {
lastLine = lastLine.substring(0, lastLine.length() - 4) + "...";
}
layouts.add(new TextLayout(lastLine, g.getFont(), g.getFontRenderContext()));
}
} else {
layouts.add(layout);
lastLine = text.substring(previousPosition, measurer.getPosition());
currentHeight += height;
}
}
int currentY = y + (centered ? ((boxHeight - currentHeight) / 2) : 0);
int currentX = 0;
// Actually draw the lines
for (TextLayout textLayout : layouts) {
currentY += textLayout.getAscent();
currentX = x + (centered ? ((boxWidth - ((Double) textLayout.getBounds().getWidth()).intValue()) / 2) : 0);
textLayout.draw(g, currentX, currentY);
currentY += textLayout.getDescent() + textLayout.getLeading();
}
}
protected String fitTextToWidth(String original, int width) {
String text = original;
// remove length for "..."
int maxWidth = width - 10;
while (fontMetrics.stringWidth(text + "...") > maxWidth && text.length() > 0) {
text = text.substring(0, text.length() - 1);
}
if (!text.equals(original)) {
text = text + "...";
}
return text;
}
public void drawUserTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(USERTASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawScriptTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(SCRIPTTASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawServiceTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(SERVICETASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawReceiveTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(RECEIVETASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawSendTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(SENDTASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawManualTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(MANUALTASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawBusinessRuleTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(BUSINESS_RULE_TASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawCamelTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(CAMEL_TASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawMuleTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
drawTask(MULE_TASK_IMAGE, name, graphicInfo, scaleFactor);
}
public void drawExpandedSubProcess(String name, GraphicInfo graphicInfo, Boolean isTriggeredByEvent, double scaleFactor) {
RoundRectangle2D rect = new RoundRectangle2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight(), 8, 8);
// Use different stroke (dashed)
if (isTriggeredByEvent) {
Stroke originalStroke = g.getStroke();
g.setStroke(EVENT_SUBPROCESS_STROKE);
g.draw(rect);
g.setStroke(originalStroke);
} else {
Paint originalPaint = g.getPaint();
g.setPaint(SUBPROCESS_BOX_COLOR);
g.fill(rect);
g.setPaint(SUBPROCESS_BORDER_COLOR);
g.draw(rect);
g.setPaint(originalPaint);
}
if (scaleFactor == 1.0 && name != null && !name.isEmpty()) {
String text = fitTextToWidth(name, (int) graphicInfo.getWidth());
g.drawString(text, (int) graphicInfo.getX() + 10, (int) graphicInfo.getY() + 15);
}
}
public void drawCollapsedSubProcess(String name, GraphicInfo graphicInfo, Boolean isTriggeredByEvent) {
drawCollapsedTask(name, graphicInfo, false);
}
public void drawCollapsedCallActivity(String name, GraphicInfo graphicInfo) {
drawCollapsedTask(name, graphicInfo, true);
}
protected void drawCollapsedTask(String name, GraphicInfo graphicInfo, boolean thickBorder) {
// The collapsed marker is now visualized separately
drawTask(name, graphicInfo, thickBorder);
}
public void drawCollapsedMarker(int x, int y, int width, int height) {
// rectangle
int rectangleWidth = MARKER_WIDTH;
int rectangleHeight = MARKER_WIDTH;
Rectangle rect = new Rectangle(x + (width - rectangleWidth) / 2, y + height - rectangleHeight - 3, rectangleWidth, rectangleHeight);
g.draw(rect);
// plus inside rectangle
Line2D.Double line = new Line2D.Double(rect.getCenterX(), rect.getY() + 2, rect.getCenterX(), rect.getMaxY() - 2);
g.draw(line);
line = new Line2D.Double(rect.getMinX() + 2, rect.getCenterY(), rect.getMaxX() - 2, rect.getCenterY());
g.draw(line);
}
public void drawActivityMarkers(int x, int y, int width, int height, boolean multiInstanceSequential, boolean multiInstanceParallel, boolean collapsed) {
if (collapsed) {
if (!multiInstanceSequential && !multiInstanceParallel) {
drawCollapsedMarker(x, y, width, height);
} else {
drawCollapsedMarker(x - MARKER_WIDTH / 2 - 2, y, width, height);
if (multiInstanceSequential) {
drawMultiInstanceMarker(true, x + MARKER_WIDTH / 2 + 2, y, width, height);
} else {
drawMultiInstanceMarker(false, x + MARKER_WIDTH / 2 + 2, y, width, height);
}
}
} else {
if (multiInstanceSequential) {
drawMultiInstanceMarker(true, x, y, width, height);
} else if (multiInstanceParallel) {
drawMultiInstanceMarker(false, x, y, width, height);
}
}
}
public void drawGateway(GraphicInfo graphicInfo) {
Polygon rhombus = new Polygon();
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
rhombus.addPoint(x, y + (height / 2));
rhombus.addPoint(x + (width / 2), y + height);
rhombus.addPoint(x + width, y + (height / 2));
rhombus.addPoint(x + (width / 2), y);
g.draw(rhombus);
}
public void drawParallelGateway(GraphicInfo graphicInfo, double scaleFactor) {
// rhombus
drawGateway(graphicInfo);
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
if (scaleFactor == 1.0) {
// plus inside rhombus
Stroke orginalStroke = g.getStroke();
g.setStroke(GATEWAY_TYPE_STROKE);
Line2D.Double line = new Line2D.Double(x + 10, y + height / 2, x + width - 10, y + height / 2); // horizontal
g.draw(line);
line = new Line2D.Double(x + width / 2, y + height - 10, x + width / 2, y + 10); // vertical
g.draw(line);
g.setStroke(orginalStroke);
}
}
public void drawExclusiveGateway(GraphicInfo graphicInfo, double scaleFactor) {
// rhombus
drawGateway(graphicInfo);
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
int quarterWidth = width / 4;
int quarterHeight = height / 4;
if (scaleFactor == 1.0) {
// X inside rhombus
Stroke orginalStroke = g.getStroke();
g.setStroke(GATEWAY_TYPE_STROKE);
Line2D.Double line = new Line2D.Double(x + quarterWidth + 3, y + quarterHeight + 3, x + 3 * quarterWidth - 3, y + 3 * quarterHeight - 3);
g.draw(line);
line = new Line2D.Double(x + quarterWidth + 3, y + 3 * quarterHeight - 3, x + 3 * quarterWidth - 3, y + quarterHeight + 3);
g.draw(line);
g.setStroke(orginalStroke);
}
}
public void drawInclusiveGateway(GraphicInfo graphicInfo, double scaleFactor) {
// rhombus
drawGateway(graphicInfo);
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
int diameter = width / 2;
if (scaleFactor == 1.0) {
// circle inside rhombus
Stroke orginalStroke = g.getStroke();
g.setStroke(GATEWAY_TYPE_STROKE);
Ellipse2D.Double circle = new Ellipse2D.Double(((width - diameter) / 2) + x, ((height - diameter) / 2) + y, diameter, diameter);
g.draw(circle);
g.setStroke(orginalStroke);
}
}
public void drawEventBasedGateway(GraphicInfo graphicInfo, double scaleFactor) {
// rhombus
drawGateway(graphicInfo);
if (scaleFactor == 1.0) {
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
double scale = .6;
GraphicInfo eventInfo = new GraphicInfo();
eventInfo.setX(x + width * (1 - scale) / 2);
eventInfo.setY(y + height * (1 - scale) / 2);
eventInfo.setWidth(width * scale);
eventInfo.setHeight(height * scale);
drawCatchingEvent(eventInfo, true, null, "eventGateway", scaleFactor);
double r = width / 6.;
// create pentagon (coords with respect to center)
int topX = (int) (.95 * r); // top right corner
int topY = (int) (-.31 * r);
int bottomX = (int) (.59 * r); // bottom right corner
int bottomY = (int) (.81 * r);
int[] xPoints = new int[]{0, topX, bottomX, -bottomX, -topX};
int[] yPoints = new int[]{-(int) r, topY, bottomY, bottomY, topY};
Polygon pentagon = new Polygon(xPoints, yPoints, 5);
pentagon.translate(x + width / 2, y + width / 2);
// draw
g.drawPolygon(pentagon);
}
}
public void drawMultiInstanceMarker(boolean sequential, int x, int y, int width, int height) {
int rectangleWidth = MARKER_WIDTH;
int rectangleHeight = MARKER_WIDTH;
int lineX = x + (width - rectangleWidth) / 2;
int lineY = y + height - rectangleHeight - 3;
Stroke orginalStroke = g.getStroke();
g.setStroke(MULTI_INSTANCE_STROKE);
if (sequential) {
g.draw(new Line2D.Double(lineX, lineY, lineX + rectangleWidth, lineY));
g.draw(new Line2D.Double(lineX, lineY + rectangleHeight / 2, lineX + rectangleWidth, lineY + rectangleHeight / 2));
g.draw(new Line2D.Double(lineX, lineY + rectangleHeight, lineX + rectangleWidth, lineY + rectangleHeight));
} else {
g.draw(new Line2D.Double(lineX, lineY, lineX, lineY + rectangleHeight));
g.draw(new Line2D.Double(lineX + rectangleWidth / 2, lineY, lineX + rectangleWidth / 2, lineY + rectangleHeight));
g.draw(new Line2D.Double(lineX + rectangleWidth, lineY, lineX + rectangleWidth, lineY + rectangleHeight));
}
g.setStroke(orginalStroke);
}
public void drawHighLight(int x, int y, int width, int height) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
g.setPaint(HIGHLIGHT_COLOR);
g.setStroke(THICK_TASK_BORDER_STROKE);
//TODO 长方形弧度修改
//RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 0, 0);
g.draw(rect);
g.setPaint(originalPaint);
g.setStroke(originalStroke);
}
/**
* Desc: 绘制正在执行中的节点红色高亮显示
*
* @param x
* @param y
* @param width
* @param height
* @author Fuxs
*/
public void drawRunningActivitiHighLight(int x, int y, int width, int height) {
Paint originalPaint = g.getPaint();
Stroke originalStroke = g.getStroke();
g.setPaint(RUNNING_HIGHLIGHT_COLOR);
g.setStroke(THICK_TASK_BORDER_STROKE);
//todo 修改长方形弧度
//RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 0, 0);
g.draw(rect);
g.setPaint(originalPaint);
g.setStroke(originalStroke);
}
public void drawTextAnnotation(String text, GraphicInfo graphicInfo) {
int x = (int) graphicInfo.getX();
int y = (int) graphicInfo.getY();
int width = (int) graphicInfo.getWidth();
int height = (int) graphicInfo.getHeight();
Font originalFont = g.getFont();
Stroke originalStroke = g.getStroke();
g.setFont(ANNOTATION_FONT);
Path2D path = new Path2D.Double();
x += .5;
int lineLength = 18;
path.moveTo(x + lineLength, y);
path.lineTo(x, y);
path.lineTo(x, y + height);
path.lineTo(x + lineLength, y + height);
path.lineTo(x + lineLength, y + height - 1);
path.lineTo(x + 1, y + height - 1);
path.lineTo(x + 1, y + 1);
path.lineTo(x + lineLength, y + 1);
path.closePath();
g.draw(path);
int boxWidth = width - (2 * ANNOTATION_TEXT_PADDING);
int boxHeight = height - (2 * ANNOTATION_TEXT_PADDING);
int boxX = x + width / 2 - boxWidth / 2;
int boxY = y + height / 2 - boxHeight / 2;
if (text != null && text.isEmpty() == false) {
drawMultilineAnnotationText(text, boxX, boxY, boxWidth, boxHeight);
}
// restore originals
g.setFont(originalFont);
g.setStroke(originalStroke);
}
public void drawLabel(String text, GraphicInfo graphicInfo) {
drawLabel(text, graphicInfo, true);
}
/**
* 绘制流程线名称
* Desc:
*
* @param text
* @param graphicInfo
* @param centered
* @author Fuxs
*/
public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) {
float interline = 1.0f;
// text
if (text != null && text.length() > 0) {
Paint originalPaint = g.getPaint();
Font originalFont = g.getFont();
g.setPaint(LABEL_COLOR);
g.setFont(LABEL_FONT);
int wrapWidth = 100;
int textY = (int) (graphicInfo.getY() + graphicInfo.getHeight());
// TODO: use drawMultilineText()
AttributedString as = new AttributedString(text);
as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());
as.addAttribute(TextAttribute.FONT, g.getFont());
AttributedCharacterIterator aci = as.getIterator();
FontRenderContext frc = new FontRenderContext(null, true, false);
LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
while (lbm.getPosition() < text.length()) {
TextLayout tl = lbm.nextLayout(wrapWidth);
textY += tl.getAscent();
Rectangle2D bb = tl.getBounds();
double tX = graphicInfo.getX();
if (centered) {
tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
}
tl.draw(g, (float) tX, textY);
textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
}
// restore originals
g.setFont(originalFont);
g.setPaint(originalPaint);
}
}
/**
* This method makes coordinates of connection flow better.
*
* @param sourceShapeType
* @param targetShapeType
* @param sourceGraphicInfo
* @param targetGraphicInfo
* @param graphicInfoList
*/
public List<GraphicInfo> connectionPerfectionizer(SHAPE_TYPE sourceShapeType, SHAPE_TYPE targetShapeType, GraphicInfo sourceGraphicInfo, GraphicInfo targetGraphicInfo,
List<GraphicInfo> graphicInfoList) {
Shape shapeFirst = createShape(sourceShapeType, sourceGraphicInfo);
Shape shapeLast = createShape(targetShapeType, targetGraphicInfo);
if (graphicInfoList != null && graphicInfoList.size() > 0) {
GraphicInfo graphicInfoFirst = graphicInfoList.get(0);
GraphicInfo graphicInfoLast = graphicInfoList.get(graphicInfoList.size() - 1);
if (shapeFirst != null) {
graphicInfoFirst.setX(shapeFirst.getBounds2D().getCenterX());
graphicInfoFirst.setY(shapeFirst.getBounds2D().getCenterY());
}
if (shapeLast != null) {
graphicInfoLast.setX(shapeLast.getBounds2D().getCenterX());
graphicInfoLast.setY(shapeLast.getBounds2D().getCenterY());
}
Point p = null;
if (shapeFirst != null) {
Line2D.Double lineFirst = new Line2D.Double(graphicInfoFirst.getX(), graphicInfoFirst.getY(), graphicInfoList.get(1).getX(), graphicInfoList.get(1).getY());
p = getIntersection(shapeFirst, lineFirst);
if (p != null) {
graphicInfoFirst.setX(p.getX());
graphicInfoFirst.setY(p.getY());
}
}
if (shapeLast != null) {
Line2D.Double lineLast = new Line2D.Double(graphicInfoLast.getX(), graphicInfoLast.getY(), graphicInfoList.get(graphicInfoList.size() - 2).getX(),
graphicInfoList.get(graphicInfoList.size() - 2).getY());
p = getIntersection(shapeLast, lineLast);
if (p != null) {
graphicInfoLast.setX(p.getX());
graphicInfoLast.setY(p.getY());
}
}
}
return graphicInfoList;
}
/**
* This method creates shape by type and coordinates.
*
* @param shapeType
* @param graphicInfo
* @return Shape
*/
private static Shape createShape(SHAPE_TYPE shapeType, GraphicInfo graphicInfo) {
if (SHAPE_TYPE.Rectangle.equals(shapeType)) {
// source is rectangle
return new Rectangle2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight());
} else if (SHAPE_TYPE.Rhombus.equals(shapeType)) {
// source is rhombus
Path2D.Double rhombus = new Path2D.Double();
rhombus.moveTo(graphicInfo.getX(), graphicInfo.getY() + graphicInfo.getHeight() / 2);
rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2, graphicInfo.getY() + graphicInfo.getHeight());
rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth(), graphicInfo.getY() + graphicInfo.getHeight() / 2);
rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2, graphicInfo.getY());
rhombus.lineTo(graphicInfo.getX(), graphicInfo.getY() + graphicInfo.getHeight() / 2);
rhombus.closePath();
return rhombus;
} else if (SHAPE_TYPE.Ellipse.equals(shapeType)) {
// source is ellipse
return new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight());
} else {
// unknown source element, just do not correct coordinates
}
return null;
}
/**
* This method returns intersection point of shape border and line.
*
* @param shape
* @param line
* @return Point
*/
private static Point getIntersection(Shape shape, Line2D.Double line) {
if (shape instanceof Ellipse2D) {
return getEllipseIntersection(shape, line);
} else if (shape instanceof Rectangle2D || shape instanceof Path2D) {
return getShapeIntersection(shape, line);
} else {
// something strange
return null;
}
}
/**
* This method calculates ellipse intersection with line
*
* @param shape Bounds of this shape used to calculate parameters of inscribed into this bounds ellipse.
* @param line
* @return Intersection point
*/
private static Point getEllipseIntersection(Shape shape, Line2D.Double line) {
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
double x = shape.getBounds2D().getWidth() / 2 * Math.cos(angle) + shape.getBounds2D().getCenterX();
double y = shape.getBounds2D().getHeight() / 2 * Math.sin(angle) + shape.getBounds2D().getCenterY();
Point p = new Point();
p.setLocation(x, y);
return p;
}
/**
* This method calculates shape intersection with line.
*
* @param shape
* @param line
* @return Intersection point
*/
private static Point getShapeIntersection(Shape shape, Line2D.Double line) {
PathIterator it = shape.getPathIterator(null);
double[] coords = new double[6];
double[] pos = new double[2];
Line2D.Double l = new Line2D.Double();
while (!it.isDone()) {
int type = it.currentSegment(coords);
switch (type) {
case PathIterator.SEG_MOVETO:
pos[0] = coords[0];
pos[1] = coords[1];
break;
case PathIterator.SEG_LINETO:
l = new Line2D.Double(pos[0], pos[1], coords[0], coords[1]);
if (line.intersectsLine(l)) {
return getLinesIntersection(line, l);
}
pos[0] = coords[0];
pos[1] = coords[1];
break;
case PathIterator.SEG_CLOSE:
break;
default:
// whatever
}
it.next();
}
return null;
}
/**
* This method calculates intersections of two lines.
*
* @param a Line 1
* @param b Line 2
* @return Intersection point
*/
private static Point getLinesIntersection(Line2D a, Line2D b) {
double d = (a.getX1() - a.getX2()) * (b.getY2() - b.getY1()) - (a.getY1() - a.getY2()) * (b.getX2() - b.getX1());
double da = (a.getX1() - b.getX1()) * (b.getY2() - b.getY1()) - (a.getY1() - b.getY1()) * (b.getX2() - b.getX1());
// double db = (a.getX1()-a.getX2())*(a.getY1()-b.getY1()) - (a.getY1()-a.getY2())*(a.getX1()-b.getX1());
double ta = da / d;
// double tb = db/d;
Point p = new Point();
p.setLocation(a.getX1() + ta * (a.getX2() - a.getX1()), a.getY1() + ta * (a.getY2() - a.getY1()));
return p;
}
}
效果:…Activiti资料是真的少…