Activiti實現自定義流程圖顏色

實現效果:java

TIM截圖20180705145356.png

版本聲明:Activiti 5.22.0mysql

需求:Activiti原生代碼在展現動態流程圖時,僅支持高亮當前節點,且高亮顏色爲紅色,如何才能實現這樣一個需求:要求在展現動態流程圖時,走過的歷史節點顯示爲綠色(顏色可配置),當前節點高亮爲紅色(顏色可配置)?下面講述下思路。spring

一、重寫ProcessDiagramGenerator接口中的generateDiagram方法,增長color參數:(currIds爲支持並行網關節點增長)sql

package com.fengunion.scf.data.workflow.manager;

import java.awt.Color;
import java.io.InputStream;
import java.util.List;
import java.util.Set;

import org.activiti.bpmn.model.BpmnModel;
import org.activiti.image.ProcessDiagramGenerator;

public interface CustomProcessDiagramGeneratorI extends ProcessDiagramGenerator {
	InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
			List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName,
			ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set<String> currIds);
}

二、在Activiti配置中注入自定義的CustomProcessDiagramGeneratorI(此處爲SpringBoot方式):shell

import java.util.ArrayList;
import java.util.List;

import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import com.fengunion.scf.data.workflow.manager.CustomGroupEntityManagerFactory;
import com.fengunion.scf.data.workflow.manager.CustomProcessDiagramGeneratorI;
import com.fengunion.scf.data.workflow.manager.CustomUserEntityManagerFactory;
import com.fengunion.scf.data.workflow.manager.ProcessHistoryManagerSessionFactory;


@Configuration
public class ActivitiConfiguration implements ProcessEngineConfigurationConfigurer{
	
	@Autowired
	private CustomUserEntityManagerFactory customUserEntityManagerFactory;
	
	@Autowired
	private CustomGroupEntityManagerFactory customGroupEntityManagerFactory;
	
	@Autowired
	private ProcessHistoryManagerSessionFactory processHistoryManagerSessionFactory;
	
	@Autowired
	private CustomProcessDiagramGeneratorI customProcessDiagramGeneratorI;
	
	@Override
	public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
		// TODO Auto-generated method stub
		//processEngineConfiguration.setDataSource(dataSource);
		processEngineConfiguration.setDatabaseSchemaUpdate("none");// none true
		processEngineConfiguration.setDatabaseType("mysql");

		//processEngineConfiguration.setTransactionManager(transactionManager);

		// 流程圖字體
		processEngineConfiguration.setActivityFontName("宋體");
		processEngineConfiguration.setAnnotationFontName("宋體");
		processEngineConfiguration.setLabelFontName("宋體");

		processEngineConfiguration.setJpaHandleTransaction(false);
		processEngineConfiguration.setJpaCloseEntityManager(false);
		//
//		processEngineConfiguration.setMailServerHost(mailProperty.getMailServerHost());
//		processEngineConfiguration.setMailServerUsername(mailProperty.getMailServerUsername());
//		processEngineConfiguration.setMailServerPassword(mailProperty.getMailServerPassword());
//		processEngineConfiguration.setMailServerPort(mailProperty.getMailServerPort());
		//
		processEngineConfiguration.setJobExecutorActivate(false);
		processEngineConfiguration.setAsyncExecutorEnabled(false);
		//processEngineConfiguration.setAsyncExecutorActivate(false);
 		//自定義用戶和組
		List<SessionFactory> customSessionFactories = new ArrayList<>();
		customSessionFactories.add(customUserEntityManagerFactory);
		customSessionFactories.add(customGroupEntityManagerFactory);
		customSessionFactories.add(processHistoryManagerSessionFactory);
		processEngineConfiguration.setCustomSessionFactories(customSessionFactories);
		
	//自定義流程圖樣式
	processEngineConfiguration.setProcessDiagramGenerator(customProcessDiagramGeneratorI);
	}
}

三、實現自定義的CustomProcessDiagramGenerator類:canvas

package com.fengunion.scf.data.workflow.manager;

import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Set;

@Component
public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator implements CustomProcessDiagramGeneratorI{
	//預初始化流程圖繪製,大大提高了系統啓動後首次查看流程圖的速度
	static {
		new CustomProcessDiagramCanvas(10,10,0,0,"png", "宋體","宋體","宋體",null);
	}
	
	public CustomProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName,
			String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor,
			Color [] colors, Set<String> currIds) {
		
		if(null == highLightedActivities) {
			highLightedActivities = Collections.<String>emptyList();
		}
		if(null == highLightedFlows) {
			highLightedFlows = Collections.<String>emptyList();
		}
		
		prepareBpmnModel(bpmnModel);
	    
	    CustomProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, 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 (Process process: bpmnModel.getProcesses()) {
	    	List<FlowNode> flowNodeList= process.findFlowElementsOfType(FlowNode.class);
	      for (FlowNode flowNode : flowNodeList) {
	    	  drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor, colors, currIds);
	      }
	    }
	    
	    // Draw artifacts
	    for (Process process : bpmnModel.getProcesses()) {
	      
	      for (Artifact artifact : process.getArtifacts()) {
	        drawArtifact(processDiagramCanvas, bpmnModel, artifact);
	      }
	      
	      List<SubProcess> subProcesses = process.findFlowElementsOfType(SubProcess.class, true);
	      if (subProcesses != null) {
	        for (SubProcess subProcess : subProcesses) {
	          for (Artifact subProcessArtifact : subProcess.getArtifacts()) {
	            drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact);
	          }
	        }
	      }
	    }
	    
	    return processDiagramCanvas;
	}

	protected void drawActivity(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode,
			List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, Color[] colors, Set<String> currIds) {
		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(!CollectionUtils.isEmpty(currIds)
                      &&currIds.contains(flowNode.getId())
                      && !(flowNode instanceof Gateway)) {//非結束節點,而且是當前節點
	    	      drawHighLight((flowNode instanceof StartEvent), processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()), colors[1]); 
	    	  }else {//普通節點
	    		  drawHighLight((flowNode instanceof StartEvent)||(flowNode instanceof EndEvent),processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()), colors[0]);
	    	  }	        
	      }

	    }
	    
	    // Outgoing transitions of activity
	    for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
	    	String flowId = sequenceFlow.getId();
	      boolean highLighted = (highLightedFlows.contains(flowId));
	      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(flowId)) {
	        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(flowId);
	      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, false, isDefault, highLighted, scaleFactor, colors[0]);
	        
	        // Draw sequenceflow label
//	        GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(flowId);
//	        if (labelGraphicInfo != null) {
//	          processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false);
//	        }else {//解決流程圖連線名稱不顯示的BUG
	        	GraphicInfo lineCenter = getLineCenter(graphicInfoList);
	        	processDiagramCanvas.drawLabel(highLighted, sequenceFlow.getName(), lineCenter, Math.abs(xPoints[1]-xPoints[0]) >= 5);
//	        }
	      }
	    }

	 // Nested elements
	    if (flowNode instanceof FlowElementsContainer) {
	      for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) {
	        if (nestedFlowElement instanceof FlowNode) {
	          drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement, 
	              highLightedActivities, highLightedFlows, scaleFactor);
	        }
	      }
	    }
	}
	protected void drawHighLight(boolean isStartOrEnd, CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo, Color color) {
      processDiagramCanvas.drawHighLight(isStartOrEnd, (int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), color);
    }
    
    protected static CustomProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType,
    	      String activityFontName, String labelFontName, String annotationFontName, 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 https://activiti.atlassian.net/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, annotationFontName, customClassLoader);
    	  }

	@Override
	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,
			List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName,
			ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set<String> currIds) {
		CustomProcessDiagramCanvas customProcessDiagramCanvas = generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, 
		        activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor,colors, currIds);
		BufferedImage bufferedImage = customProcessDiagramCanvas.generateBufferedImage(imageType);
		ByteArrayOutputStream bs = new ByteArrayOutputStream();  
		ImageOutputStream imOut;
		try {
			imOut = ImageIO.createImageOutputStream(bs);
			ImageIO.write(bufferedImage, "PNG", imOut);
		} catch (IOException e) {
			e.printStackTrace();
		}  
		  
		InputStream is = new ByteArrayInputStream(bs.toByteArray());
		return is;
	}
	@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, annotationFontName, customClassLoader, 1.0, new Color[] {Color.BLACK, Color.BLACK}, null);
	}
	
}

四、重寫DefaultProcessDiagramCanvas類drawHighLight和drawSequenceflow方法,實現走過的歷史節點高亮功能:緩存

package com.fengunion.scf.data.workflow.manager;

import java.awt.Color;
import java.awt.Font;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;

import javax.imageio.ImageIO;

import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.image.exception.ActivitiImageException;
import org.activiti.image.impl.DefaultProcessDiagramCanvas;
import org.activiti.image.util.ReflectUtil;

import com.fengunion.scf.data.workflow.common.constant.WorkflowConstants;

public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {

    protected static Color LABEL_COLOR = new Color(0, 0, 0);

	//font
	protected String activityFontName = "宋體";
    protected String labelFontName = "宋體";
    protected String annotationFontName = "宋體";
    
	private static volatile boolean flag = false;

	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
		super(width, height, minX, minY, imageType);
	}

	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType,
			String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
		super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName,
				customClassLoader);
	}

	public void drawHighLight(boolean isStartOrEnd, int x, int y, int width, int height, Color color) {
		Paint originalPaint = g.getPaint();
		Stroke originalStroke = g.getStroke();

		g.setPaint(color);
		g.setStroke(MULTI_INSTANCE_STROKE);
		if (isStartOrEnd) {// 開始、結束節點畫圓
			g.drawOval(x, y, width, height);
		} else {// 非開始、結束節點畫圓角矩形
			RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 5, 5);
			g.draw(rect);
		}
		g.setPaint(originalPaint);
		g.setStroke(originalStroke);
	}

	public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,
			boolean highLighted, double scaleFactor, Color color) {
		drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted,
				scaleFactor, color);
	}

	public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,
			String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor,
			Color color) {

		Paint originalPaint = g.getPaint();
		Stroke originalStroke = g.getStroke();

		g.setPaint(CONNECTION_COLOR);
		if (connectionType.equals("association")) {
			g.setStroke(ASSOCIATION_STROKE);
		} else if (highLighted) {
			g.setPaint(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 drawLabel(boolean highLighted, 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();
			if (highLighted) {
				g.setPaint(WorkflowConstants.COLOR_NORMAL);
			} else {
				g.setPaint(LABEL_COLOR);
			}
			g.setFont(new Font(labelFontName, Font.BOLD, 10));

			int wrapWidth = 100;
			int textY = (int) graphicInfo.getY();

			// 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);
		}
	}

	@Override
	public BufferedImage generateBufferedImage(String imageType) {
		if (closed) {
			throw new ActivitiImageException("ProcessDiagramGenerator already closed");
		}

		// Try to remove white space
		minX = (minX <= WorkflowConstants.PROCESS_PADDING) ? WorkflowConstants.PROCESS_PADDING : minX;
		minY = (minY <= WorkflowConstants.PROCESS_PADDING) ? WorkflowConstants.PROCESS_PADDING : minY;
		BufferedImage imageToSerialize = processDiagram;
		if (minX >= 0 && minY >= 0) {
			imageToSerialize = processDiagram.getSubimage(
					minX - WorkflowConstants.PROCESS_PADDING,
					minY - WorkflowConstants.PROCESS_PADDING, 
					canvasWidth - minX + WorkflowConstants.PROCESS_PADDING,
					canvasHeight - minY + WorkflowConstants.PROCESS_PADDING);
		}
		return imageToSerialize;
	}

	@Override
	public void initialize(String imageType) {	
	    this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);    
	    this.g = processDiagram.createGraphics();

	    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);
	    ANNOTATION_FONT = new Font(annotationFontName, Font.PLAIN, FONT_SIZE);
	    //優化加載速度
	    if(flag) {
			return;
		}
	    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));
/*	      String baseUrl = Thread.currentThread().getContextClassLoader().getResource("static/img/activiti/").getPath();
	      SCRIPTTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"scriptTask.png"));
	      USERTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"userTask.png"));
	      SERVICETASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"serviceTask.png"));
	      RECEIVETASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"receiveTask.png"));
	      SENDTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"sendTask.png"));
	      MANUALTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"manualTask.png"));
	      BUSINESS_RULE_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"businessRuleTask.png"));
	      SHELL_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"shellTask.png"));
	      CAMEL_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"camelTask.png"));
	      MULE_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"muleTask.png"));
	      
	      TIMER_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"timer.png"));
	      COMPENSATE_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"compensate-throw.png"));
	      COMPENSATE_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"compensate.png"));
	      ERROR_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"error-throw.png"));
	      ERROR_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"error.png"));
	      MESSAGE_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"message-throw.png"));
	      MESSAGE_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"message.png"));
	      SIGNAL_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"signal-throw.png"));
	      SIGNAL_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"signal.png"));*/
	      flag = true;
	    } catch (IOException e) {
	      flag = false;
	      LOGGER.warn("Could not load image for process diagram creation: {}", e.getMessage());
	    }  
	}

	
}

總結:核心思想就是重寫Activiti生成動態流程圖的方法(須要重寫畫節點、畫線的方法),增長顏色配置參數。 鑑於有小夥伴私信找我要自定義的常量類,現將WorkflowConstants類奉上:ide

package com.fengunion.scf.data.workflow.common.constant;

import com.google.common.collect.Lists;

import java.awt.Color;
import java.util.List;

/** 
 * 變量類
 *@author jjd 
 **/
public final class WorkflowConstants {
	
	/**businessKey**/
	public static final String WORKLOW_BUSINESS_KEY = "businessKey";
	/**按鈕網關**/
	public static final String WAY_TYPE = "wayType";
	/**按鈕網關**/
	public static final String WAY_TYPE_PREFIX = "way_type_";
	/**項目id**/
	public static final String PROJECT_ID = "projectId";
	/**核心企業Id變量**/
	public static final String CORE_ENTERPRISE_ID="coreEnterpriseId";
	/**鏈屬企業Id變量**/
	public static final String CHAIN_ENTERPRISE_ID="chainEnterpriseId";
	/**銀行企業Id變量**/
	public static final String BANK_ENTERPRISE_ID="bankEnterpriseId";
	/**保理公司Id變量**/
	public static final String BAOLI_ENTERPRISE_ID="baoliEnterpriseId";
	/**立帳開立企業Id變量**/
	public static final String START_ENTERPRISE_ID="startEnterpriseId";
	/**立帳合做企業Id變量**/
	public static final String PARTNER_ENTERPRISE_ID="partnerEnterpriseId";
	/**母公司企業id**/
	public static final String PARENT_ENTERPRISE_ID="parentEnterpriseId";
	/**指定簽收企業id**/
	public static final String RECEIVE_ENTERPRISE_ID ="receiveEnterpriseId";
	/**轉出方企業Id變量**/
	public static final String TRANSFER_ENTERPRISE_ID="transEnterpriseId";
	/**指定簽收企業id**/
	public static final String AC_TASK_ID ="acTaskId";
	/**企業全部角色**/
	public static final String ENT_ALL_ROLE ="all";
	/**運營全部角色**/
	public static final String OPER_ALL_ROLE ="oper";
	/**流程定義緩存時間**/
	public static final int PROCESS_DEFINITION_CACHE = 60;
	/**流程實例激活**/
    public static final int PROCESS_INSTANCE_ACTIVE = 1;
    
    /**流程實例掛起**/
    public static final int PROCESS_INSTANCE_SUSPEND = 0;
	
	/**讀取圖片**/
	public static final String READ_IMAGE = "image";

	/**讀取xml**/
	public static final String READ_XML = "xml";
	
	/**流程激活**/
	public static final Integer ACTIVE_PROCESSDEFINITION = 1;
	
	/**流程掛起**/
	public static final Integer SUSPEND_PROCESSDEFINITION = 2;
	
	/**流程狀態:0-所有,1-正常,2-已掛起**/
	public static final int QUERY_ALL       = 0;
	public static final int QUERY_NORMAL    = 1;
	public static final int QUERY_SUSPENDED = 2;
	
	/**流程實例狀態:0-所有,1-正常,2-已刪除**/
	public static final int INSTANCE_ALL       = 0;
	public static final int INSTANCE_NOT_DELETED    = 1;
	public static final int INSTANCE_DELETED = 2;
	
	/** 系統管理員ID **/
	public static final String INTERFACE_SYSTEM_ID = "-1";
	/** 系統管理員名稱 **/
	public static final String INTERFACE_SYSTEM_NAME = "系統操做";
	
	/** 流程部署類型:1-啓動並激活,2-啓動即掛起 **/
	public static final int PROCESS_START_ACTIVE = 1;
	public static final int PROCESS_START_SUSPEND = 2;
	
	/** 用於標識流程項目配置信息校驗結果:1:新流程,2:新版本, 3:流程類別有誤  **/
	public static final int CHECK_NEW_PROCESS = 1;
	public static final int CHECK_NEW_VERSION = 2;
	public static final int CHECK_ERROR_PROCESS_TYPE = 3;
	
	/** 默認網關條件值 **/
	public static final Integer default_GATEWAY_CONDITION_VALUE = 1;
	
	/** 工做流-業務狀態表數據類型:1-工做流狀態,2-業務狀態 **/
	public static final Integer PROCESS_STATUS = 1;
	public static final Integer BIZNESS_STATUS = 2;
	
	/** 新增流程時標識:1-直接保存,2-提示覆蓋 **/
	public static final Integer PROCESS_STATUS_SAVE = 1;
	public static final Integer BIZNESS_STATUS_WARN = 2;
	
	/** 模板類型標識:1-新建立或直接導入的模板,2-默認模板生成 **/
	public static final Integer MODEL_TYPE_1 = 1;
	public static final Integer MODEL_TYPE_2 = 2;
	
	/** 查詢流程定義標識:1-查詢最新版本流程定義,2-查詢全部版本 **/
	public static final Integer QUERY_PROCESS_LATEST_VERSION = 1;
	public static final Integer QUERY_PROCESS_ALL_VERSION = 2;
	
	/** 按鈕網關 經過1 */
    public static final String WAY_TYPE_PASS = "1";
    /** 按鈕網關 駁回或結束0 */
    public static final String WAY_TYPE_REJECT = "0";
    
    /** 按鈕網關 退回2 */
    public static final String WAY_TYPE_BACK = "2";
    
    /**任務參數爲空**/
    public static final int TASK_CHECK_PARAM_NULL = -1;
    /**任務已辦理**/
    public static final int TASK_CHECK_COMPLETED = 1;
    /**無權限辦理**/
    public static final int TASK_CHECK_NO_PERMISSIONS= 2;
    /**任務校驗經過**/
    public static final int TASK_CHECK_PASS = 0;
    /** 動態流程圖顏色定義 **/
    public static final Color COLOR_NORMAL = new Color(0, 205, 0);
    public static final Color COLOR_CURRENT = new Color(255, 0, 0);
    
    /** 定義生成流程圖時的邊距(像素) **/
    public static final int PROCESS_PADDING = 5;

    /** 定義新版業務進度查詢包含的流程類型 **/
	public static final List<ProcessTypeEnum> INCLUDE_PROCEE_TYPE = Lists.newArrayList(
			ProcessTypeEnum.BUILD_ACCOUNT_APPLY,
			ProcessTypeEnum.CREDIT_LETTER_APPLY,
			ProcessTypeEnum.CREDIT_LETTER_TRANSFER,
			ProcessTypeEnum.CREDIT_LETTER_CASH);

}

五、調用畫圖service層oop

/**
	 * @讀取動態流程圖
	 */
	public void readProcessImg(String processInstanceId, HttpServletResponse response) throws IOException {
		if (StringUtils.isBlank(processInstanceId)) {
			logger.error("參數爲空");
		}
		HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
				.processInstanceId(processInstanceId).singleResult();
		BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
		ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity) repositoryService
				.getProcessDefinition(processInstance.getProcessDefinitionId());
		List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery()
				.processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
		// 高亮環節id集合
		List<String> highLightedActivitis = new ArrayList<String>();
		// 高亮線路id集合
		List<String> highLightedFlows = getHighLightedFlows(definitionEntity, highLightedActivitList);
		for (HistoricActivityInstance tempActivity : highLightedActivitList) {
			String activityId = tempActivity.getActivityId();
			highLightedActivitis.add(activityId);
		}

        Set<String> currIds = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).list()
                .stream().map(e->e.getActivityId()).collect(Collectors.toSet());

		CustomProcessDiagramGeneratorI diagramGenerator = (CustomProcessDiagramGeneratorI) processEngineConfiguration
				.getProcessDiagramGenerator();
		InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis,
				highLightedFlows, "宋體", "宋體", "宋體", null, 1.0, new Color[] { WorkflowConstants.COLOR_NORMAL, WorkflowConstants.COLOR_CURRENT }, currIds);
		// 輸出資源內容到相應對象
		byte[] b = new byte[1024];
		int len;
		while ((len = imageStream.read(b, 0, 1024)) != -1) {
			response.getOutputStream().write(b, 0, len);
		}
	}

六、getHighLightedFlows方法調整爲以下:(註釋部分)字體

/**
	 * 獲取須要高亮的線
	 * 
	 * @param processDefinitionEntity
	 * @param historicActivityInstances
	 * @return
	 */
	private List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinitionEntity,
			List<HistoricActivityInstance> historicActivityInstances) {
		List<String> highFlows = new ArrayList<>();// 用以保存高亮的線flowId
		for (int i = 0; i < historicActivityInstances.size() - 1; i++) {// 對歷史流程節點進行遍歷
			ActivityImpl activityImpl = processDefinitionEntity
					.findActivity(historicActivityInstances.get(i).getActivityId());// 獲得節點定義的詳細信息
			List<ActivityImpl> sameStartTimeNodes = new ArrayList<>();// 用以保存後需開始時間相同的節點
			ActivityImpl sameActivityImpl1 = processDefinitionEntity
					.findActivity(historicActivityInstances.get(i + 1).getActivityId());
			// 將後面第一個節點放在時間相同節點的集合裏
			sameStartTimeNodes.add(sameActivityImpl1);
			for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
				HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 後續第一個節點
				HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 後續第二個節點
				if (Math.abs(activityImpl1.getStartTime().getTime()-activityImpl2.getStartTime().getTime()) < 200) {
//                    if (activityImpl1.getStartTime().equals(activityImpl2.getStartTime())) {
					// 若是第一個節點和第二個節點開始時間相同保存
					ActivityImpl sameActivityImpl2 = processDefinitionEntity
							.findActivity(activityImpl2.getActivityId());
					sameStartTimeNodes.add(sameActivityImpl2);
				} else {
					// 有不相同跳出循環
					break;
				}
			}
			List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions();// 取出節點的全部出去的線
			for (PvmTransition pvmTransition : pvmTransitions) {
				// 對全部的線進行遍歷
				ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition.getDestination();
				// 若是取出的線的目標節點存在時間相同的節點裏,保存該線的id,進行高亮顯示
				if (sameStartTimeNodes.contains(pvmActivityImpl)) {
					highFlows.add(pvmTransition.getId());
				}
			}
		}
		return highFlows;
	}

附20190319日修改內容: 鑑於有小夥伴向我反映並行網關的高亮顯示有問題,在他的熱心幫助下,我修改了部分代碼,以支持並行網關,在此感謝! 感謝你們的關注!

相關文章
相關標籤/搜索