端到端功能 连线相关代码提交

This commit is contained in:
qinoy 2023-05-19 17:45:13 +08:00
parent a46cba6ceb
commit 075507553c
5 changed files with 297 additions and 24 deletions

View File

@ -0,0 +1,13 @@
package com.actionsoft.apps.coe.method.process.subprocess.constant;
public class LinkerDefConstant {
// 构造连线时的几个固定参数
public static final double ANGLE_RIGHT = 0;
public static final double ANGLE_DOWN = 1.5707963267948968;
public static final double ANGLE_LEFT = 3.141592653589793;
public static final double ANGLE_UP = 4.71238898038469;
public static final String linker = "{\"fontStyle\":{\"fontFamily\":\"Arial\",\"size\":13,\"color\":\"50,50,50\",\"underline\":false,\"textAlign\":\"center\",\"bold\":false,\"italic\":false},\"points\":[],\"dataAttributes\":[{\"shapeDesc\":\"\",\"name\":\"AWSProperties\",\"id\":\"AWSPropertiesID\",\"type\":\"string\",\"category\":\"default\",\"value\":\"\"}],\"props\":{\"zindex\":0},\"linkerType\":\"broken\",\"lineStyle\":{\"lineStyle\":\"solid\",\"lineColor\":\"50,50,50\",\"beginArrowStyle\":\"none\",\"endArrowStyle\":\"solidArrow\",\"lineWidth\":1},\"name\":\"linker\",\"orderIndex\":0,\"from\":{\"x\":0,\"y\":0,\"angle\":0,\"id\":\"\"},\"id\":\"\",\"text\":\"\",\"to\":{\"x\":0,\"y\":0,\"angle\":0,\"id\":\"\"},\"locked\":false,\"group\":\"\"}";
}

View File

@ -20,4 +20,14 @@ public interface SubProcessConst {
// 后置流程属性key
String REAR_PROCESS_ATTR_ID = "rear_process";
// 子流程图形宽度
double SUB_PROCESS_SHAPE_W = 100.0;
// 子流程图形高度
double SUB_PROCESS_SHAPE_H = 70.0;
// 图形间垂直间隔
double SHAPE_VERT_INTERVAL = 80.0;
// 图形间水平间隔
double SHAPE_HORIZ_INTERVAL = 80.0;
}

View File

@ -1,5 +1,6 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import com.actionsoft.apps.coe.method.process.subprocess.constant.SubProcessConst;
import com.actionsoft.apps.coe.method.process.subprocess.mode.Node;
import java.util.*;
@ -29,11 +30,11 @@ public class GraphLayout {
this.nodeList = nodeList;
this.isPosition = new boolean[nodeList.size()];
this.vertInterval = 50.0;
this.horizInterval = 80.0;
this.vertInterval = SubProcessConst.SHAPE_VERT_INTERVAL;
this.horizInterval = SubProcessConst.SHAPE_HORIZ_INTERVAL;
this.shapeW = 100.0;
this.shapeH = 70.0;
this.shapeW = SubProcessConst.SUB_PROCESS_SHAPE_W;
this.shapeH = SubProcessConst.SUB_PROCESS_SHAPE_H;
this.position = new double[nodeList.size()][2];
}

View File

@ -1,38 +1,287 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import com.actionsoft.apps.coe.method.process.subprocess.constant.LinkerDefConstant;
import com.actionsoft.apps.coe.method.process.subprocess.constant.SubProcessConst;
import com.actionsoft.apps.coe.method.process.subprocess.mode.Node;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.List;
/**
* 图形节点连线渲染
* 图形节点连线渲染的类
*
* 假设每一个节点以自己为中心 类比直角坐标系
* 那么与其相连的其它节点位置大体分布在
* 垂直方向的y轴水平方向的x轴第一象限第二象限第三象限第四象限
*
* 以此为依据 连线大体分为两类 无明显折点有明显折点
* 其中有明显折点又细分出4种仅限本次需求
*/
public class GraphLinkerRender {
private String linkerId;
private Node fromNode;
private Node toNode;
private double[] fromPoi;
private double[] toPoi;
private double[][] vertexPosition; // 所有节点的坐标
private String linkerDefine;
private final List<Node> nodeList; // 节点集合
private final double[][] vertexPosition; // 所有节点的坐标
private final GraphAdjMatrix graphAdjMatrix;
public GraphLinkerRender(double[][] vertexPosition) {
public GraphLinkerRender(List<Node> nodeList, double[][] vertexPosition, GraphAdjMatrix graphAdjMatrix) {
this.nodeList = nodeList;
this.vertexPosition = vertexPosition;
this.graphAdjMatrix = graphAdjMatrix;
}
/**
* 渲染连线
* @param linkerId
* @return
*/
public JSONArray renderLinker(String linkerId){
JSONArray linkers = new JSONArray();
for (int i = 0; i < vertexPosition.length; i++) {
double[] fromPoi = vertexPosition[i];
List<Integer> nextNodeIndex = graphAdjMatrix.getNeighbors(i);
if (nextNodeIndex.size() > 0){ // 说明当前节点有连线
for (Integer nodeIndex : nextNodeIndex) {
double[] toPoi = vertexPosition[nodeIndex];
double[][] turnPoi = calculationStartAndEndAndTurningPoint(fromPoi, toPoi);
double[] angleArr = calculationLinkerAngle(fromPoi, toPoi, turnPoi[1], turnPoi[turnPoi.length - 2]);
// 构建连线
JSONObject linkerObj = JSONObject.parseObject(LinkerDefConstant.linker);
linkerObj.put("id", linkerId);
// 折点
JSONArray points = new JSONArray();
for (double[] point : turnPoi) {
JSONObject pointObj = new JSONObject();
pointObj.put("x", point[0]);
pointObj.put("y", point[1]);
points.add(pointObj);
}
linkerObj.put("points", points);
// 起点与终点
JSONObject fromObj = new JSONObject();
fromObj.put("x", turnPoi[0][0]);
fromObj.put("y", turnPoi[0][1]);
fromObj.put("angle", angleArr[0]);
fromObj.put("id", nodeList.get(i).getId());
JSONObject toObj = new JSONObject();
toObj.put("x", turnPoi[turnPoi.length - 1][0]);
toObj.put("y", turnPoi[turnPoi.length - 1][1]);
toObj.put("angle", angleArr[1]);
toObj.put("id", nodeList.get(nodeIndex.intValue()).getId());
linkers.add(linkerObj);
}
}
}
return linkers;
}
/**
* 计算angle值
* @param fromPoi 起始节点坐标
* @param toPoi 结束节点坐标
* @param firstTurnPoi 连线第一个折点坐标
* @param lastTurnPoi 连线最后一个折点坐标
* @return [0]: from的angle [1]: to的angle
*/
private double[] calculationLinkerAngle(double[] fromPoi, double[] toPoi, double[] firstTurnPoi, double[] lastTurnPoi){
double fromX = fromPoi[0], fromY = fromPoi[1];
double toX = toPoi[0], toY = toPoi[1];
double firstTurnX = firstTurnPoi[0], firstTurnY = firstTurnPoi[1];
double lastTurnX = lastTurnPoi[0], lastTurnY = lastTurnPoi[1];
if (fromY == toY){ // 水平
return fromX < toX ? new double[]{LinkerDefConstant.ANGLE_LEFT, LinkerDefConstant.ANGLE_RIGHT} : new double[]{LinkerDefConstant.ANGLE_RIGHT, LinkerDefConstant.ANGLE_LEFT};
}else if (fromX == toX){ // 垂直
return fromY < toY ? new double[]{LinkerDefConstant.ANGLE_UP, LinkerDefConstant.ANGLE_DOWN} : new double[]{LinkerDefConstant.ANGLE_DOWN, LinkerDefConstant.ANGLE_UP};
}else {
double fromAngle = 0.0;
if (fromY == firstTurnY){ // 水平
fromAngle = fromX < firstTurnX ? LinkerDefConstant.ANGLE_LEFT : LinkerDefConstant.ANGLE_RIGHT;
}else if (fromX == firstTurnX){ // 垂直
fromAngle = fromY < firstTurnY ? LinkerDefConstant.ANGLE_UP : LinkerDefConstant.ANGLE_DOWN;
}
double toAngle = 0.0;
if (toY == lastTurnY){ // 水平
toAngle = toX < lastTurnX ? LinkerDefConstant.ANGLE_LEFT : LinkerDefConstant.ANGLE_RIGHT;
}else if (toX == lastTurnX){ // 垂直
toAngle = toY < lastTurnY ? LinkerDefConstant.ANGLE_UP : LinkerDefConstant.ANGLE_DOWN;
}
return new double[]{fromAngle, toAngle};
}
}
/**
* 计算两个坐标之间的折点
* @param fromPoi
* @param toPoi
* @return
* @param fromPoi 起始图形节点坐标(左上角)
* @param toPoi 终点图形节点坐标(左上角)
* @return [0]: 连线起始点坐标[1]: 连线第一个折点[2]: 连线第二个折点[3]: 连线第三个折点如果存在[n]: 最后一个为连线终点 中间都为折点
*/
private double[][] calculationTurningPoint(double[] fromPoi, double[] toPoi){
double fromX = fromPoi[0];
double fromY = fromPoi[1];
double toX = toPoi[0];
double toY = toPoi[1];
private double[][] calculationStartAndEndAndTurningPoint(double[] fromPoi, double[] toPoi){
double fromX = fromPoi[0],fromY = fromPoi[1],toX = toPoi[0],toY = toPoi[1];
if (fromY == toY) { // 水平
double[] startPoint = (fromX < toX)
? new double[]{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2}
: new double[]{fromX, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
double turnPointX = (fromX < toX)
? fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + (toX - (fromX + SubProcessConst.SUB_PROCESS_SHAPE_W)) / 2
: toX + SubProcessConst.SUB_PROCESS_SHAPE_W + (fromX - (toX + SubProcessConst.SUB_PROCESS_SHAPE_W)) / 2;
double[] endPoint = (fromX < toX)
? new double[]{toX, toY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2}
: new double[]{toX + SubProcessConst.SUB_PROCESS_SHAPE_W, toY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
return new double[][]{startPoint, {turnPointX, toY + (SubProcessConst.SUB_PROCESS_SHAPE_H / 2)},{turnPointX, toY + (SubProcessConst.SUB_PROCESS_SHAPE_H)}, endPoint};
}else if (fromX == toX) { // 垂直
double[] startPoint = (fromY < toY)
? new double[]{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H}
: new double[]{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, fromY};
double turnPointY = (fromY < toY)
? fromY + SubProcessConst.SUB_PROCESS_SHAPE_H + (toY - (fromY + SubProcessConst.SUB_PROCESS_SHAPE_H)) / 2
: toY + SubProcessConst.SUB_PROCESS_SHAPE_H + (fromY - (toY + SubProcessConst.SUB_PROCESS_SHAPE_H)) / 2;
double[] endPoint = (fromY < toY)
? new double[]{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY}
: new double[]{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
return new double[][]{startPoint, {fromX + (SubProcessConst.SUB_PROCESS_SHAPE_W / 2), turnPointY},{fromX + (SubProcessConst.SUB_PROCESS_SHAPE_W / 2), turnPointY}, endPoint};
}else {
if (fromX < toX && fromY > toY){ // 目标节点在第一象限
double[] startPoint = new double[]{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
double turnPointX = fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL / 2;
if (fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL == toX){ // 相邻节点 存在两个折点
double[] endPoint = new double[]{toX, toY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
return new double[][]{startPoint,{turnPointX, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2},{turnPointX, toY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2}, endPoint};
}else { // 不相邻节点 存在三个折点
double[] endPoint = new double[]{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY};
return new double[][]{
startPoint,
{turnPointX, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2},
{turnPointX, toY - SubProcessConst.SHAPE_VERT_INTERVAL / 2},
{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY - SubProcessConst.SHAPE_VERT_INTERVAL / 2},
endPoint
};
}
}else if (fromX > toX && fromY > toY){ // 目标节点在第二象限 无论节点是否相邻 都按照三个折点走
double[] startPoint = new double[]{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
double[] endPoint = new double[]{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY};
return new double[][]{
startPoint,
{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL / 2, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2},
{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL / 2, toY - SubProcessConst.SHAPE_VERT_INTERVAL / 2},
{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY - SubProcessConst.SHAPE_VERT_INTERVAL / 2},
endPoint
};
}else if (fromX > toX && fromY < toY){ // 目标节点在第三象限
double[] startPoint = new double[]{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H};
double[] endPoint = new double[]{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY};
return new double[][]{
startPoint,
{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY - SubProcessConst.SHAPE_VERT_INTERVAL / 2},
{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY - SubProcessConst.SHAPE_VERT_INTERVAL / 2},
endPoint
};
}else if (fromX < toX && fromY < toY){ // 目标节点在第四象限
double[] startPoint = new double[]{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
if (fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL == toX){ // 相邻节点 存在两个折点
double turnPointX = fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL / 2;
double[] endPoint = new double[]{toX, toY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2};
return new double[][]{
startPoint,
{turnPointX, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2},
{turnPointX, toY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2},
endPoint
};
}else { // 不相邻节点 存在三个折点
double[] endPoint = new double[]{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY + SubProcessConst.SUB_PROCESS_SHAPE_H};
return new double[][]{
startPoint,
{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL / 2, fromY + SubProcessConst.SUB_PROCESS_SHAPE_H / 2},
{fromX + SubProcessConst.SUB_PROCESS_SHAPE_W + SubProcessConst.SHAPE_HORIZ_INTERVAL / 2, toY + SubProcessConst.SHAPE_VERT_INTERVAL / 2},
{toX + SubProcessConst.SUB_PROCESS_SHAPE_W / 2, toY + SubProcessConst.SUB_PROCESS_SHAPE_H + SubProcessConst.SHAPE_VERT_INTERVAL / 2},
endPoint
};
}
}
}
return new double[2][2];
}
/**
* 判断连线是否与矩形节点相交
*
* @param startX 连线起点横坐标
* @param startY 连线起点纵坐标
* @param endX 连线终点横坐标
* @param endY 连线终点纵坐标
* @param rectX1 矩形左上角横坐标
* @param rectY1 矩形左上角纵坐标
* @param rectX2 矩形右下角横坐标
* @param rectY2 矩形右下角纵坐标
* @return true表示相交/false表示不相交
*/
public boolean isLineIntersectRect(double startX, double startY, double endX, double endY, double rectX1, double rectY1, double rectX2, double rectY2) {
// 计算连线的斜率和截距
double k = (endY - startY) / (endX - startX);
double b = startY - k * startX;
// 判断连线是否在矩形外部
if (endX < rectX1 || startX > rectX2 || endY < rectY1 || startY > rectY2) {
return false;
}
// 判断连线是否从矩形内部穿过
double[] xVals = new double[] { startX, endX };
double[] yVals = new double[] { startY, endY };
for (int i = 0; i < 2; i++) {
double x = xVals[i], y = yVals[i];
// 判断交点是否在矩形有效范围内
if (x >= rectX1 && x <= rectX2 && y >= rectY1 && y <= rectY2) {
return true;
}
}
// 计算连线与矩形边界的交点
double[] crossPoints = new double[4];
crossPoints[0] = getXOnRectTop(k, b, rectY1); // 计算连线与矩形上边界的交点横坐标
crossPoints[1] = getXOnRectBottom(k, b, rectY2); // 计算连线与矩形下边界的交点横坐标
crossPoints[2] = getYOnRectLeft(k, b, rectX1); // 计算连线与矩形左边界的交点纵坐标
crossPoints[3] = getYOnRectRight(k, b, rectX2); // 计算连线与矩形右边界的交点纵坐标
// 判断交点是否在连线的起止点之间
for (double crossPoint : crossPoints) {
if (crossPoint >= startX && crossPoint <= endX) {
return true;
}
}
return false;
}
/**
* 计算连线与矩形上边界的交点横坐标
*/
private double getXOnRectTop(double k, double b, double y) {
return (y - b) / k;
}
/**
* 计算连线与矩形下边界的交点横坐标
*/
private double getXOnRectBottom(double k, double b, double y) {
return (y - b) / k;
}
/**
* 计算连线与矩形左边界的交点纵坐标
*/
private double getYOnRectLeft(double k, double b, double x) {
return k * x + b;
}
/**
计算连线与矩形右边界的交点纵坐标
*/
private double getYOnRectRight(double k, double b, double x) {
return k * x + b;
}
}