public void drawSequenceFlow(
      BufferedImage image, String processDefinitionId, String sequenceFlowId) {
    GetBpmnModelCmd getBpmnModelCmd = new GetBpmnModelCmd(processDefinitionId);
    BpmnModel bpmnModel = getBpmnModelCmd.execute(Context.getCommandContext());

    Graphics2D graphics = image.createGraphics();
    graphics.setPaint(HISTORY_COLOR);
    graphics.setStroke(new BasicStroke(2f));

    try {
      List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlowId);

      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() - minX;
          yPoints[0] = (int) previousGraphicInfo.getY() - minY;
        }

        xPoints[i] = (int) graphicInfo.getX() - minX;
        yPoints[i] = (int) graphicInfo.getY() - minY;
      }

      int radius = 15;

      Path2D path = new Path2D.Double();

      for (int i = 0; i < xPoints.length; i++) {
        Integer anchorX = xPoints[i];
        Integer anchorY = yPoints[i];

        double targetX = anchorX;
        double targetY = anchorY;

        double ax = 0;
        double ay = 0;
        double bx = 0;
        double by = 0;
        double zx = 0;
        double zy = 0;

        if ((i > 0) && (i < (xPoints.length - 1))) {
          Integer cx = anchorX;
          Integer cy = anchorY;

          // pivot point of prev line
          double lineLengthY = yPoints[i] - yPoints[i - 1];

          // pivot point of prev line
          double lineLengthX = xPoints[i] - xPoints[i - 1];
          double lineLength = Math.sqrt(Math.pow(lineLengthY, 2) + Math.pow(lineLengthX, 2));
          double dx = (lineLengthX * radius) / lineLength;
          double dy = (lineLengthY * radius) / lineLength;
          targetX = targetX - dx;
          targetY = targetY - dy;

          // isDefaultConditionAvailable = isDefault && i == 1 && lineLength > 10;
          if ((lineLength < (2 * radius)) && (i > 1)) {
            targetX = xPoints[i] - (lineLengthX / 2);
            targetY = yPoints[i] - (lineLengthY / 2);
          }

          // pivot point of next line
          lineLengthY = yPoints[i + 1] - yPoints[i];
          lineLengthX = xPoints[i + 1] - xPoints[i];
          lineLength = Math.sqrt(Math.pow(lineLengthY, 2) + Math.pow(lineLengthX, 2));

          if (lineLength < radius) {
            lineLength = radius;
          }

          dx = (lineLengthX * radius) / lineLength;
          dy = (lineLengthY * radius) / lineLength;

          double nextSrcX = xPoints[i] + dx;
          double nextSrcY = yPoints[i] + dy;

          if ((lineLength < (2 * radius)) && (i < (xPoints.length - 2))) {
            nextSrcX = xPoints[i] + (lineLengthX / 2);
            nextSrcY = yPoints[i] + (lineLengthY / 2);
          }

          double dx0 = (cx - targetX) / 3;
          double dy0 = (cy - targetY) / 3;
          ax = cx - dx0;
          ay = cy - dy0;

          double dx1 = (cx - nextSrcX) / 3;
          double dy1 = (cy - nextSrcY) / 3;
          bx = cx - dx1;
          by = cy - dy1;

          zx = nextSrcX;
          zy = nextSrcY;
        }

        if (i == 0) {
          path.moveTo(targetX, targetY);
        } else {
          path.lineTo(targetX, targetY);
        }

        if ((i > 0) && (i < (xPoints.length - 1))) {
          // add curve
          path.curveTo(ax, ay, bx, by, zx, zy);
        }
      }

      graphics.draw(path);

      // draw arrow
      Line2D.Double line =
          new Line2D.Double(
              xPoints[xPoints.length - 2],
              yPoints[xPoints.length - 2],
              xPoints[xPoints.length - 1],
              yPoints[xPoints.length - 1]);

      int ARROW_WIDTH = 5;
      int doubleArrowWidth = 2 * ARROW_WIDTH;
      Polygon arrowHead = new Polygon();
      arrowHead.addPoint(0, 0);
      arrowHead.addPoint(-ARROW_WIDTH, -doubleArrowWidth);
      arrowHead.addPoint(ARROW_WIDTH, -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 = graphics.getTransform();
      graphics.setTransform(transformation);
      graphics.fill(arrowHead);
      graphics.setTransform(originalTransformation);
    } finally {
      graphics.dispose();
    }
  }
  public InputStream generateDiagram(String processInstanceId) throws IOException {
    HistoricProcessInstance historicProcessInstance =
        Context.getCommandContext()
            .getHistoricProcessInstanceEntityManager()
            .findHistoricProcessInstance(processInstanceId);
    String processDefinitionId = historicProcessInstance.getProcessDefinitionId();
    GetBpmnModelCmd getBpmnModelCmd = new GetBpmnModelCmd(processDefinitionId);
    BpmnModel bpmnModel = getBpmnModelCmd.execute(Context.getCommandContext());
    Point point = getMinXAndMinY(bpmnModel);
    this.minX = point.x;
    this.minY = point.y;
    this.minX = (this.minX <= 5) ? 5 : this.minX;
    this.minY = (this.minY <= 5) ? 5 : this.minY;
    this.minX -= 5;
    this.minY -= 5;

    ProcessDefinitionEntity definition =
        new GetDeploymentProcessDefinitionCmd(processDefinitionId)
            .execute(Context.getCommandContext());
    String diagramResourceName = definition.getDiagramResourceName();
    String deploymentId = definition.getDeploymentId();
    byte[] bytes =
        Context.getCommandContext()
            .getResourceEntityManager()
            .findResourceByDeploymentIdAndResourceName(deploymentId, diagramResourceName)
            .getBytes();
    InputStream originDiagram = new ByteArrayInputStream(bytes);
    BufferedImage image = ImageIO.read(originDiagram);

    HistoricActivityInstanceQueryImpl historicActivityInstanceQueryImpl =
        new HistoricActivityInstanceQueryImpl();
    historicActivityInstanceQueryImpl
        .processInstanceId(processInstanceId)
        .orderByHistoricActivityInstanceStartTime()
        .asc();

    Page page = new Page(0, 100);
    List<HistoricActivityInstance> activityInstances =
        Context.getCommandContext()
            .getHistoricActivityInstanceEntityManager()
            .findHistoricActivityInstancesByQueryCriteria(historicActivityInstanceQueryImpl, page);

    this.drawHistoryFlow(image, processInstanceId);

    for (HistoricActivityInstance historicActivityInstance : activityInstances) {
      String historicActivityId = historicActivityInstance.getActivityId();
      ActivityImpl activity = definition.findActivity(historicActivityId);

      if (activity != null) {
        if (historicActivityInstance.getEndTime() == null) {
          // 节点正在运行中
          signRunningNode(
              image,
              activity.getX() - this.minX,
              activity.getY() - this.minY,
              activity.getWidth(),
              activity.getHeight(),
              historicActivityInstance.getActivityType());
        } else {
          String deleteReason = null;

          if (historicActivityInstance.getTaskId() != null) {
            deleteReason =
                Context.getCommandContext()
                    .getHistoricTaskInstanceEntityManager()
                    .findHistoricTaskInstanceById(historicActivityInstance.getTaskId())
                    .getDeleteReason();
          }

          // 节点已经结束
          if ("跳过".equals(deleteReason)) {
            signSkipNode(
                image,
                activity.getX() - this.minX,
                activity.getY() - this.minY,
                activity.getWidth(),
                activity.getHeight(),
                historicActivityInstance.getActivityType());
          } else {
            signHistoryNode(
                image,
                activity.getX() - this.minX,
                activity.getY() - this.minY,
                activity.getWidth(),
                activity.getHeight(),
                historicActivityInstance.getActivityType());
          }
        }
      }
    }

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    String formatName = getDiagramExtension(diagramResourceName);
    ImageIO.write(image, formatName, out);

    return new ByteArrayInputStream(out.toByteArray());
  }