hookehuyr

优化:根据节点不同类型,连接线颜色调整

1 /* 1 /*
2 * @Date: 2023-10-27 09:29:48 2 * @Date: 2023-10-27 09:29:48
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2023-11-23 17:55:22 4 + * @LastEditTime: 2023-12-01 16:47:11
5 * @FilePath: /vue-flow-editor/doc/data.js 5 * @FilePath: /vue-flow-editor/doc/data.js
6 * @Description: 初始化结构,数据都是固定的 6 * @Description: 初始化结构,数据都是固定的
7 */ 7 */
......
...@@ -131,11 +131,16 @@ export function dragEdge(G6, option: OptionType) { ...@@ -131,11 +131,16 @@ export function dragEdge(G6, option: OptionType) {
131 if (this.origin.targetNode) { 131 if (this.origin.targetNode) {
132 const addModel = { 132 const addModel = {
133 class: 'flow', 133 class: 'flow',
134 + shape: "flow-polyline-round1",
134 source: this.origin.sourceNode.get('id'), 135 source: this.origin.sourceNode.get('id'),
135 target: this.origin.targetNode.get('id'), 136 target: this.origin.targetNode.get('id'),
136 sourceAnchor: this.origin.sourceAnchor, 137 sourceAnchor: this.origin.sourceAnchor,
137 targetAnchor: this.origin.targetAnchor, 138 targetAnchor: this.origin.targetAnchor,
138 } 139 }
140 + // TAG: 修改连接线颜色 开始出去的线和连接到抄送节点的颜色是灰色
141 + if (this.origin.sourceNode.get('id') === 'start-node' || this.origin.targetNode.get('model').control ==='cc') {
142 + addModel.shape = 'flow-polyline-round'
143 + }
139 if (this.graph.executeCommand) { 144 if (this.graph.executeCommand) {
140 this.graph.executeCommand('add', { 145 this.graph.executeCommand('add', {
141 type: 'edge', 146 type: 'edge',
......
...@@ -327,4 +327,4 @@ export function registerEdge(G6) { ...@@ -327,4 +327,4 @@ export function registerEdge(G6) {
327 return polylinePoints; 327 return polylinePoints;
328 }, 328 },
329 }, 'polyline'); 329 }, 'polyline');
330 -}
...\ No newline at end of file ...\ No newline at end of file
330 +}
......
1 +import {GraphStyle} from "@/utils/styles";
2 +
3 +import editorStyle from "../util/defaultStyle";
4 +import {G6} from "@/g6/g6";
5 +
6 +const deepMix = G6.Util.deepMix
7 +
8 +const uniqBy = (arr, key) => {
9 + const result = [];
10 + arr.forEach(i => {
11 + if (!result.find(r => r[key] === i[key]))
12 + result.push(i)
13 + });
14 + return result;
15 +};
16 +
17 +export function registerEdge1(G6) {
18 + G6.registerEdge('flow-polyline-round1', {
19 + options: {
20 + style: {
21 + ...GraphStyle.default.defaultEdge1.style,
22 + },
23 + stateStyles: {
24 + ...GraphStyle.default.edgeStateStyles,
25 + }
26 + },
27 + setState(name, value, item) {
28 + const shape = item.get('keyShape');
29 + if (!shape) {
30 + return;
31 + }
32 + const itemStateStyle = item.getStateStyle(name);
33 + const stateStyle = this.getStateStyle(name, value, item);
34 + const styles = deepMix({}, stateStyle, itemStateStyle);
35 + if (value) { // 如果设置状态,在原本状态上叠加绘图属性
36 + console.warn(styles);
37 +
38 + shape.attr(styles);
39 + } else { // 取消状态时重置所有状态,依次叠加仍有的状态
40 + const style = item.getCurrentStatesStyle();
41 + // 如果默认状态下没有设置attr,在某状态下设置了,需要重置到没有设置的状态
42 + G6.Util.each(styles, (val, attr) => {
43 + if (!style[attr]) {
44 + style[attr] = null;
45 + }
46 + });
47 + shape.attr(style);
48 + }
49 + },
50 + drawShape(cfg, group) {
51 + this.group = group;
52 + const shapeStyle = this.getShapeStyle(cfg);
53 + const shape = group.addShape('path', {
54 + className: 'edge-shape',
55 + attrs: shapeStyle
56 + });
57 + return shape;
58 + },
59 + drawLabel(cfg, group) {
60 + const labelCfg = cfg.labelCfg || {};
61 + const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
62 + const label = group.addShape('text', {
63 + attrs: labelStyle
64 + });
65 + const labelBBox = label.getBBox();
66 + group.addShape('rect', {
67 + className: 'edge-labelRect',
68 + attrs: {
69 + x: labelBBox.x - editorStyle.edgeLabelRectPadding / 2,
70 + y: labelBBox.y - editorStyle.edgeLabelRectPadding / 2,
71 + width: labelBBox.width + editorStyle.edgeLabelRectPadding,
72 + height: labelBBox.height + editorStyle.edgeLabelRectPadding,
73 + fill: '#fff',
74 + stroke: '#fff',
75 + }
76 + });
77 + group.toBack();
78 + label.toFront();
79 + return label;
80 + },
81 + afterUpdate(cfg, item) {
82 + const label = item.getContainer().findByClassName('edge-label');
83 + const labelRect = item.getContainer().findByClassName('edge-labelRect');
84 + if (label) {
85 + const labelBBox = label.getBBox();
86 + labelRect.attr({
87 + x: labelBBox.x - editorStyle.edgeLabelRectPadding / 2,
88 + y: labelBBox.y - editorStyle.edgeLabelRectPadding / 2,
89 + width: labelBBox.width + editorStyle.edgeLabelRectPadding,
90 + height: labelBBox.height + editorStyle.edgeLabelRectPadding,
91 + });
92 + }
93 + },
94 + getShapeStyle(cfg) {
95 + cfg = this.getPathPoints(cfg);
96 + const startPoint = cfg.startPoint;
97 + const endPoint = cfg.endPoint;
98 + const controlPoints = this.getControlPoints(cfg);
99 + let points = [startPoint];
100 + if (controlPoints) {
101 + points = points.concat(controlPoints);
102 + }
103 + points.push(endPoint);
104 + const path = this.getPath(points);
105 + let style = this.options.style;
106 + if (cfg.reverse)
107 + style = {...style, lineDash: [1, 3]};
108 + else
109 + style = {...style, lineDash: null};
110 + return {
111 + path,
112 + ...style,
113 + endArrow: {
114 + path: 'M 0,0 L -10,-4 S -8 0,-10 4 Z',
115 + }
116 + }
117 + },
118 + getPath(points) {
119 + const path = [];
120 + for (let i = 0; i < points.length; i++) {
121 + const point = points[i];
122 + if (i === 0) {
123 + path.push(['M', point.x, point.y]);
124 + } else if (i === points.length - 1) {
125 + path.push(['L', point.x, point.y]);
126 + } else {
127 + const prevPoint = points[i - 1];
128 + let nextPoint = points[i + 1];
129 + let cornerLen = 5;
130 + if (Math.abs(point.y - prevPoint.y) > cornerLen || Math.abs(point.x - prevPoint.x) > cornerLen) {
131 + if (prevPoint.x === point.x) {
132 + path.push(['L', point.x, point.y > prevPoint.y ? point.y - cornerLen : point.y + cornerLen]);
133 + } else if (prevPoint.y === point.y) {
134 + path.push(['L', point.x > prevPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]);
135 + }
136 + }
137 + const yLen = Math.abs(point.y - nextPoint.y);
138 + const xLen = Math.abs(point.x - nextPoint.x);
139 + if (yLen > 0 && yLen < cornerLen) {
140 + cornerLen = yLen;
141 + } else if (xLen > 0 && xLen < cornerLen) {
142 + cornerLen = xLen;
143 + }
144 + if (prevPoint.x !== nextPoint.x && nextPoint.x === point.x) {
145 + path.push(['Q', point.x, point.y, point.x, point.y > nextPoint.y ? point.y - cornerLen : point.y + cornerLen]);
146 + } else if (prevPoint.y !== nextPoint.y && nextPoint.y === point.y) {
147 + path.push(['Q', point.x, point.y, point.x > nextPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]);
148 + }
149 + }
150 + }
151 + return path;
152 + },
153 + getControlPoints(cfg) {
154 + if (!cfg.sourceNode) {
155 + return cfg.controlPoints;
156 + }
157 + return this.polylineFinding(cfg.sourceNode, cfg.targetNode, cfg.startPoint, cfg.endPoint, 15);
158 + },
159 + getExpandedBBox(bbox, offset) {
160 + return 0 === bbox.width && 0 === bbox.height ? bbox : {
161 + centerX: bbox.centerX,
162 + centerY: bbox.centerY,
163 + minX: bbox.minX - offset,
164 + minY: bbox.minY - offset,
165 + maxX: bbox.maxX + offset,
166 + maxY: bbox.maxY + offset,
167 + height: bbox.height + 2 * offset,
168 + width: bbox.width + 2 * offset,
169 + };
170 + },
171 + getExpandedPort(bbox, point) {
172 + return Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height
173 + ? {x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, y: point.y}
174 + : {x: point.x, y: point.y > bbox.centerY ? bbox.maxY : bbox.minY};
175 + },
176 + combineBBoxes(sBBox, tBBox) {
177 + const minX = Math.min(sBBox.minX, tBBox.minX), minY = Math.min(sBBox.minY, tBBox.minY),
178 + maxX = Math.max(sBBox.maxX, tBBox.maxX), maxY = Math.max(sBBox.maxY, tBBox.maxY);
179 + return {
180 + centerX: (minX + maxX) / 2,
181 + centerY: (minY + maxY) / 2,
182 + minX: minX,
183 + minY: minY,
184 + maxX: maxX,
185 + maxY: maxY,
186 + height: maxY - minY,
187 + width: maxX - minX,
188 + };
189 + },
190 + getBBoxFromVertexes(sPoint, tPoint) {
191 + const minX = Math.min(sPoint.x, tPoint.x), maxX = Math.max(sPoint.x, tPoint.x),
192 + minY = Math.min(sPoint.y, tPoint.y), maxY = Math.max(sPoint.y, tPoint.y);
193 + return {
194 + centerX: (minX + maxX) / 2,
195 + centerY: (minY + maxY) / 2,
196 + maxX: maxX,
197 + maxY: maxY,
198 + minX: minX,
199 + minY: minY,
200 + height: maxY - minY,
201 + width: maxX - minX,
202 + };
203 + },
204 + vertexOfBBox(bbox) {
205 + return [{x: bbox.minX, y: bbox.minY}, {x: bbox.maxX, y: bbox.minY}, {x: bbox.maxX, y: bbox.maxY}, {x: bbox.minX, y: bbox.maxY}];
206 + },
207 + crossPointsByLineAndBBox(bbox, centerPoint) {
208 + let crossPoints = [];
209 + if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX))
210 + crossPoints = crossPoints.concat([{x: centerPoint.x, y: bbox.minY}, {x: centerPoint.x, y: bbox.maxY}]);
211 + if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY))
212 + crossPoints = crossPoints.concat([{x: bbox.minX, y: centerPoint.y}, {x: bbox.maxX, y: centerPoint.y}]);
213 + return crossPoints;
214 + },
215 + getConnectablePoints(sBBox, tBBox, sPoint, tPoint) {
216 + const lineBBox = this.getBBoxFromVertexes(sPoint, tPoint);
217 + const outerBBox = this.combineBBoxes(sBBox, tBBox);
218 + const sLineBBox = this.combineBBoxes(sBBox, lineBBox);
219 + const tLineBBox = this.combineBBoxes(tBBox, lineBBox);
220 + let points = [];
221 + points = points.concat(this.vertexOfBBox(sLineBBox), this.vertexOfBBox(tLineBBox), this.vertexOfBBox(outerBBox));
222 + const centerPoint = {x: outerBBox.centerX, y: outerBBox.centerY};
223 + [outerBBox, sLineBBox, tLineBBox, lineBBox].forEach(bbox => {
224 + points = points.concat(this.crossPointsByLineAndBBox(bbox, centerPoint))
225 + });
226 + points.push({x: sPoint.x, y: tPoint.y});
227 + points.push({x: tPoint.x, y: sPoint.y});
228 + return points
229 + },
230 + filterConnectablePoints(points, bbox) {
231 + return points.filter(point => point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY)
232 + },
233 + AStar(points, sPoint, tPoint, sBBox, tBBox) {
234 + const openList = [sPoint];
235 + const closeList = [];
236 + points = uniqBy(this.fillId(points), 'id');
237 + points.push(tPoint);
238 + let endPoint;
239 + while (openList.length > 0) {
240 + let minCostPoint;
241 + openList.forEach((p, i) => {
242 + if (!p.parent)
243 + p.f = 0;
244 + if (!minCostPoint)
245 + minCostPoint = p;
246 + if (p.f < minCostPoint.f)
247 + minCostPoint = p;
248 + });
249 + if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) {
250 + endPoint = minCostPoint;
251 + break;
252 + }
253 + openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1);
254 + closeList.push(minCostPoint);
255 + const neighbor = points.filter(p => (p.x === minCostPoint.x || p.y === minCostPoint.y)
256 + && !(p.x === minCostPoint.x && p.y === minCostPoint.y)
257 + && !this.crossBBox([sBBox, tBBox], minCostPoint, p));
258 + neighbor.forEach(p => {
259 + const inOpen = openList.find(o => o.x === p.x && o.y === p.y);
260 + const currentG = this.getCost(p, minCostPoint);
261 + if (closeList.find(o => o.x === p.x && o.y === p.y)) {
262 +
263 + } else if (inOpen) {
264 + if (p.g > currentG) {
265 + p.parent = minCostPoint;
266 + p.g = currentG;
267 + p.f = p.g + p.h;
268 + }
269 + } else {
270 + p.parent = minCostPoint;
271 + p.g = currentG;
272 + let h = this.getCost(p, tPoint);
273 + if (this.crossBBox([tBBox], p, tPoint)) {
274 + h += (tBBox.width / 2 + tBBox.height / 2); //如果穿过bbox则增加该点的预估代价为bbox周长的一半
275 + }
276 + p.h = h;
277 + p.f = p.g + p.h;
278 + openList.push(p)
279 + }
280 + });
281 + }
282 + if (endPoint) {
283 + const result = [];
284 + result.push({x: endPoint.x, y: endPoint.y});
285 + while (endPoint.parent) {
286 + endPoint = endPoint.parent;
287 + result.push({x: endPoint.x, y: endPoint.y});
288 + }
289 + return result.reverse();
290 + }
291 + return [];
292 + },
293 + crossBBox(bboxes, p1, p2) {
294 + for (let i = 0; i < bboxes.length; i++) {
295 + const bbox = bboxes[i];
296 + if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) {
297 + if (p1.y < bbox.maxY && p2.y >= bbox.maxY || p2.y < bbox.maxY && p1.y >= bbox.maxY)
298 + return true
299 + } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) {
300 + if (p1.x < bbox.maxX && p2.x >= bbox.maxX || p2.x < bbox.maxX && p1.x >= bbox.maxX)
301 + return true
302 + }
303 + }
304 + return false;
305 + },
306 + getCost(p1, p2) {
307 + return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
308 + },
309 + getPointBBox(t) {
310 + return {centerX: t.x, centerY: t.y, minX: t.x, minY: t.y, maxX: t.x, maxY: t.y, height: 0, width: 0};
311 + },
312 + fillId(points) {
313 + points.forEach(p => {
314 + p.id = p.x + '-' + p.y;
315 + });
316 + return points;
317 + },
318 + polylineFinding(sNode, tNode, sPort, tPort, offset) {
319 + const sourceBBox = sNode && sNode.getBBox() ? sNode.getBBox() : this.getPointBBox(sPort);
320 + const targetBBox = tNode && tNode.getBBox() ? tNode.getBBox() : this.getPointBBox(tPort);
321 + const sBBox = this.getExpandedBBox(sourceBBox, offset);
322 + const tBBox = this.getExpandedBBox(targetBBox, offset);
323 + const sPoint = this.getExpandedPort(sBBox, sPort);
324 + const tPoint = this.getExpandedPort(tBBox, tPort);
325 + let points = this.getConnectablePoints(sBBox, tBBox, sPoint, tPoint);
326 + points = this.filterConnectablePoints(points, sBBox);
327 + points = this.filterConnectablePoints(points, tBBox);
328 + const polylinePoints = this.AStar(points, sPoint, tPoint, sBBox, tBBox);
329 + return polylinePoints;
330 + },
331 + }, 'polyline');
332 +}
1 /* 1 /*
2 * @Date: 2023-10-27 09:29:59 2 * @Date: 2023-10-27 09:29:59
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2023-11-14 11:17:20 4 + * @LastEditTime: 2023-12-01 17:11:30
5 * @FilePath: /vue-flow-editor/src/shape/index.ts 5 * @FilePath: /vue-flow-editor/src/shape/index.ts
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 import {registerAnchor} from "@/shape/anchor"; 8 import {registerAnchor} from "@/shape/anchor";
9 import {registerEdge} from "@/shape/edge"; 9 import {registerEdge} from "@/shape/edge";
10 +import {registerEdge1} from "@/shape/edge1"; // TAG:新增一条彩色的连接线
10 // TAG: 自定义节点 - 引入节点 11 // TAG: 自定义节点 - 引入节点
11 import {registerActivity} from "@/shape/activity"; 12 import {registerActivity} from "@/shape/activity";
12 import {registerControl} from "@/shape/control"; 13 import {registerControl} from "@/shape/control";
...@@ -14,6 +15,7 @@ import {registerControl} from "@/shape/control"; ...@@ -14,6 +15,7 @@ import {registerControl} from "@/shape/control";
14 export function registerShape(G6) { 15 export function registerShape(G6) {
15 registerAnchor(G6) 16 registerAnchor(G6)
16 registerEdge(G6) 17 registerEdge(G6)
18 + registerEdge1(G6)
17 registerActivity(G6) 19 registerActivity(G6)
18 registerControl(G6) 20 registerControl(G6)
19 } 21 }
......
...@@ -83,6 +83,15 @@ export const GraphStyle = { ...@@ -83,6 +83,15 @@ export const GraphStyle = {
83 cursor: 'pointer', 83 cursor: 'pointer',
84 }, 84 },
85 }, 85 },
86 + defaultEdge1: { // TAG:新增彩色连接线
87 + shape: 'flow-polyline-round1',
88 + style: {
89 + stroke: '#CB7FE3',
90 + lineWidth: 1,
91 + lineAppendWidth: 10,
92 + cursor: 'pointer',
93 + },
94 + },
86 defaultNode: { 95 defaultNode: {
87 shape: 'rect', 96 shape: 'rect',
88 size: [120, 40], 97 size: [120, 40],
...@@ -133,4 +142,4 @@ export const GraphStyle = { ...@@ -133,4 +142,4 @@ export const GraphStyle = {
133 anchorPointHoverStyle: {radius: 4, fill: BASE_COLOR, fillOpacity: 1, stroke: BASE_COLOR_DEEP}, 142 anchorPointHoverStyle: {radius: 4, fill: BASE_COLOR, fillOpacity: 1, stroke: BASE_COLOR_DEEP},
134 143
135 edgeDelegationStyle: {stroke: BASE_COLOR, lineDash: [4, 4], lineWidth: 1}, 144 edgeDelegationStyle: {stroke: BASE_COLOR, lineDash: [4, 4], lineWidth: 1},
136 -}
...\ No newline at end of file ...\ No newline at end of file
145 +}
......