端到端功能 节点展开相关代码提交

This commit is contained in:
yhyd 2023-05-26 17:37:57 +08:00
parent f087279d5e
commit b6c23fdcfe
9 changed files with 203 additions and 335 deletions

View File

@ -1,131 +0,0 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import java.util.Random;
/**
* 图模型节点布局
* 采用力导向算法
* @author oYang
* @create 2023-05-12 14:58
*/
public class ForceDirectedGraphLayout {
private final int n; // 节点数
private final int[][] adjacency; // 邻接矩阵
private final double[][] position; // 节点位置
private final double[] charge; // 节点电荷
private final double[] forceX; // 节点受到的x方向力
private final double[] forceY; // 节点受到的y方向力
private final double[] displacementX; // 节点位移的x方向补偿
private final double[] displacementY; // 节点位移的y方向补偿
private double temperature = 100.0; // 温度
private double temperatureDecay = 0.1; // 温度衰减率
private final double maxDisplacement = 180.0; // 最大位移量
private final Random random = new Random();
private final double width; // 画布宽度
private final double height; // 画布高度
private final double shapeInterval = 80.0; // 图形节点在画布上的间隔
private final double nodeW = 100.0; // 图形节点默认宽度
private final double nodeH = 70.0; // 图形节点默认高度
public ForceDirectedGraphLayout(int[][] adjacency) {
this.n = adjacency.length;
this.adjacency = adjacency;
this.position = new double[n][2];
this.charge = new double[n];
this.forceX = new double[n];
this.forceY = new double[n];
this.displacementX = new double[n];
this.displacementY = new double[n];
this.width = n * (shapeInterval + nodeW);
this.height = n * (shapeInterval + nodeH);
// 初始化节点位置随机分布在一个2000x2000的矩形内
for (int i = 0; i < n; i++) {
position[i][0] = random.nextDouble() * width;
position[i][1] = random.nextDouble() * height;
charge[i] = 1.0; // 电荷为1
}
}
public void run() {
for (int i = 0; i < 1000; i++) { // 迭代1000次
calculateForces();
moveNodes();
cool(); // 降温
}
}
private void calculateForces() {
// 清空所有节点受到的力
for (int i = 0; i < n; i++) {
forceX[i] = 0;
forceY[i] = 0;
}
// 计算节点间的斥力
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j) {
double dx = position[j][0] - position[i][0];
double dy = position[j][1] - position[i][1];
double distance = Math.sqrt(dx * dx + dy * dy);
double repulsiveForce = charge[i] * charge[j] / distance * distance;
forceX[i] -= repulsiveForce * dx / distance;
forceY[i] -= repulsiveForce * dy / distance;
}
}
}
// 计算节点所在边的吸引力
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (adjacency[i][j] != 0) { // 代表节点i和节点j之间存在一条边
double dx = position[j][0] - position[i][0];
double dy = position[j][1] - position[i][1];
double distance = Math.sqrt(dx * dx + dy * dy);
double attractiveForce = distance * distance / charge[i];
forceX[i] += attractiveForce * dx / distance;
forceY[i] += attractiveForce * dy / distance;
}
}
}
}
private void moveNodes() {
for (int i = 0; i < n; i++) {
double displacement = Math.sqrt(forceX[i] * forceX[i] + forceY[i] * forceY[i]);
if (displacement == 0) { // 如果节点受到的力为0就加一点随机扰动防止节点过于静止
displacementX[i] = random.nextDouble() * maxDisplacement * 2 - maxDisplacement;
displacementY[i] = random.nextDouble() * maxDisplacement * 2 - maxDisplacement;
} else {
displacementX[i] = forceX[i] / displacement * Math.min(displacement, maxDisplacement); // 获得x方向的位移补偿
displacementY[i] = forceY[i] / displacement * Math.min(displacement, maxDisplacement); // 获得y方向的位移补偿
}
}
for (int i = 0; i < n; i++) {
position[i][0] += displacementX[i]; // 更新节点的x坐标
position[i][1] += displacementY[i]; // 更新节点的y坐标
}
}
private void cool() {
temperature *= 1 - temperatureDecay; // 温度指数级下降达到类似于退火的贪心优化效果
}
public double[][] getPosition() {
return position;
}
public static void main(String[] args) {
int[][] adjacency = new int[][]{{0, 1, 1, 0}, {0, 0, 1, 1}, {1, 0, 0, 1}, {1, 1, 0, 0}}; // 代表四个节点的有向图
ForceDirectedGraphLayout fdl = new ForceDirectedGraphLayout(adjacency);
fdl.run();
double[][] position = fdl.getPosition();
for (int i = 0; i < position.length; i++) {
System.out.println("Node " + i + ": (" + position[i][0] + ", " + position[i][1] + ")");
}
}
}

View File

@ -17,24 +17,33 @@ import java.util.List;
*/
public class GraphNodeExpandHandle {
private String repositoryId;
private String shapeId;
private String repositoryId; // 总图模型ID
private String shapeId; // 总图中当前要展开的子流程图形ID
private String relationFileId; // 当前要展开的子流程图形所标识模型文件ID
private CoeDesignerAPIManager apiManager;
private String childProcessDefine;
private String childProcessDefine; // 要展开的子流程模型信息
private String endToEndProcessDefine; // 总图的模型信息
public GraphNodeExpandHandle(String repositoryId, String shapeId) {
this.repositoryId = repositoryId;
this.shapeId = shapeId;
apiManager = CoeDesignerAPIManager.getInstance();
readChildProcessDefine();
readCurrentProcessDefine();
}
/**
* 读取子流程节点的存储信息
* @throws AWSException
*/
public void readChildProcessDefine() throws AWSException{
List<DesignerShapeRelationModel> childProcessModelList = DesignerShapeRelationCache.getListByAttrId(repositoryId, shapeId, SubProcessConst.CHILD_PROCESS);
DesignerShapeRelationModel relationModel = childProcessModelList.stream().findFirst().orElse(null);
if (relationModel == null)
throw new AWSException("未找到当前节点所标识的子流程文件信息");
String relationFileId = relationModel.getRelationFileId();
relationFileId = relationModel.getRelationFileId();
childProcessDefine = apiManager.getChildProcessDefine(repositoryId, 0, relationFileId);
if (UtilString.isEmpty(childProcessDefine)){ // 初次展开 去源文件目录读取
BaseModel childProcessBaseModel = apiManager.getDefinition(relationFileId, 0);
@ -43,10 +52,22 @@ public class GraphNodeExpandHandle {
}
/**
* 组装范围限制框
* @return 范围限制框
* 读取当前总图的存储信息
* @throws AWSException
*/
public JSONObject toAssembleScopeLimitationShape(){
public void readCurrentProcessDefine() throws AWSException{
BaseModel baseModel = apiManager.getDefinition(repositoryId, 0);
if (baseModel == null)
throw new AWSException("未找到当前总图存储的模型信息");
endToEndProcessDefine = baseModel.getDefinition();
}
/**
* 组装范围标注框
* @return 范围标注框
* @throws AWSException
*/
public JSONObject toAssembleScopeLimitationShape() throws AWSException{
JSONObject scopeLimitationShape = ShapeUtil.getProcessShapeDefinition(SubProcessConst.SUB_PROCESS_METHOD_ID, "展开范围标注");
JSONObject childProcessDefineObj = JSONObject.parseObject(childProcessDefine);
JSONObject childProcessPage = childProcessDefineObj.getJSONObject("page");
@ -54,6 +75,51 @@ public class GraphNodeExpandHandle {
double childProcessPageWidth = childProcessPage.getDoubleValue("width") - childProcessPage.getDoubleValue("padding") * 2;
double childProcessPageHeight = childProcessPage.getDoubleValue("height") - childProcessPage.getDoubleValue("padding") * 2;
JSONObject endToEndProcessDefineObj = JSONObject.parseObject(endToEndProcessDefine);
JSONObject elements = endToEndProcessDefineObj.getJSONObject("elements");
// 找到当前要展开的子流程节点
JSONObject currentExpandShape = elements.getJSONObject(shapeId);
if (currentExpandShape == null)
throw new AWSException("未找到当前要展开的子流程节点信息");
JSONObject props = currentExpandShape.getJSONObject("props");
double x = props.getDoubleValue("x");
double y = props.getDoubleValue("y");
// 当前要展开的子流程节点的坐标赋给范围标注框
JSONObject scopeShapeProps = scopeLimitationShape.getJSONObject("props");
scopeShapeProps.put("x", x);
scopeShapeProps.put("y", y);
scopeShapeProps.put("w", childProcessPageWidth);
scopeShapeProps.put("h", childProcessPageHeight);
scopeShapeProps.put("zindex", 1);
return scopeLimitationShape;
}
/**
* 处理子流程中所有节点的坐标
*/
public void handleRelationModelNodePosition(){
// 范围标注框 坐标
JSONObject scopeLimitationShape = toAssembleScopeLimitationShape();
JSONObject scopeShapeProps = scopeLimitationShape.getJSONObject("props");
double scopeShapeX = scopeShapeProps.getDoubleValue("x");
double scopeShapeY = scopeShapeProps.getDoubleValue("y");
// 根据范围标注框的坐标 调整子流程所有元素的坐标
JSONObject childProcessDefineObj = JSONObject.parseObject(childProcessDefine);
JSONObject elements = childProcessDefineObj.getJSONObject("elements");
for (String key : elements.keySet()) {
JSONObject ele = elements.getJSONObject(key);
}
}
/**
* 存储子流程模型信息
* 在总图存储的同一级目录下
*/
public void storeChildProcessDefine(){
BaseModel baseModel = apiManager.getDefinition(relationFileId, 0);
apiManager.storeChildProcessDefine(baseModel, relationFileId, childProcessDefine);
}
}

View File

@ -1,197 +0,0 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import java.util.Arrays;
/**
* 图模型节点布局
* 采用 PageRank 算法
* @author oYang
* @create 2023-05-16 11:10
*/
public class PageRankLayout {
private int[][] matrix; // 邻接矩阵
private double width; // 画布宽度
private double height; // 画布高度
private int maxIter; // 最大迭代次数
private double damp; // 阻尼因子
private int nodeSize; // 节点数
private double[] pageRankVal; // PageRank数据
private final double shapeInterval = 80.0; // 图形节点在画布上的间隔
private final double nodeW = 100.0; // 图形节点默认宽度
private final double nodeH = 70.0; // 图形节点默认高度
public PageRankLayout(int[][] matrix, int maxIter) {
this.matrix = matrix;
this.maxIter = maxIter;
this.damp = 0.85;
this.nodeSize = matrix.length;
this.pageRankVal = new double[nodeSize];
this.width = matrix.length * (shapeInterval + nodeW);
this.height = matrix.length * (shapeInterval + nodeH);
// 初始化每个节点的 PageRank 值为 1/N
Arrays.fill(pageRankVal, 1.0/nodeSize);
}
/**
* 计算每个节点的入度加权平均值
*/
public void calculatePageRankVal(){
for (int iter = 0; iter < maxIter; iter++) {
double[] nextPR = new double[nodeSize];
for (int i = 0; i < nodeSize; i++) {
double sum = 0.0;
for (int j = 0; j < nodeSize; j++) {
if (matrix[j][i] == 1) {
sum += pageRankVal[j] / countOutLinks(j);
}
}
nextPR[i] = (1 - damp) / nodeSize + damp * sum;
}
pageRankVal = nextPR;
}
// 输出结果
for (int i = 0; i < nodeSize; i++) {
System.out.printf("Node %d: PageRank = %.3f\n", i, pageRankVal[i]);
}
}
/**
* 根据节点邻接矩阵计算节点的出度
* @param nodeIndex
* @return
*/
private int countOutLinks(int nodeIndex){
int count = 0;
for (int i = 0; i < matrix[nodeIndex].length; i++) {
if (matrix[nodeIndex][i] == 1) {
count++;
}
}
return count;
}
/**
* 根据节点邻接矩阵计算节点入度
* @param nodeIndex
* @return
*/
private int countInputLinks(int nodeIndex){
int count = 0;
for (int i = 0; i < matrix.length; i++) {
if (matrix[i][nodeIndex] == 1){
count++;
}
}
return count;
}
private void layOut(){
int n = pageRankVal.length;
// 计算 PageRank 值最大的节点将该节点定位在画布的中心
int maxIndex = 0;
double maxPageRank = pageRankVal[0];
for (int i = 1; i < n; i++) {
if (pageRankVal[i] > maxPageRank) {
maxIndex = i;
maxPageRank = pageRankVal[i];
}
}
double centerX = width / 2;
double centerY = height / 2;
// 计算其它节点相对于最大节点的 PageRank 值的比例关系以及节点数量的平方根
double scale = Math.sqrt(n);
double[] distX = new double[n];
double[] distY = new double[n];
for (int i = 0; i < n; i++) {
if (i == maxIndex) {
distX[i] = 0;
distY[i] = 0;
} else {
double ratio = pageRankVal[i] / pageRankVal[maxIndex];
distX[i] = ratio * Math.cos(2 * Math.PI * i / n);
distY[i] = ratio * Math.sin(2 * Math.PI * i / n);
}
}
// 将节点的 PageRank 值乘以比例关系和节点数量的平方根得到节点在横向和竖向上的缩放比例
double[] scaleX = new double[n];
double[] scaleY = new double[n];
for (int i = 0; i < n; i++) {
scaleX[i] = distX[i] * scale;
scaleY[i] = distY[i] * scale;
}
// 将横向和竖向上的缩放比例分别乘以画布的宽度和高度得到节点在画布上的实际坐标
double[] x = new double[n];
double[] y = new double[n];
for (int i = 0; i < n; i++) {
x[i] = centerX + (int)(scaleX[i] * 200);
y[i] = centerY + (int)(scaleY[i] * 200);
}
for (int i = 0; i < nodeSize; i++) {
System.out.printf("NodeIndex %d: Position [%f%n,%f%n]", i, x[i], y[i]);
}
}
/**
* 根据节点 PageRank 值计算节点坐标
*
* @param pageRank 节点 PageRank
* @param canvasWidth 画布宽度
* @param canvasHeight 画布高度
* @return 节点坐标数组每个点都是 (x, y) 形式
*/
public double[][] computeNodeCoordinates() {
int n = pageRankVal.length;
double[][] nodeCoords = new double[n][2];
double maxPageRank = Double.NEGATIVE_INFINITY;
for (double x : pageRankVal) {
maxPageRank = Math.max(maxPageRank, x);
}
double centerX = width / 2.0;
double centerY = height / 2.0;
double radius = Math.min(centerX, centerY) - 50; // 保留一些边距避免节点被切掉
for (int i = 0; i < n; i++) {
double angle = 2 * Math.PI * i / n;
double x = centerX + (pageRankVal[i] / maxPageRank) * radius * Math.cos(angle);
double y = centerY + (pageRankVal[i] / maxPageRank) * radius * Math.sin(angle);
nodeCoords[i][0] = x;
nodeCoords[i][1] = y;
}
return nodeCoords;
}
public static void main(String[] args) {
// 生成邻接矩阵
int[][] matrix = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
PageRankLayout pageRankLayout = new PageRankLayout(matrix, 100);
pageRankLayout.calculatePageRankVal();
double[][] nodeCoords = pageRankLayout.computeNodeCoordinates();
// 输出每个节点在直角坐标系中的坐标
for (int i = 0; i < nodeCoords.length; i++) {
System.out.printf("(%.2f, %.2f)\n", nodeCoords[i][0], nodeCoords[i][1]);
}
}
}

View File

@ -47,6 +47,18 @@ public class CoeDesignerDefaultDao {
return CoeDesignerConstant.ANSWER_SUCESS;
}
/**
* 存储子流程文件信息
* @param model
* @param childProcessId
* @param childProcessDefine
*/
public void storeChildProcess(BaseModel model, String childProcessId, String childProcessDefine){
storer = new CoeDesignerFile(model);
final CoeDesignerFile storerModel = storer;
storerModel.store(childProcessId, childProcessDefine);
}
/**
* 将操作记入日志
*/
@ -148,6 +160,24 @@ public class CoeDesignerDefaultDao {
return model;
}
/**
* 端到端功能
* 获取与总图存放在一起的展开过的子流程模型信息
* @param uuid
* @param ver
* @param childProcessId
* @return
*/
public String get(String uuid, int ver, String childProcessId){
BaseModel baseModel = CoeDesignerUtil.createModel(uuid, ver);
storer = new CoeDesignerFile(baseModel);
String define = storer.readChildProcessDefinition(childProcessId);
if (UtilString.isEmpty(define)){
return "";
}
return define;
}
/**
* @Deprecated
* @param model

View File

@ -58,6 +58,23 @@ public class CoeDesignerFile {
return JSONArray.parseArray(JSON.toJSONString(list));
}
/**
* 保存子流程文件
* @param childProcessId
* @param childProcessDefine
* @throws AWSException
*/
public void store(String childProcessId, String childProcessDefine) throws AWSException{
checkChildProcessFile(childProcessId);
String childProcessPathName = getChildProcessPathName(childProcessId);
UtilFile childProcessDefineFile = new UtilFile(childProcessPathName);
try {
childProcessDefineFile.write(childProcessDefine.getBytes("UTF8"));
} catch (UnsupportedEncodingException e) {
throw new AWSException("子流程文件保存失败");
}
}
/**
* 读取define
*
@ -74,6 +91,22 @@ public class CoeDesignerFile {
return define;
}
/**
* 端到端功能
* 读取与总图存放在一起的展开过的子流程模型 define 信息
* @param childProcessId
* @return
*/
public String readChildProcessDefinition(String childProcessId){
if (!childProcessIsExist(childProcessId)){
return "";
}
String childProcessPath = getChildProcessPathName(childProcessId);
UtilFile utilFile = new UtilFile(childProcessPath);
String define = utilFile.readStrUTF8();
return define;
}
/**
* 读取draw
*
@ -154,6 +187,26 @@ public class CoeDesignerFile {
}
/**
* 子流程文件不存在 创建
* @param childProcessId
* @throws AWSException
*/
protected void checkChildProcessFile(String childProcessId) throws AWSException{
File dir = new File(model.getPath());
if (!dir.exists()) {
dir.mkdirs();
}
UtilFile utilFile = new UtilFile(getChildProcessPathName(childProcessId));
if (!utilFile.exists()){
try {
utilFile.createNewFile();
} catch (IOException e) {
throw new AWSException("子流程文件创建失败");
}
}
}
/**
* 获取文件存放路径名
*
@ -168,6 +221,16 @@ public class CoeDesignerFile {
return model.getPath() + model.getUUID() + _ver + type;
}
/**
* 端到端功能
* 获取与总图存放在一起的展开过的子流程模型信息
* @param childProcessId
* @return
*/
public String getChildProcessPathName(String childProcessId){
return model.getPath() + childProcessId;
}
/**
* 获取文件存放路径名
*
@ -195,6 +258,24 @@ public class CoeDesignerFile {
return true;
}
/**
* 端到端功能
* 判断与总图存放在一起的展开过的子流程模型文件是否存在
* @param childProcessId
* @return
*/
public boolean childProcessIsExist(String childProcessId){
File dir = new File(model.getPath());
if (!dir.exists()){
dir.mkdirs();
}
UtilFile utilFile = new UtilFile(getChildProcessPathName(childProcessId));
if (!utilFile.exists()){
return false;
}
return true;
}
/**
* 存储日志文件
*/

View File

@ -22,6 +22,16 @@ public class CoeDesignerAPIManager {
return new CoeDesignerDefaultDao().store(model);
}
/**
* 存储子流程模型信息
* @param model
* @param childProcessId
* @param childProcessDefine
*/
public void storeChildProcessDefine(BaseModel model, String childProcessId, String childProcessDefine){
new CoeDesignerDefaultDao().storeChildProcess(model, childProcessId, childProcessDefine);
}
public int storeDefinitionOfBpmn(BPMNModel model) {
return new BPMNDao().store(model);
}
@ -37,6 +47,10 @@ public class CoeDesignerAPIManager {
}
public String getChildProcessDefine(String uuid, int ver, String childProcessId){
return new CoeDesignerDefaultDao().get(uuid, ver, childProcessId);
}
public BPMNModel getDefinitionOfBpmn(String uuid, int ver) {
return new BPMNDao().get(uuid, ver);
}

View File

@ -74,6 +74,7 @@
}
</style>
<link rel="stylesheet" href="../commons/css/awsui.css">
<link rel="stylesheet" href="../apps/com.actionsoft.apps.coe.pal/lib/designer/extend/css/subprocess/iconfont.css">
<!-- <script src="../apps/com.actionsoft.apps.coe.pal/lib/designer/scripts/jquery.js"></script> -->
<script type='text/javascript' src='../commons/js/jquery/scripts/jquery.js'></script>
<script type="text/javascript" src="../commons/js/jquery/scripts/jquery-migrate.js"></script>
@ -258,6 +259,7 @@
<!--测试-->
<!--<script type='text/javascript' charset='UTF-8' src='../apps/com.actionsoft.apps.coe.pal/lib/designer/scripts/diagraming/designer.core.debug.js'></script>-->
<script type='text/javascript' charset='UTF-8' src='../apps/com.actionsoft.apps.coe.pal/lib/designer/scripts/diagraming/designer.methods.js'></script>
<script type='text/javascript' charset='UTF-8' src='../apps/com.actionsoft.apps.coe.pal/lib/designer/scripts/diagraming/designer.method.subprocess.js'></script>
<!--测试-->
<!--<script type='text/javascript' charset='UTF-8' src='../apps/com.actionsoft.apps.coe.pal/lib/designer/scripts/diagraming/designer.methods.debug.js'></script>-->
<script type='text/javascript' charset='UTF-8' src='../apps/com.actionsoft.apps.coe.pal/lib/designer/scripts/diagraming/designer.events.js'></script>

View File

@ -1,4 +1,7 @@
$(function(){
if (methodId != 'process.subprocess') { // 如果当前打开的模型不是端到端总图 那么整个js也没有执行的必要
return;
}
// 1. 子流程展开 事件:获取当前子流程所代表的模型文件
(function (Model, ruuid, sid) {
const subProcess = new SubProcess(Model, ruuid, sid);