aldehyde 0.2.485 → 0.2.487

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/lib/controls/entry-control.d.ts +1 -0
  2. package/lib/controls/entry-control.d.ts.map +1 -1
  3. package/lib/controls/entry-control.js +2 -0
  4. package/lib/controls/entry-control.js.map +1 -1
  5. package/lib/controls/select/index.d.ts +1 -1
  6. package/lib/controls/select/index.d.ts.map +1 -1
  7. package/lib/controls/select/index.js +6 -6
  8. package/lib/controls/select/index.js.map +1 -1
  9. package/lib/draw-canvas-edit/components/asset-bar/index.d.ts.map +1 -1
  10. package/lib/draw-canvas-edit/components/asset-bar/index.js +26 -7
  11. package/lib/draw-canvas-edit/components/asset-bar/index.js.map +1 -1
  12. package/lib/draw-canvas-edit/components/asset-bar/index.less +1 -1
  13. package/lib/draw-canvas-edit/components/render/handlers/drag-outside-handlers.d.ts.map +1 -1
  14. package/lib/draw-canvas-edit/components/render/handlers/drag-outside-handlers.js +9 -5
  15. package/lib/draw-canvas-edit/components/render/handlers/drag-outside-handlers.js.map +1 -1
  16. package/lib/draw-canvas-edit/components/render/handlers/selection-handlers.d.ts.map +1 -1
  17. package/lib/draw-canvas-edit/components/render/handlers/selection-handlers.js +1 -0
  18. package/lib/draw-canvas-edit/components/render/handlers/selection-handlers.js.map +1 -1
  19. package/lib/draw-canvas-edit/components/render/index.d.ts.map +1 -1
  20. package/lib/draw-canvas-edit/components/render/index.js +3 -6
  21. package/lib/draw-canvas-edit/components/render/index.js.map +1 -1
  22. package/lib/draw-canvas-edit/components/render/tools/component-factory.d.ts.map +1 -1
  23. package/lib/draw-canvas-edit/components/render/tools/component-factory.js +4 -5
  24. package/lib/draw-canvas-edit/components/render/tools/component-factory.js.map +1 -1
  25. package/lib/draw-canvas-edit/components/render/types.d.ts +1 -0
  26. package/lib/draw-canvas-edit/components/render/types.d.ts.map +1 -1
  27. package/lib/draw-canvas-edit/components/render/types.js.map +1 -1
  28. package/lib/draw-canvas-edit/components/setting-form/index.d.ts.map +1 -1
  29. package/lib/draw-canvas-edit/components/setting-form/index.js +5 -3
  30. package/lib/draw-canvas-edit/components/setting-form/index.js.map +1 -1
  31. package/lib/draw-canvas-edit/index.d.ts.map +1 -1
  32. package/lib/draw-canvas-edit/index.js +12 -3
  33. package/lib/draw-canvas-edit/index.js.map +1 -1
  34. package/lib/module/dtmpl-edit-card.d.ts +1 -1
  35. package/lib/module/dtmpl-edit-card.d.ts.map +1 -1
  36. package/lib/module/dtmpl-edit-card.js +40 -7
  37. package/lib/module/dtmpl-edit-card.js.map +1 -1
  38. package/lib/module/dtmpl-edit-page.d.ts +1 -1
  39. package/lib/module/dtmpl-edit-page.d.ts.map +1 -1
  40. package/lib/module/dtmpl-edit-page.js +40 -7
  41. package/lib/module/dtmpl-edit-page.js.map +1 -1
  42. package/lib/table/act-table.d.ts +1 -0
  43. package/lib/table/act-table.d.ts.map +1 -1
  44. package/lib/table/act-table.js +5 -2
  45. package/lib/table/act-table.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/aldehyde/controls/entry-control.tsx +3 -0
  48. package/src/aldehyde/controls/select/index.tsx +2 -3
  49. package/src/aldehyde/draw-canvas-edit/components/asset-bar/index.less +1 -1
  50. package/src/aldehyde/draw-canvas-edit/components/asset-bar/index.tsx +19 -6
  51. package/src/aldehyde/draw-canvas-edit/components/render/draws/graph-draw.ts +249 -249
  52. package/src/aldehyde/draw-canvas-edit/components/render/draws/link-draw.ts +1414 -1414
  53. package/src/aldehyde/draw-canvas-edit/components/render/draws/preview-draw.ts +273 -273
  54. package/src/aldehyde/draw-canvas-edit/components/render/draws/ref-line-draw.ts +70 -70
  55. package/src/aldehyde/draw-canvas-edit/components/render/draws/ruler-draw.ts +165 -165
  56. package/src/aldehyde/draw-canvas-edit/components/render/handlers/drag-outside-handlers.ts +4 -0
  57. package/src/aldehyde/draw-canvas-edit/components/render/handlers/selection-handlers.ts +1 -0
  58. package/src/aldehyde/draw-canvas-edit/components/render/index.ts +2 -6
  59. package/src/aldehyde/draw-canvas-edit/components/render/tools/component-factory.ts +4 -5
  60. package/src/aldehyde/draw-canvas-edit/components/render/types.ts +1 -0
  61. package/src/aldehyde/draw-canvas-edit/components/setting-form/index.tsx +5 -3
  62. package/src/aldehyde/draw-canvas-edit/index.tsx +12 -3
  63. package/src/aldehyde/module/dtmpl-edit-card.tsx +37 -7
  64. package/src/aldehyde/module/dtmpl-edit-page.tsx +38 -8
  65. package/src/aldehyde/table/act-table.tsx +5 -2
@@ -1,1414 +1,1414 @@
1
- import Konva from 'konva';
2
- import { LinkType, BaseDraw, Draw, Render, LinkDrawPoint, LinkDrawPair, ManualPointsMap, ManualPoint } from '../types';
3
- import * as Draws from '../draws';
4
- import { nanoid } from 'nanoid';
5
- import aStar from '../utils/a-star';
6
- import { BezierSceneFunc } from '../utils/bezier-scene-func';
7
-
8
- // 绘制连线
9
-
10
- export interface LinkDrawOption {
11
- size: number
12
- }
13
-
14
- // 连接线(临时)
15
- export interface LinkDrawState {
16
- linkingLine: {
17
- group: Konva.Group;
18
- circle: Konva.Circle;
19
- line: Konva.Line
20
- } | null;
21
- linkType: LinkType; // 连接线类型
22
- linkManualing: boolean // 是否 正在操作拐点
23
- }
24
-
25
- type Area = {
26
- x1: number;
27
- y1: number;
28
- x2: number;
29
- y2: number;
30
- }
31
-
32
- export class LinkDraw extends BaseDraw implements Draw {
33
- option: LinkDrawOption;
34
-
35
- state: LinkDrawState = {
36
- linkingLine: null,
37
- linkType: "curve",
38
- linkManualing: false
39
- }
40
-
41
- constructor(render: Render, layer: Konva.Layer, option: LinkDrawOption) {
42
- super(render, layer);
43
- this.option = option;
44
- this.group.name(this.constructor.name);
45
- }
46
-
47
- // 元素(连接点们)最小区域(绝对值)
48
- getGroupLinkArea(group?: Konva.Group): Area {
49
- let area: Area = { x1: 0, y1: 0, x2: 0, y2: 0 };
50
- if (group) {
51
- const stageState = this.render.getStageState(); // stage 状态
52
- const anchors = group.find('.link-anchor');
53
- const positions = anchors.map((o) => o.absolutePosition());
54
- area = {
55
- x1: Math.min(...positions.map((o) => o.x)) - stageState.x,
56
- y1: Math.min(...positions.map((o) => o.y)) - stageState.y,
57
- x2: Math.max(...positions.map((o) => o.x)) - stageState.x,
58
- y2: Math.max(...positions.map((o) => o.y)) - stageState.y
59
- };
60
- }
61
- return area;
62
- }
63
-
64
- // 连线不可通过区域
65
- getGroupForbiddenArea(groupArea: Area, gap: number): Area {
66
- const area: Area = {
67
- x1: groupArea.x1 - gap,
68
- y1: groupArea.y1 - gap,
69
- x2: groupArea.x2 + gap,
70
- y2: groupArea.y2 + gap
71
- };
72
- return area;
73
- }
74
-
75
- // 连线通过区域
76
- getGroupAccessArea(groupArea: Area, gap: number): Area {
77
- const area: Area = {
78
- x1: groupArea.x1 - gap,
79
- y1: groupArea.y1 - gap,
80
- x2: groupArea.x2 + gap,
81
- y2: groupArea.y2 + gap
82
- };
83
- return area;
84
- }
85
-
86
- // 两区域扩展
87
- getGroupPairArea(groupArea1: Area, groupArea2: Area): Area {
88
- const area: Area = {
89
- x1: Math.min(groupArea1.x1, groupArea2.x1),
90
- y1: Math.min(groupArea1.y1, groupArea2.y1),
91
- x2: Math.max(groupArea1.x2, groupArea2.x2),
92
- y2: Math.max(groupArea1.y2, groupArea2.y2)
93
- };
94
- return area;
95
- }
96
-
97
- // 两区域最短距离
98
- getGroupPairDistance(groupArea1: Area, groupArea2: Area): number {
99
- const xs = [groupArea1.x1, groupArea1.x2, groupArea2.x1, groupArea2.x2];
100
- const maxX = Math.max(...xs);
101
- const minX = Math.min(...xs);
102
- const dx = maxX - minX - (groupArea1.x2 - groupArea1.x1 + (groupArea2.x2 - groupArea2.x1));
103
- const ys = [groupArea1.y1, groupArea1.y2, groupArea2.y1, groupArea2.y2];
104
- const maxY = Math.max(...ys);
105
- const minY = Math.min(...ys);
106
- const dy = maxY - minY - (groupArea1.y2 - groupArea1.y1 + (groupArea2.y2 - groupArea2.y1));
107
- return this.render.toBoardValue(Math.min(this.render.bgSize * 0.5, Math.max(dx < 6 ? 6 : dx, dy < 6 ? 6 : dy) * 0.5));
108
- }
109
-
110
- // 两区域空隙中点
111
- getGroupPairCenter(groupArea1: Area, groupArea2: Area): Konva.Vector2d {
112
- return { x: (groupArea2.x1 + groupArea1.x2) * 0.5, y: (groupArea2.y1 + groupArea1.y2) * 0.5 };
113
- }
114
-
115
- // 连接出入口
116
- getEntry(anchor: Konva.Node, groupForbiddenArea: Area, gap: number): Konva.Vector2d {
117
- const stageState = this.render.getStageState();
118
- const fromPos = anchor.absolutePosition();
119
- // 默认为 起点/终点 位置(无 direction 时的值)
120
- let x = fromPos.x - stageState.x;
121
- let y = fromPos.y - stageState.y;
122
- const direction = anchor.attrs.direction;
123
- // 定义了 direction 的时候
124
- if (direction) {
125
- // 取整 连接点 锚点 旋转角度(保留 1 位小数点)
126
- const rotate = Math.round(anchor.getAbsoluteRotation() * 10) / 10;
127
- if (rotate === -45) {
128
- if (direction === 'top') {
129
- x = groupForbiddenArea.x1;
130
- y = groupForbiddenArea.y1;
131
- } else if (direction === 'bottom') {
132
- x = groupForbiddenArea.x2;
133
- y = groupForbiddenArea.y2;
134
- } else if (direction === 'left') {
135
- x = groupForbiddenArea.x1;
136
- y = groupForbiddenArea.y2;
137
- } else if (direction === 'right') {
138
- x = groupForbiddenArea.x2;
139
- y = groupForbiddenArea.y1;
140
- }
141
- } else if (rotate === 45) {
142
- if (direction === 'top') {
143
- x = groupForbiddenArea.x2;
144
- y = groupForbiddenArea.y1;
145
- } else if (direction === 'bottom') {
146
- x = groupForbiddenArea.x1;
147
- y = groupForbiddenArea.y2;
148
- } else if (direction === 'left') {
149
- x = groupForbiddenArea.x1;
150
- y = groupForbiddenArea.y1;
151
- } else if (direction === 'right') {
152
- x = groupForbiddenArea.x2;
153
- y = groupForbiddenArea.y2;
154
- }
155
- } else if (rotate === 135) {
156
- if (direction === 'top') {
157
- x = groupForbiddenArea.x2;
158
- y = groupForbiddenArea.y2;
159
- } else if (direction === 'bottom') {
160
- x = groupForbiddenArea.x1;
161
- y = groupForbiddenArea.y1;
162
- } else if (direction === 'left') {
163
- x = groupForbiddenArea.x2;
164
- y = groupForbiddenArea.y1;
165
- } else if (direction === 'right') {
166
- x = groupForbiddenArea.x1;
167
- y = groupForbiddenArea.y2;
168
- }
169
- } else if (rotate === -135) {
170
- if (direction === 'top') {
171
- x = groupForbiddenArea.x1;
172
- y = groupForbiddenArea.y2;
173
- } else if (direction === 'bottom') {
174
- x = groupForbiddenArea.x2;
175
- y = groupForbiddenArea.y1;
176
- } else if (direction === 'left') {
177
- x = groupForbiddenArea.x2;
178
- y = groupForbiddenArea.y2;
179
- } else if (direction === 'right') {
180
- x = groupForbiddenArea.x1;
181
- y = groupForbiddenArea.y1;
182
- }
183
- } else if (rotate > -45 && rotate < 45) {
184
- const offset = gap * Math.tan((rotate * Math.PI) / 180);
185
- if (direction === 'top') {
186
- x = fromPos.x - stageState.x + offset;
187
- y = groupForbiddenArea.y1;
188
- } else if (direction === 'bottom') {
189
- x = fromPos.x - stageState.x - offset;
190
- y = groupForbiddenArea.y2;
191
- } else if (direction === 'left') {
192
- x = groupForbiddenArea.x1;
193
- y = fromPos.y - stageState.y - offset;
194
- } else if (direction === 'right') {
195
- x = groupForbiddenArea.x2;
196
- y = fromPos.y - stageState.y + offset;
197
- }
198
- } else if (rotate > 45 && rotate < 135) {
199
- const offset = gap * Math.atan(((90 - rotate) * Math.PI) / 180);
200
- if (direction === 'top') {
201
- x = groupForbiddenArea.x2;
202
- y = fromPos.y - stageState.y - offset;
203
- } else if (direction === 'bottom') {
204
- x = groupForbiddenArea.x1;
205
- y = fromPos.y - stageState.y + offset;
206
- } else if (direction === 'left') {
207
- x = fromPos.x - stageState.x - offset;
208
- y = groupForbiddenArea.y1;
209
- } else if (direction === 'right') {
210
- x = fromPos.x - stageState.x + offset;
211
- y = groupForbiddenArea.y2;
212
- }
213
- } else if ((rotate > 135 && rotate <= 180) || (rotate >= -180 && rotate < -135)) {
214
- const offset = gap * Math.tan((rotate * Math.PI) / 180);
215
- if (direction === 'top') {
216
- x = fromPos.x - stageState.x - offset;
217
- y = groupForbiddenArea.y2;
218
- } else if (direction === 'bottom') {
219
- x = fromPos.x - stageState.x + offset;
220
- y = groupForbiddenArea.y1;
221
- } else if (direction === 'left') {
222
- x = groupForbiddenArea.x2;
223
- y = fromPos.y - stageState.y + offset;
224
- } else if (direction === 'right') {
225
- x = groupForbiddenArea.x1;
226
- y = fromPos.y - stageState.y - offset;
227
- }
228
- } else if (rotate > -135 && rotate < -45) {
229
- const offset = gap * Math.atan(((90 + rotate) * Math.PI) / 180);
230
- if (direction === 'top') {
231
- x = groupForbiddenArea.x1;
232
- y = fromPos.y - stageState.y - offset;
233
- } else if (direction === 'bottom') {
234
- x = groupForbiddenArea.x2;
235
- y = fromPos.y - stageState.y + offset;
236
- } else if (direction === 'left') {
237
- x = fromPos.x - stageState.x - offset;
238
- y = groupForbiddenArea.y2;
239
- } else if (direction === 'right') {
240
- x = fromPos.x - stageState.x + offset;
241
- y = groupForbiddenArea.y1;
242
- }
243
- }
244
- }
245
- return { x, y } as Konva.Vector2d;
246
- }
247
-
248
- // 连接点信息
249
- getAnchorPos(anchor?: Konva.Node): Konva.Vector2d {
250
- const stageState = this.render.getStageState();
251
- return anchor ? { x: anchor.absolutePosition().x - stageState.x, y: anchor.absolutePosition().y - stageState.y } : { x: 0, y: 0 };
252
- }
253
-
254
- /**
255
- * 修改当前连接线类型
256
- * @param linkType LinkType
257
- */
258
- changeLinkType(linkType: LinkType) {
259
- this.state.linkType = linkType;
260
- this.render.emit('link-type-change', this.state.linkType);
261
- }
262
-
263
- override draw() {
264
- this.clear();
265
- const stageState = this.render.getStageState();
266
-
267
- // 所有层级的素材
268
- const groups = [
269
- ...(this.render.layer.find('.asset') as Konva.Group[]),
270
- ...(this.render.layer.find('.sub-asset') as Konva.Group[])
271
- ];
272
- const points = groups.reduce((ps, group) => ps.concat(Array.isArray(group.getAttr('points')) ? group.getAttr('points') : []), [] as LinkDrawPoint[]);
273
- const pairs = points.reduce((ps, point) => ps.concat(point.pairs ? point.pairs.filter((o) => !o.disabled) : []), [] as LinkDrawPair[]);
274
-
275
- // 连接线
276
- for (const pair of pairs) {
277
- // 多层素材,需要排除内部 pair 对
278
- // pair 也不能为 disabled
279
- if (pair.from.groupId !== pair.to.groupId && !pair.disabled) {
280
- const fromGroup = groups.find((o) => o.id() === pair.from.groupId);
281
- const fromPoint = points.find((o) => o.id === pair.from.pointId);
282
- const toGroup = groups.find((o) => o.id() === pair.to.groupId);
283
- const toPoint = points.find((o) => o.id === pair.to.pointId);
284
- if (pair.linkType === "manual") {
285
- // 折线
286
- if (fromGroup && toGroup && fromPoint && toPoint) {
287
- const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
288
- const toAnchor = toGroup.findOne(`#${toPoint.id}`);
289
- // 锚点信息
290
- const fromAnchorPos = this.getAnchorPos(fromAnchor);
291
- const toAnchorPos = this.getAnchorPos(toAnchor);
292
- // 拐点(已拐)记录
293
- const manualPointsMap: ManualPointsMap = fromGroup.getAttr('manualPointsMap') ?? ({} as ManualPointsMap);
294
- const manualPoints = manualPointsMap[pair.id] ?? ([] as ManualPoint[]);
295
- // 连接点 + 拐点
296
- const linkPoints = [
297
- [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)],
298
- ...manualPoints.map((o) => [o.x, o.y]),
299
- [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
300
- ];
301
-
302
- // 连接线
303
- const linkLine = new Konva.Arrow({
304
- name: 'link-line',
305
- // 用于删除连接线
306
- groupId: fromGroup.id(),
307
- pointId: fromPoint.id,
308
- pairId: pair.id,
309
- linkType: pair.linkType,
310
- points: linkPoints.flat(),
311
- pointerAtBeginning: false,
312
- pointerAtEnding: false
313
- });
314
- linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
315
- linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
316
- linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
317
- linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
318
- linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
319
- linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
320
- linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
321
- if (!this.render.config.readonly) {
322
- linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
323
- linkLine.on('mouseenter', () => {
324
- linkLine.opacity(0.5);
325
- document.body.style.cursor = 'pointer';
326
- });
327
- linkLine.on('mouseleave', () => {
328
- linkLine.opacity(1);
329
- document.body.style.cursor = 'default';
330
- });
331
- }
332
- this.group.add(linkLine);
333
- if (!this.render.config.readonly) {
334
- // 正在拖动效果
335
- const manualingLine = new Konva.Line({
336
- name: 'manualing-line',
337
- stroke: '#ff0000',
338
- strokeWidth: 2,
339
- points: [],
340
- dash: [4, 4]
341
- });
342
- this.group.add(manualingLine);
343
- for (let i = 0; i < linkPoints.length - 1; i++) {
344
- const circle = new Konva.Circle({
345
- name: 'link-manual-point',
346
- id: nanoid(),
347
- pairId: pair.id,
348
- x: (linkPoints[i][0] + linkPoints[i + 1][0]) / 2,
349
- y: (linkPoints[i][1] + linkPoints[i + 1][1]) / 2,
350
- radius: this.render.toStageValue(this.render.bgSize / 2),
351
- stroke: 'rgba(0,0,255,0.2)',
352
- strokeWidth: this.render.toStageValue(1),
353
- linkManualIndex: i // 当前拐点位置
354
- });
355
- // hover 效果
356
- circle.on('mouseenter', () => {
357
- circle.stroke('rgba(0,0,255,0.8)');
358
- document.body.style.cursor = 'pointer';
359
- });
360
- circle.on('mouseleave', () => {
361
- if (!circle.attrs.dragStart) {
362
- circle.stroke('rgba(0,0,255,0.1)');
363
- document.body.style.cursor = 'default';
364
- }
365
- });
366
-
367
- // 拐点操作
368
- circle.on('mousedown', () => {
369
- const pos = circle.getAbsolutePosition();
370
- // 记录操作开始状态
371
- circle.setAttrs({
372
- // 开始坐标
373
- dragStartX: pos.x,
374
- dragStartY: pos.y,
375
- // 正在操作
376
- dragStart: true
377
- });
378
- // 标记状态 - 正在操作拐点
379
- this.state.linkManualing = true;
380
- });
381
- this.render.stage.on('mousemove', () => {
382
- if (circle.attrs.dragStart) {
383
- // 正在操作
384
- const pos = this.render.stage.getPointerPosition();
385
- if (pos) {
386
- // 磁贴
387
- const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
388
- // 移动拐点
389
- circle.setAbsolutePosition(transformerPos);
390
- // 正在拖动效果
391
- const tempPoints = [...linkPoints];
392
- tempPoints.splice(circle.attrs.linkManualIndex + 1, 0, [
393
- this.render.toStageValue(transformerPos.x - stageState.x),
394
- this.render.toStageValue(transformerPos.y - stageState.y)
395
- ]);
396
- manualingLine.points(tempPoints.flat());
397
- }
398
- }
399
- });
400
- circle.on('mouseup', () => {
401
- const pos = circle.getAbsolutePosition();
402
- if (
403
- Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
404
- Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
405
- ) {
406
- // 操作移动距离达到阈值
407
- const stageState = this.render.getStageState();
408
- // 记录(插入)拐点
409
- manualPoints.splice(circle.attrs.linkManualIndex, 0, {
410
- x: this.render.toStageValue(pos.x - stageState.x),
411
- y: this.render.toStageValue(pos.y - stageState.y)
412
- });
413
- manualPointsMap[pair.id] = manualPoints;
414
- fromGroup.setAttr('manualPointsMap', manualPointsMap);
415
- }
416
-
417
- // 操作结束
418
- circle.setAttrs({ dragStart: false });
419
-
420
- // state 操作结束
421
- this.state.linkManualing = false;
422
-
423
- // 销毁
424
- circle.destroy();
425
- manualingLine.destroy();
426
-
427
- // 更新历史
428
- this.render.updateHistory();
429
-
430
- // 对齐线清除
431
- this.render.attractTool.alignLinesClear();
432
-
433
- // 重绘
434
- this.render.redraw([
435
- Draws.LinkDraw.name,
436
- Draws.RulerDraw.name,
437
- Draws.PreviewDraw.name
438
- ]);
439
- })
440
-
441
- this.group.add(circle);
442
- }
443
-
444
- // 拐点(已拐)
445
- for (let i = 1; i < linkPoints.length - 1; i++) {
446
- const circle = new Konva.Circle({
447
- name: 'link-manual-point',
448
- id: nanoid(),
449
- pairId: pair.id,
450
- x: linkPoints[i][0],
451
- y: linkPoints[i][1],
452
- radius: this.render.toStageValue(this.render.bgSize / 2),
453
- stroke: 'rgba(0,100,0,0.2)',
454
- strokeWidth: this.render.toStageValue(1),
455
- linkManualIndex: i // 当前拐点位置
456
- });
457
-
458
- // hover 效果
459
- circle.on('mouseenter', () => {
460
- circle.stroke('rgba(0,100,0,1)');
461
- document.body.style.cursor = 'pointer';
462
- });
463
- circle.on('mouseleave', () => {
464
- if (!circle.attrs.dragStart) {
465
- circle.stroke('rgba(0,100,0,0.1)');
466
- document.body.style.cursor = 'default';
467
- }
468
- });
469
-
470
- // 拐点操作
471
- circle.on('mousedown', () => {
472
- const pos = circle.getAbsolutePosition();
473
- // 记录操作开始状态
474
- circle.setAttrs({
475
- dragStartX: pos.x,
476
- dragStartY: pos.y,
477
- dragStart: true
478
- });
479
-
480
- // 标记状态 - 正在操作拐点
481
- this.state.linkManualing = true;
482
- })
483
- this.render.stage.on('mousemove', () => {
484
- if (circle.attrs.dragStart) {
485
- // 正在操作
486
- const pos = this.render.stage.getPointerPosition();
487
- if (pos) {
488
- // 磁贴
489
- const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
490
- // 移动拐点
491
- circle.setAbsolutePosition(transformerPos);
492
- // 正在拖动效果
493
- const tempPoints = [...linkPoints];
494
- tempPoints[circle.attrs.linkManualIndex] = [
495
- this.render.toStageValue(transformerPos.x - stageState.x),
496
- this.render.toStageValue(transformerPos.y - stageState.y)
497
- ];
498
- manualingLine.points(tempPoints.flat());
499
- }
500
- }
501
- });
502
- circle.on('mouseup', () => {
503
- const pos = circle.getAbsolutePosition();
504
- if (
505
- Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
506
- Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
507
- ) {
508
- // 操作移动距离达到阈值
509
- const stageState = this.render.getStageState();
510
- // 记录(更新)拐点
511
- manualPoints[circle.attrs.linkManualIndex - 1] = {
512
- x: this.render.toStageValue(pos.x - stageState.x),
513
- y: this.render.toStageValue(pos.y - stageState.y)
514
- };
515
- manualPointsMap[pair.id] = manualPoints;
516
- fromGroup.setAttr('manualPointsMap', manualPointsMap);
517
- }
518
- // 操作结束
519
- circle.setAttrs({ dragStart: false });
520
- // state 操作结束
521
- this.state.linkManualing = false;
522
- // 销毁
523
- circle.destroy();
524
- manualingLine.destroy();
525
- // 更新历史
526
- this.render.updateHistory();
527
- // 对齐线清除
528
- this.render.attractTool.alignLinesClear();
529
- // 重绘
530
- this.render.redraw([
531
- Draws.LinkDraw.name,
532
- Draws.RulerDraw.name,
533
- Draws.PreviewDraw.name
534
- ]);
535
- })
536
- this.group.add(circle);
537
- }
538
- }
539
- }
540
- } else if (pair.linkType === "curve") {
541
- // 曲线
542
- if (fromGroup && toGroup && fromPoint && toPoint) {
543
- const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
544
- const toAnchor = toGroup.findOne(`#${toPoint.id}`);
545
- // 锚点信息
546
- const fromAnchorPos = this.getAnchorPos(fromAnchor);
547
- const toAnchorPos = this.getAnchorPos(toAnchor);
548
- // 拐点(已拐)记录
549
- const manualPointsMap: ManualPointsMap = fromGroup.getAttr('manualPointsMap') ?? ({} as ManualPointsMap);
550
- const manualPoints = manualPointsMap[pair.id] ?? ([] as ManualPoint[]);
551
- // 连接点 + 拐点
552
- const linkPoints = [
553
- [
554
- this.render.toStageValue(fromAnchorPos.x),
555
- this.render.toStageValue(fromAnchorPos.y)
556
- ],
557
- ...manualPoints.map((o) => [o.x, o.y]),
558
- [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
559
- ];
560
-
561
- // 连接线
562
- const linkLine = new Konva.Arrow({
563
- name: 'link-line',
564
- // 用于删除连接线
565
- groupId: fromGroup.id(),
566
- pointId: fromPoint.id,
567
- pairId: pair.id,
568
- linkType: pair.linkType,
569
- points: linkPoints.flat(),
570
- pointerAtBeginning: false,
571
- pointerAtEnding: false
572
- });
573
- linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
574
- linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
575
- linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
576
- linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
577
- linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
578
- linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
579
- linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
580
- linkLine.tension(this.render.getLinkSettings(linkLine).tension || 0);
581
- if (!this.render.config.readonly) {
582
- linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
583
- linkLine.on('mouseenter', () => {
584
- linkLine.opacity(0.5);
585
- document.body.style.cursor = 'pointer';
586
- });
587
- linkLine.on('mouseleave', () => {
588
- linkLine.opacity(1);
589
- document.body.style.cursor = 'default';
590
- });
591
- }
592
- this.group.add(linkLine);
593
- if (!this.render.config.readonly) {
594
- // 正在拖动效果
595
- const manualingLine = new Konva.Arrow({
596
- name: 'manualing-line',
597
- stroke: '#ff0000',
598
- strokeWidth: 2,
599
- points: [],
600
- dash: [4, 4],
601
- pointerAtBeginning: false,
602
- pointerAtEnding: false,
603
- tension: this.render.getLinkSettings(linkLine).tension
604
- });
605
- this.group.add(manualingLine);
606
- // 拐点(待拐)
607
- for (let i = 0; i < linkPoints.length - 1; i++) {
608
- const circle = new Konva.Circle({
609
- name: 'link-manual-point',
610
- id: nanoid(),
611
- pairId: pair.id,
612
- x: (linkPoints[i][0] + linkPoints[i + 1][0]) / 2,
613
- y: (linkPoints[i][1] + linkPoints[i + 1][1]) / 2,
614
- radius: this.render.toStageValue(this.render.bgSize / 2),
615
- stroke: 'rgba(0,0,255,0.2)',
616
- strokeWidth: this.render.toStageValue(1),
617
- linkManualIndex: i // 当前拐点位置
618
- });
619
- // hover 效果
620
- circle.on('mouseenter', () => {
621
- circle.stroke('rgba(0,0,255,0.8)');
622
- document.body.style.cursor = 'pointer';
623
- });
624
- circle.on('mouseleave', () => {
625
- if (!circle.attrs.dragStart) {
626
- circle.stroke('rgba(0,0,255,0.1)');
627
- document.body.style.cursor = 'default';
628
- }
629
- });
630
- // 拐点操作
631
- circle.on('mousedown', () => {
632
- const pos = circle.getAbsolutePosition();
633
- // 记录操作开始状态
634
- circle.setAttrs({
635
- // 开始坐标
636
- dragStartX: pos.x,
637
- dragStartY: pos.y,
638
- // 正在操作
639
- dragStart: true
640
- });
641
- // 标记状态 - 正在操作拐点
642
- this.state.linkManualing = true;
643
- });
644
- this.render.stage.on('mousemove', () => {
645
- if (circle.attrs.dragStart) {
646
- // 正在操作
647
- const pos = this.render.stage.getPointerPosition();
648
- if (pos) {
649
- // 磁贴
650
- const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
651
- // 移动拐点
652
- circle.setAbsolutePosition(transformerPos);
653
- // 正在拖动效果
654
- const tempPoints = [...linkPoints];
655
- tempPoints.splice(circle.attrs.linkManualIndex + 1, 0, [
656
- this.render.toStageValue(transformerPos.x - stageState.x),
657
- this.render.toStageValue(transformerPos.y - stageState.y)
658
- ]);
659
- manualingLine.points(tempPoints.flat());
660
- }
661
- }
662
- });
663
- circle.on('mouseup', () => {
664
- const pos = circle.getAbsolutePosition();
665
- if (
666
- Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
667
- Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
668
- ) {
669
- // 操作移动距离达到阈值
670
- const stageState = this.render.getStageState();
671
- // 记录(插入)拐点
672
- manualPoints.splice(circle.attrs.linkManualIndex, 0, {
673
- x: this.render.toStageValue(pos.x - stageState.x),
674
- y: this.render.toStageValue(pos.y - stageState.y)
675
- });
676
- manualPointsMap[pair.id] = manualPoints;
677
- fromGroup.setAttr('manualPointsMap', manualPointsMap);
678
- }
679
- // 操作结束
680
- circle.setAttrs({ dragStart: false });
681
- // state 操作结束
682
- this.state.linkManualing = false;
683
- // 销毁
684
- circle.destroy();
685
- manualingLine.destroy();
686
- // 更新历史
687
- this.render.updateHistory();
688
- // 对齐线清除
689
- this.render.attractTool.alignLinesClear();
690
- // 重绘
691
- this.render.redraw([
692
- Draws.LinkDraw.name,
693
- Draws.RulerDraw.name,
694
- Draws.PreviewDraw.name
695
- ]);
696
- });
697
- this.group.add(circle);
698
- }
699
- // 拐点(已拐)
700
- for (let i = 1; i < linkPoints.length - 1; i++) {
701
- const circle = new Konva.Circle({
702
- name: 'link-manual-point',
703
- id: nanoid(),
704
- pairId: pair.id,
705
- x: linkPoints[i][0],
706
- y: linkPoints[i][1],
707
- radius: this.render.toStageValue(this.render.bgSize / 2),
708
- stroke: 'rgba(0,100,0,0.2)',
709
- strokeWidth: this.render.toStageValue(1),
710
- linkManualIndex: i // 当前拐点位置
711
- });
712
- // hover 效果
713
- circle.on('mouseenter', () => {
714
- circle.stroke('rgba(0,100,0,1)');
715
- document.body.style.cursor = 'pointer';
716
- });
717
- circle.on('mouseleave', () => {
718
- if (!circle.attrs.dragStart) {
719
- circle.stroke('rgba(0,100,0,0.1)');
720
- document.body.style.cursor = 'default';
721
- }
722
- });
723
- // 拐点操作
724
- circle.on('mousedown', () => {
725
- const pos = circle.getAbsolutePosition();
726
- // 记录操作开始状态
727
- circle.setAttrs({
728
- dragStartX: pos.x,
729
- dragStartY: pos.y,
730
- dragStart: true
731
- });
732
- // 标记状态 - 正在操作拐点
733
- this.state.linkManualing = true;
734
- });
735
- this.render.stage.on('mousemove', () => {
736
- if (circle.attrs.dragStart) {
737
- // 正在操作
738
- const pos = this.render.stage.getPointerPosition();
739
- if (pos) {
740
- // 磁贴
741
- const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
742
- // 移动拐点
743
- circle.setAbsolutePosition(transformerPos);
744
- // 正在拖动效果
745
- const tempPoints = [...linkPoints];
746
- tempPoints[circle.attrs.linkManualIndex] = [
747
- this.render.toStageValue(transformerPos.x - stageState.x),
748
- this.render.toStageValue(transformerPos.y - stageState.y)
749
- ];
750
- manualingLine.points(tempPoints.flat());
751
- }
752
- }
753
- });
754
- circle.on('mouseup', () => {
755
- const pos = circle.getAbsolutePosition();
756
- if (
757
- Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
758
- Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
759
- ) {
760
- // 操作移动距离达到阈值
761
- const stageState = this.render.getStageState();
762
- // 记录(更新)拐点
763
- manualPoints[circle.attrs.linkManualIndex - 1] = {
764
- x: this.render.toStageValue(pos.x - stageState.x),
765
- y: this.render.toStageValue(pos.y - stageState.y)
766
- };
767
- manualPointsMap[pair.id] = manualPoints;
768
- fromGroup.setAttr('manualPointsMap', manualPointsMap);
769
- }
770
- // 操作结束
771
- circle.setAttrs({ dragStart: false });
772
- // state 操作结束
773
- this.state.linkManualing = false;
774
- // 销毁
775
- circle.destroy();
776
- manualingLine.destroy();
777
- // 更新历史
778
- this.render.updateHistory();
779
- // 对齐线清除
780
- this.render.attractTool.alignLinesClear();
781
- // 重绘
782
- this.render.redraw([
783
- Draws.LinkDraw.name,
784
- Draws.RulerDraw.name,
785
- Draws.PreviewDraw.name
786
- ]);
787
- })
788
- this.group.add(circle);
789
- }
790
- }
791
- }
792
- } else if (pair.linkType === "bezier") {
793
- if (fromGroup && toGroup && fromPoint && toPoint) {
794
- const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
795
- const toAnchor = toGroup.findOne(`#${toPoint.id}`);
796
- // 锚点信息
797
- const fromAnchorPos = this.getAnchorPos(fromAnchor);
798
- const toAnchorPos = this.getAnchorPos(toAnchor);
799
- // 拐点(已拐)记录
800
- const manualPointsMap: ManualPointsMap = fromGroup.getAttr('manualPointsMap') ?? ({} as ManualPointsMap);
801
- const manualPoints = manualPointsMap[pair.id] ?? ([] as ManualPoint[]);
802
- // 连接点 + 拐点
803
- const linkPoints = [
804
- [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)],
805
- ...manualPoints.map((o) => [o.x, o.y]),
806
- [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
807
- ];
808
- // 连接线
809
- const linkLine = new Konva.Arrow({
810
- name: 'link-line',
811
- // 用于删除连接线
812
- groupId: fromGroup.id(),
813
- pointId: fromPoint.id,
814
- pairId: pair.id,
815
- linkType: pair.linkType,
816
- points: linkPoints.flat(),
817
- pointerAtBeginning: false,
818
- pointerAtEnding: false,
819
- sceneFunc: BezierSceneFunc
820
- });
821
- linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
822
- linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
823
- linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
824
- linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
825
- linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
826
- linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
827
- linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
828
- if (!this.render.config.readonly) {
829
- linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
830
- linkLine.on('mouseenter', () => {
831
- linkLine.opacity(0.5);
832
- document.body.style.cursor = 'pointer';
833
- });
834
- linkLine.on('mouseleave', () => {
835
- linkLine.opacity(1);
836
- document.body.style.cursor = 'default';
837
- });
838
- }
839
- this.group.add(linkLine);
840
- if (!this.render.config.readonly) {
841
- // 正在拖动效果
842
- const manualingLine = new Konva.Arrow({
843
- name: 'manualing-line',
844
- stroke: '#ff0000',
845
- strokeWidth: 2,
846
- points: [],
847
- dash: [4, 4],
848
- pointerAtBeginning: false,
849
- pointerAtEnding: false,
850
- sceneFunc: BezierSceneFunc
851
- });
852
- this.group.add(manualingLine);
853
- // 拐点
854
- if (linkPoints.length <= 3) {
855
- // 拐点(待拐)
856
- for (let i = 0; i < linkPoints.length - 1; i++) {
857
- const circle = new Konva.Circle({
858
- name: 'link-manual-point',
859
- id: nanoid(),
860
- pairId: pair.id,
861
- x: (linkPoints[i][0] + linkPoints[i + 1][0]) / 2,
862
- y: (linkPoints[i][1] + linkPoints[i + 1][1]) / 2,
863
- radius: this.render.toStageValue(this.render.bgSize / 2),
864
- stroke: 'rgba(0,0,255,0.2)',
865
- strokeWidth: this.render.toStageValue(1),
866
- linkManualIndex: i // 当前拐点位置
867
- });
868
- // hover 效果
869
- circle.on('mouseenter', () => {
870
- circle.stroke('rgba(0,0,255,0.8)');
871
- document.body.style.cursor = 'pointer';
872
- });
873
- circle.on('mouseleave', () => {
874
- if (!circle.attrs.dragStart) {
875
- circle.stroke('rgba(0,0,255,0.1)');
876
- document.body.style.cursor = 'default';
877
- }
878
- });
879
- // 拐点操作
880
- circle.on('mousedown', () => {
881
- const pos = circle.getAbsolutePosition();
882
- // 记录操作开始状态
883
- circle.setAttrs({
884
- // 开始坐标
885
- dragStartX: pos.x,
886
- dragStartY: pos.y,
887
- // 正在操作
888
- dragStart: true
889
- });
890
- // 标记状态 - 正在操作拐点
891
- this.state.linkManualing = true;
892
- });
893
- this.render.stage.on('mousemove', () => {
894
- if (circle.attrs.dragStart) {
895
- // 正在操作
896
- const pos = this.render.stage.getPointerPosition();
897
- if (pos) {
898
- // 磁贴
899
- const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
900
- // 移动拐点
901
- circle.setAbsolutePosition(transformerPos);
902
- // 正在拖动效果
903
- const tempPoints = [...linkPoints];
904
- tempPoints.splice(circle.attrs.linkManualIndex + 1, 0, [
905
- this.render.toStageValue(transformerPos.x - stageState.x),
906
- this.render.toStageValue(transformerPos.y - stageState.y)
907
- ]);
908
- manualingLine.points(tempPoints.flat());
909
- }
910
- }
911
- });
912
- circle.on('mouseup', () => {
913
- const pos = circle.getAbsolutePosition();
914
- if (
915
- Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
916
- Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
917
- ) {
918
- // 操作移动距离达到阈值
919
- const stageState = this.render.getStageState();
920
- // 记录(插入)拐点
921
- manualPoints.splice(circle.attrs.linkManualIndex, 0, {
922
- x: this.render.toStageValue(pos.x - stageState.x),
923
- y: this.render.toStageValue(pos.y - stageState.y)
924
- });
925
- manualPointsMap[pair.id] = manualPoints;
926
- fromGroup.setAttr('manualPointsMap', manualPointsMap);
927
- }
928
- // 操作结束
929
- circle.setAttrs({ dragStart: false });
930
- // state 操作结束
931
- this.state.linkManualing = false;
932
- // 销毁
933
- circle.destroy();
934
- manualingLine.destroy();
935
- // 更新历史
936
- this.render.updateHistory();
937
- // 对齐线清除
938
- this.render.attractTool.alignLinesClear();
939
- // 重绘
940
- this.render.redraw([
941
- Draws.LinkDraw.name,
942
- Draws.RulerDraw.name,
943
- Draws.PreviewDraw.name
944
- ]);
945
- });
946
- this.group.add(circle);
947
- }
948
- }
949
- // 拐点(已拐)
950
- for (let i = 1; i < linkPoints.length - 1; i++) {
951
- const circle = new Konva.Circle({
952
- name: 'link-manual-point',
953
- id: nanoid(),
954
- pairId: pair.id,
955
- x: linkPoints[i][0],
956
- y: linkPoints[i][1],
957
- radius: this.render.toStageValue(this.render.bgSize / 2),
958
- stroke: 'rgba(0,100,0,0.2)',
959
- strokeWidth: this.render.toStageValue(1),
960
- linkManualIndex: i // 当前拐点位置
961
- });
962
- // hover 效果
963
- circle.on('mouseenter', () => {
964
- circle.stroke('rgba(0,100,0,1)');
965
- document.body.style.cursor = 'pointer';
966
- });
967
- circle.on('mouseleave', () => {
968
- if (!circle.attrs.dragStart) {
969
- circle.stroke('rgba(0,100,0,0.1)');
970
- document.body.style.cursor = 'default';
971
- }
972
- });
973
- // 拐点操作
974
- circle.on('mousedown', () => {
975
- const pos = circle.getAbsolutePosition();
976
- // 记录操作开始状态
977
- circle.setAttrs({
978
- dragStartX: pos.x,
979
- dragStartY: pos.y,
980
- dragStart: true
981
- });
982
- // 标记状态 - 正在操作拐点
983
- this.state.linkManualing = true;
984
- });
985
- this.render.stage.on('mousemove', () => {
986
- if (circle.attrs.dragStart) {
987
- // 正在操作
988
- const pos = this.render.stage.getPointerPosition();
989
- if (pos) {
990
- // 磁贴
991
- const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
992
- // 移动拐点
993
- circle.setAbsolutePosition(transformerPos);
994
- // 正在拖动效果
995
- const tempPoints = [...linkPoints]
996
- tempPoints[circle.attrs.linkManualIndex] = [
997
- this.render.toStageValue(transformerPos.x - stageState.x),
998
- this.render.toStageValue(transformerPos.y - stageState.y)
999
- ];
1000
- manualingLine.points(tempPoints.flat());
1001
- }
1002
- }
1003
- });
1004
- circle.on('mouseup', () => {
1005
- const pos = circle.getAbsolutePosition();
1006
- if (
1007
- Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
1008
- Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
1009
- ) {
1010
- // 操作移动距离达到阈值
1011
- const stageState = this.render.getStageState();
1012
- // 记录(更新)拐点
1013
- manualPoints[circle.attrs.linkManualIndex - 1] = {
1014
- x: this.render.toStageValue(pos.x - stageState.x),
1015
- y: this.render.toStageValue(pos.y - stageState.y)
1016
- };
1017
- manualPointsMap[pair.id] = manualPoints;
1018
- fromGroup.setAttr('manualPointsMap', manualPointsMap);
1019
- }
1020
- // 操作结束
1021
- circle.setAttrs({ dragStart: false });
1022
- // state 操作结束
1023
- this.state.linkManualing = false;
1024
- // 销毁
1025
- circle.destroy();
1026
- manualingLine.destroy();
1027
- // 更新历史
1028
- this.render.updateHistory();
1029
- // 对齐线清除
1030
- this.render.attractTool.alignLinesClear();
1031
- // 重绘
1032
- this.render.redraw([
1033
- Draws.LinkDraw.name,
1034
- Draws.RulerDraw.name,
1035
- Draws.PreviewDraw.name
1036
- ]);
1037
- });
1038
- this.group.add(circle);
1039
- }
1040
- }
1041
- }
1042
- } else if (pair.linkType === "straight") {
1043
- // 直线
1044
- if (fromGroup && toGroup && fromPoint && toPoint) {
1045
- const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
1046
- const toAnchor = toGroup.findOne(`#${toPoint.id}`);
1047
- // 锚点信息
1048
- const fromAnchorPos = this.getAnchorPos(fromAnchor);
1049
- const toAnchorPos = this.getAnchorPos(toAnchor);
1050
- const linkLine = new Konva.Arrow({
1051
- name: 'link-line',
1052
- // 用于删除连接线
1053
- groupId: fromGroup.id(),
1054
- pointId: fromPoint.id,
1055
- pairId: pair.id,
1056
- linkType: pair.linkType,
1057
- points: [
1058
- [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)],
1059
- [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
1060
- ].flat(),
1061
- pointerAtBeginning: false,
1062
- pointerAtEnding: false
1063
- });
1064
- linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
1065
- linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
1066
- linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
1067
- linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
1068
- linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
1069
- linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
1070
- linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
1071
-
1072
- if (!this.render.config.readonly) {
1073
- linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
1074
- linkLine.on('mouseenter', () => {
1075
- linkLine.opacity(0.5);
1076
- document.body.style.cursor = 'pointer';
1077
- });
1078
- linkLine.on('mouseleave', () => {
1079
- linkLine.opacity(1);
1080
- document.body.style.cursor = 'default';
1081
- });
1082
- }
1083
- this.group.add(linkLine);
1084
- }
1085
- } else {
1086
- // 自动
1087
- // 最小区域
1088
- const fromGroupLinkArea = this.getGroupLinkArea(fromGroup);
1089
- const toGroupLinkArea = this.getGroupLinkArea(toGroup);
1090
- // 两区域的最短距离
1091
- const groupDistance = this.getGroupPairDistance(fromGroupLinkArea, toGroupLinkArea);
1092
- // 不可通过区域
1093
- const fromGroupForbiddenArea = this.getGroupForbiddenArea(fromGroupLinkArea, groupDistance - 2);
1094
- const toGroupForbiddenArea = this.getGroupForbiddenArea(toGroupLinkArea, groupDistance - 2);
1095
- // 两区域扩展
1096
- const groupForbiddenArea = this.getGroupPairArea(fromGroupForbiddenArea, toGroupForbiddenArea);
1097
- // 连线通过区域
1098
- const groupAccessArea = this.getGroupPairArea(this.getGroupAccessArea(fromGroupForbiddenArea, groupDistance), this.getGroupAccessArea(toGroupForbiddenArea, groupDistance));
1099
- if (fromGroup && toGroup && fromPoint && toPoint) {
1100
- const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
1101
- const toAnchor = toGroup.findOne(`#${toPoint.id}`);
1102
- // 锚点信息
1103
- const fromAnchorPos = this.getAnchorPos(fromAnchor);
1104
- const toAnchorPos = this.getAnchorPos(toAnchor);
1105
- if (fromAnchor && toAnchor) {
1106
- // 连接出入口
1107
- const fromEntry: Konva.Vector2d = this.getEntry(fromAnchor, fromGroupForbiddenArea, groupDistance);
1108
- const toEntry: Konva.Vector2d = this.getEntry(toAnchor, toGroupForbiddenArea, groupDistance);
1109
- type matrixPoint = { x: number, y: number, type?: 'from' | 'to' | 'from-entry' | 'to-entry' };
1110
- // 可能点
1111
- let matrixPoints: matrixPoint[] = [];
1112
- // 通过区域 四角
1113
- matrixPoints.push({ x: groupAccessArea.x1, y: groupAccessArea.y1 });
1114
- matrixPoints.push({ x: groupAccessArea.x2, y: groupAccessArea.y2 });
1115
- matrixPoints.push({ x: groupAccessArea.x1, y: groupAccessArea.y2 });
1116
- matrixPoints.push({ x: groupAccessArea.x2, y: groupAccessArea.y1 });
1117
- // 最小区域 四角
1118
- matrixPoints.push({ x: groupForbiddenArea.x1, y: groupForbiddenArea.y1 });
1119
- matrixPoints.push({ x: groupForbiddenArea.x2, y: groupForbiddenArea.y2 });
1120
- matrixPoints.push({ x: groupForbiddenArea.x1, y: groupForbiddenArea.y2 });
1121
- matrixPoints.push({ x: groupForbiddenArea.x2, y: groupForbiddenArea.y1 });
1122
- // 起点
1123
- matrixPoints.push({ ...fromAnchorPos, type: 'from' });
1124
- // 起点 出口
1125
- matrixPoints.push({ ...fromEntry, type: 'from-entry' });
1126
- // 终点
1127
- matrixPoints.push({ ...toAnchorPos, type: 'to' });
1128
- // 终点 入口
1129
- matrixPoints.push({ ...toEntry, type: 'to-entry' });
1130
- // 通过区域 中点
1131
- matrixPoints.push(this.getGroupPairCenter(fromGroupForbiddenArea, toGroupForbiddenArea));
1132
- // 去重
1133
- matrixPoints = matrixPoints.reduce(
1134
- (arr, item) => {
1135
- if (item.type === void 0) {
1136
- if (arr.findIndex((o) => o.x === item.x && o.y === item.y) < 0) {
1137
- arr.push(item);
1138
- }
1139
- } else {
1140
- const idx = arr.findIndex((o) => o.x === item.x && o.y === item.y)
1141
- if (idx > -1) {
1142
- arr.splice(idx, 1);
1143
- }
1144
- arr.push(item);
1145
- }
1146
- return arr;
1147
- },
1148
- [] as typeof matrixPoints
1149
- );
1150
- const columns = [
1151
- ...matrixPoints.map((o) => o.x),
1152
- // 增加列
1153
- fromGroupForbiddenArea.x1,
1154
- fromGroupForbiddenArea.x2,
1155
- toGroupForbiddenArea.x1,
1156
- toGroupForbiddenArea.x2
1157
- ].sort((a, b) => a - b);
1158
- // 去重
1159
- for (let x = columns.length - 1; x > 0; x--) {
1160
- if (columns[x] === columns[x - 1]) {
1161
- columns.splice(x, 1);
1162
- }
1163
- }
1164
- const rows = [
1165
- ...matrixPoints.map((o) => o.y),
1166
- // 增加行
1167
- fromGroupForbiddenArea.y1,
1168
- fromGroupForbiddenArea.y2,
1169
- toGroupForbiddenArea.y1,
1170
- toGroupForbiddenArea.y2
1171
- ].sort((a, b) => a - b);
1172
- // 去重
1173
- for (let y = rows.length - 1; y > 0; y--) {
1174
- if (rows[y] === rows[y - 1]) {
1175
- rows.splice(y, 1);
1176
- }
1177
- }
1178
- // 屏蔽区域(序号)
1179
- const columnFromStart = columns.findIndex((o) => o === fromGroupForbiddenArea.x1);
1180
- const columnFromEnd = columns.findIndex((o) => o === fromGroupForbiddenArea.x2);
1181
- const rowFromStart = rows.findIndex((o) => o === fromGroupForbiddenArea.y1);
1182
- const rowFromEnd = rows.findIndex((o) => o === fromGroupForbiddenArea.y2);
1183
- const columnToStart = columns.findIndex((o) => o === toGroupForbiddenArea.x1);
1184
- const columnToEnd = columns.findIndex((o) => o === toGroupForbiddenArea.x2);
1185
- const rowToStart = rows.findIndex((o) => o === toGroupForbiddenArea.y1);
1186
- const rowToEnd = rows.findIndex((o) => o === toGroupForbiddenArea.y2);
1187
- // 算法矩阵起点、终点
1188
- let matrixStart: Konva.Vector2d | null = null;
1189
- let matrixEnd: Konva.Vector2d | null = null;
1190
- // 算法地图矩阵
1191
- const matrix: Array<number[]> = [];
1192
- for (let y = 0; y < rows.length; y++) {
1193
- // 新增行
1194
- if (matrix[y] === void 0) {
1195
- matrix[y] = [];
1196
- }
1197
- for (let x = 0; x < columns.length; x++) {
1198
- // 不可通过区域
1199
- if (
1200
- x >= columnFromStart &&
1201
- x <= columnFromEnd &&
1202
- y >= rowFromStart &&
1203
- y <= rowFromEnd
1204
- ) {
1205
- matrix[y][x] = 2;
1206
- } else if (
1207
- x >= columnToStart &&
1208
- x <= columnToEnd &&
1209
- y >= rowToStart &&
1210
- y <= rowToEnd
1211
- ) {
1212
- matrix[y][x] = 2;
1213
- } else {
1214
- // 可通过区域
1215
- matrix[y][x] = 0;
1216
- }
1217
- // 出口、入口 -> 算法 起点、终点
1218
- if (columns[x] === fromEntry.x && rows[y] === fromEntry.y) {
1219
- matrix[y][x] = 1;
1220
- matrixStart = { x, y };
1221
- }
1222
- if (columns[x] === toEntry.x && rows[y] === toEntry.y) {
1223
- matrix[y][x] = 1;
1224
- matrixEnd = { x, y };
1225
- }
1226
- // 没有定义方向(给于十字可通过区域)
1227
- // 如,从:
1228
- // 1 1 1
1229
- // 1 0 1
1230
- // 1 1 1
1231
- // 变成:
1232
- // 1 0 1
1233
- // 0 0 0
1234
- // 1 0 1
1235
- if (!fromAnchor.attrs.direction) {
1236
- if (columns[x] === fromEntry.x || rows[y] === fromEntry.y) {
1237
- if (
1238
- x >= columnFromStart &&
1239
- x <= columnFromEnd &&
1240
- y >= rowFromStart &&
1241
- y <= rowFromEnd
1242
- ) {
1243
- matrix[y][x] = 1;
1244
- }
1245
- }
1246
- }
1247
- if (!toAnchor.attrs.direction) {
1248
- if (columns[x] === toEntry.x || rows[y] === toEntry.y) {
1249
- if (
1250
- x >= columnToStart &&
1251
- x <= columnToEnd &&
1252
- y >= rowToStart &&
1253
- y <= rowToEnd
1254
- ) {
1255
- matrix[y][x] = 1;
1256
- }
1257
- }
1258
- }
1259
- }
1260
- }
1261
-
1262
- if (matrixStart && matrixEnd) {
1263
- const way = aStar({ from: matrixStart, to: matrixEnd, matrix, maxCost: 2 });
1264
- const linkLine = new Konva.Arrow({
1265
- name: 'link-line',
1266
- // 用于删除连接线
1267
- groupId: fromGroup.id(),
1268
- pointId: fromPoint.id,
1269
- pairId: pair.id,
1270
- linkType: pair.linkType, // 记录 连接线 类型
1271
- points: [
1272
- [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)], // 补充 起点
1273
- ...way.map((o) => [this.render.toStageValue(columns[o.x]), this.render.toStageValue(rows[o.y])]),
1274
- [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)] // 补充 终点
1275
- ].flat(),
1276
- pointerAtBeginning: false,
1277
- pointerAtEnding: false
1278
- });
1279
- linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
1280
- linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
1281
- linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
1282
- linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
1283
- linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
1284
- linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
1285
- linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
1286
- if (!this.render.config.readonly) {
1287
- linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
1288
- linkLine.on('mouseenter', () => {
1289
- linkLine.opacity(0.5);
1290
- document.body.style.cursor = 'pointer';
1291
- });
1292
- linkLine.on('mouseleave', () => {
1293
- linkLine.opacity(1);
1294
- document.body.style.cursor = 'default';
1295
- });
1296
- }
1297
- this.group.add(linkLine);
1298
- }
1299
- }
1300
- }
1301
- }
1302
- }
1303
- }
1304
-
1305
- if (!this.render.config.readonly) {
1306
- // 连接点
1307
- for (const point of points) {
1308
- const group = groups.find((o) => o.id() === point.groupId);
1309
- // 非 选择中
1310
- if (group && !group.getAttr('selected')) {
1311
- const anchor = this.render.layer.findOne(`#${point.id}`);
1312
- if (anchor) {
1313
- const circle = new Konva.Circle({
1314
- name: 'link-point',
1315
- id: point.id,
1316
- groupId: group.id(),
1317
- x: this.render.toStageValue(anchor.absolutePosition().x - stageState.x),
1318
- y: this.render.toStageValue(anchor.absolutePosition().y - stageState.y),
1319
- radius: this.render.toStageValue(this.option.size),
1320
- stroke: 'rgba(255,0,0,0.2)',
1321
- strokeWidth: this.render.toStageValue(1),
1322
- // 调整中,不显示连接点
1323
- opacity: point.visible && !(this.render.draws[Draws.GraphDraw.name] as Draws.GraphDraw).state.adjusting ? 1 : 0
1324
- });
1325
- // hover 效果
1326
- circle.on('mouseenter', () => {
1327
- circle.stroke('rgba(255,0,0,0.5)');
1328
- circle.opacity(1);
1329
- document.body.style.cursor = 'pointer';
1330
- });
1331
- circle.on('mouseleave', () => {
1332
- circle.stroke('rgba(255,0,0,0.2)');
1333
- circle.opacity(0);
1334
- document.body.style.cursor = 'default';
1335
- });
1336
- circle.on('mousedown', () => {
1337
- this.render.selectionTool.selectingClear();
1338
- this.render.linkTool.selectingClear();
1339
- const pos = this.render.stage.getPointerPosition();
1340
- if (pos) {
1341
- // 临时 连接线 画
1342
- this.state.linkingLine = {
1343
- group: group,
1344
- circle: circle,
1345
- line: new Konva.Line({
1346
- name: 'linking-line',
1347
- points: [
1348
- [circle.x(), circle.y()],
1349
- [
1350
- this.render.toStageValue(pos.x - stageState.x),
1351
- this.render.toStageValue(pos.y - stageState.y)
1352
- ]
1353
- ].flat(),
1354
- stroke: 'blue',
1355
- strokeWidth: 1
1356
- })
1357
- };
1358
- this.layer.add(this.state.linkingLine.line);
1359
- }
1360
- });
1361
- circle.on('mouseup', () => {
1362
- if (this.state.linkingLine) {
1363
- const line = this.state.linkingLine;
1364
- // 不同连接点
1365
- if (line.circle.id() !== circle.id()) {
1366
- const toGroup = groups.find((o) => o.id() === circle.getAttr('groupId'));
1367
- if (toGroup) {
1368
- const fromPoint = points.find((o) => o.id === line.circle.id());
1369
- if (fromPoint) {
1370
- const toPoint = points.find((o) => o.id === line.circle.id());
1371
- if (toPoint) {
1372
- if (Array.isArray(fromPoint.pairs)) {
1373
- fromPoint.pairs = [
1374
- ...fromPoint.pairs,
1375
- {
1376
- id: nanoid(),
1377
- from: {
1378
- groupId: line.group.id(),
1379
- pointId: line.circle.id()
1380
- },
1381
- to: {
1382
- groupId: circle.getAttr('groupId'),
1383
- pointId: circle.id()
1384
- },
1385
- linkType: this.state.linkType // 记录 连接线 类型
1386
- }
1387
- ];
1388
- }
1389
- // 更新历史
1390
- this.render.updateHistory();
1391
- // 对齐线清除
1392
- this.render.attractTool.alignLinesClear();
1393
- // 重绘
1394
- this.render.redraw([
1395
- Draws.LinkDraw.name,
1396
- Draws.RulerDraw.name,
1397
- Draws.PreviewDraw.name
1398
- ]);
1399
- }
1400
- }
1401
- }
1402
- }
1403
- // 临时 连接线 移除
1404
- this.state.linkingLine?.line.destroy();
1405
- this.state.linkingLine = null;
1406
- }
1407
- });
1408
- this.group.add(circle);
1409
- }
1410
- }
1411
- }
1412
- }
1413
- }
1414
- }
1
+ import Konva from 'konva';
2
+ import { LinkType, BaseDraw, Draw, Render, LinkDrawPoint, LinkDrawPair, ManualPointsMap, ManualPoint } from '../types';
3
+ import * as Draws from '../draws';
4
+ import { nanoid } from 'nanoid';
5
+ import aStar from '../utils/a-star';
6
+ import { BezierSceneFunc } from '../utils/bezier-scene-func';
7
+
8
+ // 绘制连线
9
+
10
+ export interface LinkDrawOption {
11
+ size: number
12
+ }
13
+
14
+ // 连接线(临时)
15
+ export interface LinkDrawState {
16
+ linkingLine: {
17
+ group: Konva.Group;
18
+ circle: Konva.Circle;
19
+ line: Konva.Line
20
+ } | null;
21
+ linkType: LinkType; // 连接线类型
22
+ linkManualing: boolean // 是否 正在操作拐点
23
+ }
24
+
25
+ type Area = {
26
+ x1: number;
27
+ y1: number;
28
+ x2: number;
29
+ y2: number;
30
+ }
31
+
32
+ export class LinkDraw extends BaseDraw implements Draw {
33
+ option: LinkDrawOption;
34
+
35
+ state: LinkDrawState = {
36
+ linkingLine: null,
37
+ linkType: "curve",
38
+ linkManualing: false
39
+ }
40
+
41
+ constructor(render: Render, layer: Konva.Layer, option: LinkDrawOption) {
42
+ super(render, layer);
43
+ this.option = option;
44
+ this.group.name(this.constructor.name);
45
+ }
46
+
47
+ // 元素(连接点们)最小区域(绝对值)
48
+ getGroupLinkArea(group?: Konva.Group): Area {
49
+ let area: Area = { x1: 0, y1: 0, x2: 0, y2: 0 };
50
+ if (group) {
51
+ const stageState = this.render.getStageState(); // stage 状态
52
+ const anchors = group.find('.link-anchor');
53
+ const positions = anchors.map((o) => o.absolutePosition());
54
+ area = {
55
+ x1: Math.min(...positions.map((o) => o.x)) - stageState.x,
56
+ y1: Math.min(...positions.map((o) => o.y)) - stageState.y,
57
+ x2: Math.max(...positions.map((o) => o.x)) - stageState.x,
58
+ y2: Math.max(...positions.map((o) => o.y)) - stageState.y
59
+ };
60
+ }
61
+ return area;
62
+ }
63
+
64
+ // 连线不可通过区域
65
+ getGroupForbiddenArea(groupArea: Area, gap: number): Area {
66
+ const area: Area = {
67
+ x1: groupArea.x1 - gap,
68
+ y1: groupArea.y1 - gap,
69
+ x2: groupArea.x2 + gap,
70
+ y2: groupArea.y2 + gap
71
+ };
72
+ return area;
73
+ }
74
+
75
+ // 连线通过区域
76
+ getGroupAccessArea(groupArea: Area, gap: number): Area {
77
+ const area: Area = {
78
+ x1: groupArea.x1 - gap,
79
+ y1: groupArea.y1 - gap,
80
+ x2: groupArea.x2 + gap,
81
+ y2: groupArea.y2 + gap
82
+ };
83
+ return area;
84
+ }
85
+
86
+ // 两区域扩展
87
+ getGroupPairArea(groupArea1: Area, groupArea2: Area): Area {
88
+ const area: Area = {
89
+ x1: Math.min(groupArea1.x1, groupArea2.x1),
90
+ y1: Math.min(groupArea1.y1, groupArea2.y1),
91
+ x2: Math.max(groupArea1.x2, groupArea2.x2),
92
+ y2: Math.max(groupArea1.y2, groupArea2.y2)
93
+ };
94
+ return area;
95
+ }
96
+
97
+ // 两区域最短距离
98
+ getGroupPairDistance(groupArea1: Area, groupArea2: Area): number {
99
+ const xs = [groupArea1.x1, groupArea1.x2, groupArea2.x1, groupArea2.x2];
100
+ const maxX = Math.max(...xs);
101
+ const minX = Math.min(...xs);
102
+ const dx = maxX - minX - (groupArea1.x2 - groupArea1.x1 + (groupArea2.x2 - groupArea2.x1));
103
+ const ys = [groupArea1.y1, groupArea1.y2, groupArea2.y1, groupArea2.y2];
104
+ const maxY = Math.max(...ys);
105
+ const minY = Math.min(...ys);
106
+ const dy = maxY - minY - (groupArea1.y2 - groupArea1.y1 + (groupArea2.y2 - groupArea2.y1));
107
+ return this.render.toBoardValue(Math.min(this.render.bgSize * 0.5, Math.max(dx < 6 ? 6 : dx, dy < 6 ? 6 : dy) * 0.5));
108
+ }
109
+
110
+ // 两区域空隙中点
111
+ getGroupPairCenter(groupArea1: Area, groupArea2: Area): Konva.Vector2d {
112
+ return { x: (groupArea2.x1 + groupArea1.x2) * 0.5, y: (groupArea2.y1 + groupArea1.y2) * 0.5 };
113
+ }
114
+
115
+ // 连接出入口
116
+ getEntry(anchor: Konva.Node, groupForbiddenArea: Area, gap: number): Konva.Vector2d {
117
+ const stageState = this.render.getStageState();
118
+ const fromPos = anchor.absolutePosition();
119
+ // 默认为 起点/终点 位置(无 direction 时的值)
120
+ let x = fromPos.x - stageState.x;
121
+ let y = fromPos.y - stageState.y;
122
+ const direction = anchor.attrs.direction;
123
+ // 定义了 direction 的时候
124
+ if (direction) {
125
+ // 取整 连接点 锚点 旋转角度(保留 1 位小数点)
126
+ const rotate = Math.round(anchor.getAbsoluteRotation() * 10) / 10;
127
+ if (rotate === -45) {
128
+ if (direction === 'top') {
129
+ x = groupForbiddenArea.x1;
130
+ y = groupForbiddenArea.y1;
131
+ } else if (direction === 'bottom') {
132
+ x = groupForbiddenArea.x2;
133
+ y = groupForbiddenArea.y2;
134
+ } else if (direction === 'left') {
135
+ x = groupForbiddenArea.x1;
136
+ y = groupForbiddenArea.y2;
137
+ } else if (direction === 'right') {
138
+ x = groupForbiddenArea.x2;
139
+ y = groupForbiddenArea.y1;
140
+ }
141
+ } else if (rotate === 45) {
142
+ if (direction === 'top') {
143
+ x = groupForbiddenArea.x2;
144
+ y = groupForbiddenArea.y1;
145
+ } else if (direction === 'bottom') {
146
+ x = groupForbiddenArea.x1;
147
+ y = groupForbiddenArea.y2;
148
+ } else if (direction === 'left') {
149
+ x = groupForbiddenArea.x1;
150
+ y = groupForbiddenArea.y1;
151
+ } else if (direction === 'right') {
152
+ x = groupForbiddenArea.x2;
153
+ y = groupForbiddenArea.y2;
154
+ }
155
+ } else if (rotate === 135) {
156
+ if (direction === 'top') {
157
+ x = groupForbiddenArea.x2;
158
+ y = groupForbiddenArea.y2;
159
+ } else if (direction === 'bottom') {
160
+ x = groupForbiddenArea.x1;
161
+ y = groupForbiddenArea.y1;
162
+ } else if (direction === 'left') {
163
+ x = groupForbiddenArea.x2;
164
+ y = groupForbiddenArea.y1;
165
+ } else if (direction === 'right') {
166
+ x = groupForbiddenArea.x1;
167
+ y = groupForbiddenArea.y2;
168
+ }
169
+ } else if (rotate === -135) {
170
+ if (direction === 'top') {
171
+ x = groupForbiddenArea.x1;
172
+ y = groupForbiddenArea.y2;
173
+ } else if (direction === 'bottom') {
174
+ x = groupForbiddenArea.x2;
175
+ y = groupForbiddenArea.y1;
176
+ } else if (direction === 'left') {
177
+ x = groupForbiddenArea.x2;
178
+ y = groupForbiddenArea.y2;
179
+ } else if (direction === 'right') {
180
+ x = groupForbiddenArea.x1;
181
+ y = groupForbiddenArea.y1;
182
+ }
183
+ } else if (rotate > -45 && rotate < 45) {
184
+ const offset = gap * Math.tan((rotate * Math.PI) / 180);
185
+ if (direction === 'top') {
186
+ x = fromPos.x - stageState.x + offset;
187
+ y = groupForbiddenArea.y1;
188
+ } else if (direction === 'bottom') {
189
+ x = fromPos.x - stageState.x - offset;
190
+ y = groupForbiddenArea.y2;
191
+ } else if (direction === 'left') {
192
+ x = groupForbiddenArea.x1;
193
+ y = fromPos.y - stageState.y - offset;
194
+ } else if (direction === 'right') {
195
+ x = groupForbiddenArea.x2;
196
+ y = fromPos.y - stageState.y + offset;
197
+ }
198
+ } else if (rotate > 45 && rotate < 135) {
199
+ const offset = gap * Math.atan(((90 - rotate) * Math.PI) / 180);
200
+ if (direction === 'top') {
201
+ x = groupForbiddenArea.x2;
202
+ y = fromPos.y - stageState.y - offset;
203
+ } else if (direction === 'bottom') {
204
+ x = groupForbiddenArea.x1;
205
+ y = fromPos.y - stageState.y + offset;
206
+ } else if (direction === 'left') {
207
+ x = fromPos.x - stageState.x - offset;
208
+ y = groupForbiddenArea.y1;
209
+ } else if (direction === 'right') {
210
+ x = fromPos.x - stageState.x + offset;
211
+ y = groupForbiddenArea.y2;
212
+ }
213
+ } else if ((rotate > 135 && rotate <= 180) || (rotate >= -180 && rotate < -135)) {
214
+ const offset = gap * Math.tan((rotate * Math.PI) / 180);
215
+ if (direction === 'top') {
216
+ x = fromPos.x - stageState.x - offset;
217
+ y = groupForbiddenArea.y2;
218
+ } else if (direction === 'bottom') {
219
+ x = fromPos.x - stageState.x + offset;
220
+ y = groupForbiddenArea.y1;
221
+ } else if (direction === 'left') {
222
+ x = groupForbiddenArea.x2;
223
+ y = fromPos.y - stageState.y + offset;
224
+ } else if (direction === 'right') {
225
+ x = groupForbiddenArea.x1;
226
+ y = fromPos.y - stageState.y - offset;
227
+ }
228
+ } else if (rotate > -135 && rotate < -45) {
229
+ const offset = gap * Math.atan(((90 + rotate) * Math.PI) / 180);
230
+ if (direction === 'top') {
231
+ x = groupForbiddenArea.x1;
232
+ y = fromPos.y - stageState.y - offset;
233
+ } else if (direction === 'bottom') {
234
+ x = groupForbiddenArea.x2;
235
+ y = fromPos.y - stageState.y + offset;
236
+ } else if (direction === 'left') {
237
+ x = fromPos.x - stageState.x - offset;
238
+ y = groupForbiddenArea.y2;
239
+ } else if (direction === 'right') {
240
+ x = fromPos.x - stageState.x + offset;
241
+ y = groupForbiddenArea.y1;
242
+ }
243
+ }
244
+ }
245
+ return { x, y } as Konva.Vector2d;
246
+ }
247
+
248
+ // 连接点信息
249
+ getAnchorPos(anchor?: Konva.Node): Konva.Vector2d {
250
+ const stageState = this.render.getStageState();
251
+ return anchor ? { x: anchor.absolutePosition().x - stageState.x, y: anchor.absolutePosition().y - stageState.y } : { x: 0, y: 0 };
252
+ }
253
+
254
+ /**
255
+ * 修改当前连接线类型
256
+ * @param linkType LinkType
257
+ */
258
+ changeLinkType(linkType: LinkType) {
259
+ this.state.linkType = linkType;
260
+ this.render.emit('link-type-change', this.state.linkType);
261
+ }
262
+
263
+ override draw() {
264
+ this.clear();
265
+ const stageState = this.render.getStageState();
266
+
267
+ // 所有层级的素材
268
+ const groups = [
269
+ ...(this.render.layer.find('.asset') as Konva.Group[]),
270
+ ...(this.render.layer.find('.sub-asset') as Konva.Group[])
271
+ ];
272
+ const points = groups.reduce((ps, group) => ps.concat(Array.isArray(group.getAttr('points')) ? group.getAttr('points') : []), [] as LinkDrawPoint[]);
273
+ const pairs = points.reduce((ps, point) => ps.concat(point.pairs ? point.pairs.filter((o) => !o.disabled) : []), [] as LinkDrawPair[]);
274
+
275
+ // 连接线
276
+ for (const pair of pairs) {
277
+ // 多层素材,需要排除内部 pair 对
278
+ // pair 也不能为 disabled
279
+ if (pair.from.groupId !== pair.to.groupId && !pair.disabled) {
280
+ const fromGroup = groups.find((o) => o.id() === pair.from.groupId);
281
+ const fromPoint = points.find((o) => o.id === pair.from.pointId);
282
+ const toGroup = groups.find((o) => o.id() === pair.to.groupId);
283
+ const toPoint = points.find((o) => o.id === pair.to.pointId);
284
+ if (pair.linkType === "manual") {
285
+ // 折线
286
+ if (fromGroup && toGroup && fromPoint && toPoint) {
287
+ const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
288
+ const toAnchor = toGroup.findOne(`#${toPoint.id}`);
289
+ // 锚点信息
290
+ const fromAnchorPos = this.getAnchorPos(fromAnchor);
291
+ const toAnchorPos = this.getAnchorPos(toAnchor);
292
+ // 拐点(已拐)记录
293
+ const manualPointsMap: ManualPointsMap = fromGroup.getAttr('manualPointsMap') ?? ({} as ManualPointsMap);
294
+ const manualPoints = manualPointsMap[pair.id] ?? ([] as ManualPoint[]);
295
+ // 连接点 + 拐点
296
+ const linkPoints = [
297
+ [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)],
298
+ ...manualPoints.map((o) => [o.x, o.y]),
299
+ [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
300
+ ];
301
+
302
+ // 连接线
303
+ const linkLine = new Konva.Arrow({
304
+ name: 'link-line',
305
+ // 用于删除连接线
306
+ groupId: fromGroup.id(),
307
+ pointId: fromPoint.id,
308
+ pairId: pair.id,
309
+ linkType: pair.linkType,
310
+ points: linkPoints.flat(),
311
+ pointerAtBeginning: false,
312
+ pointerAtEnding: false
313
+ });
314
+ linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
315
+ linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
316
+ linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
317
+ linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
318
+ linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
319
+ linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
320
+ linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
321
+ if (!this.render.config.readonly) {
322
+ linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
323
+ linkLine.on('mouseenter', () => {
324
+ linkLine.opacity(0.5);
325
+ document.body.style.cursor = 'pointer';
326
+ });
327
+ linkLine.on('mouseleave', () => {
328
+ linkLine.opacity(1);
329
+ document.body.style.cursor = 'default';
330
+ });
331
+ }
332
+ this.group.add(linkLine);
333
+ if (!this.render.config.readonly) {
334
+ // 正在拖动效果
335
+ const manualingLine = new Konva.Line({
336
+ name: 'manualing-line',
337
+ stroke: '#ff0000',
338
+ strokeWidth: 2,
339
+ points: [],
340
+ dash: [4, 4]
341
+ });
342
+ this.group.add(manualingLine);
343
+ for (let i = 0; i < linkPoints.length - 1; i++) {
344
+ const circle = new Konva.Circle({
345
+ name: 'link-manual-point',
346
+ id: nanoid(),
347
+ pairId: pair.id,
348
+ x: (linkPoints[i][0] + linkPoints[i + 1][0]) / 2,
349
+ y: (linkPoints[i][1] + linkPoints[i + 1][1]) / 2,
350
+ radius: this.render.toStageValue(this.render.bgSize / 2),
351
+ stroke: 'rgba(0,0,255,0.2)',
352
+ strokeWidth: this.render.toStageValue(1),
353
+ linkManualIndex: i // 当前拐点位置
354
+ });
355
+ // hover 效果
356
+ circle.on('mouseenter', () => {
357
+ circle.stroke('rgba(0,0,255,0.8)');
358
+ document.body.style.cursor = 'pointer';
359
+ });
360
+ circle.on('mouseleave', () => {
361
+ if (!circle.attrs.dragStart) {
362
+ circle.stroke('rgba(0,0,255,0.1)');
363
+ document.body.style.cursor = 'default';
364
+ }
365
+ });
366
+
367
+ // 拐点操作
368
+ circle.on('mousedown', () => {
369
+ const pos = circle.getAbsolutePosition();
370
+ // 记录操作开始状态
371
+ circle.setAttrs({
372
+ // 开始坐标
373
+ dragStartX: pos.x,
374
+ dragStartY: pos.y,
375
+ // 正在操作
376
+ dragStart: true
377
+ });
378
+ // 标记状态 - 正在操作拐点
379
+ this.state.linkManualing = true;
380
+ });
381
+ this.render.stage.on('mousemove', () => {
382
+ if (circle.attrs.dragStart) {
383
+ // 正在操作
384
+ const pos = this.render.stage.getPointerPosition();
385
+ if (pos) {
386
+ // 磁贴
387
+ const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
388
+ // 移动拐点
389
+ circle.setAbsolutePosition(transformerPos);
390
+ // 正在拖动效果
391
+ const tempPoints = [...linkPoints];
392
+ tempPoints.splice(circle.attrs.linkManualIndex + 1, 0, [
393
+ this.render.toStageValue(transformerPos.x - stageState.x),
394
+ this.render.toStageValue(transformerPos.y - stageState.y)
395
+ ]);
396
+ manualingLine.points(tempPoints.flat());
397
+ }
398
+ }
399
+ });
400
+ circle.on('mouseup', () => {
401
+ const pos = circle.getAbsolutePosition();
402
+ if (
403
+ Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
404
+ Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
405
+ ) {
406
+ // 操作移动距离达到阈值
407
+ const stageState = this.render.getStageState();
408
+ // 记录(插入)拐点
409
+ manualPoints.splice(circle.attrs.linkManualIndex, 0, {
410
+ x: this.render.toStageValue(pos.x - stageState.x),
411
+ y: this.render.toStageValue(pos.y - stageState.y)
412
+ });
413
+ manualPointsMap[pair.id] = manualPoints;
414
+ fromGroup.setAttr('manualPointsMap', manualPointsMap);
415
+ }
416
+
417
+ // 操作结束
418
+ circle.setAttrs({ dragStart: false });
419
+
420
+ // state 操作结束
421
+ this.state.linkManualing = false;
422
+
423
+ // 销毁
424
+ circle.destroy();
425
+ manualingLine.destroy();
426
+
427
+ // 更新历史
428
+ this.render.updateHistory();
429
+
430
+ // 对齐线清除
431
+ this.render.attractTool.alignLinesClear();
432
+
433
+ // 重绘
434
+ this.render.redraw([
435
+ Draws.LinkDraw.name,
436
+ Draws.RulerDraw.name,
437
+ Draws.PreviewDraw.name
438
+ ]);
439
+ })
440
+
441
+ this.group.add(circle);
442
+ }
443
+
444
+ // 拐点(已拐)
445
+ for (let i = 1; i < linkPoints.length - 1; i++) {
446
+ const circle = new Konva.Circle({
447
+ name: 'link-manual-point',
448
+ id: nanoid(),
449
+ pairId: pair.id,
450
+ x: linkPoints[i][0],
451
+ y: linkPoints[i][1],
452
+ radius: this.render.toStageValue(this.render.bgSize / 2),
453
+ stroke: 'rgba(0,100,0,0.2)',
454
+ strokeWidth: this.render.toStageValue(1),
455
+ linkManualIndex: i // 当前拐点位置
456
+ });
457
+
458
+ // hover 效果
459
+ circle.on('mouseenter', () => {
460
+ circle.stroke('rgba(0,100,0,1)');
461
+ document.body.style.cursor = 'pointer';
462
+ });
463
+ circle.on('mouseleave', () => {
464
+ if (!circle.attrs.dragStart) {
465
+ circle.stroke('rgba(0,100,0,0.1)');
466
+ document.body.style.cursor = 'default';
467
+ }
468
+ });
469
+
470
+ // 拐点操作
471
+ circle.on('mousedown', () => {
472
+ const pos = circle.getAbsolutePosition();
473
+ // 记录操作开始状态
474
+ circle.setAttrs({
475
+ dragStartX: pos.x,
476
+ dragStartY: pos.y,
477
+ dragStart: true
478
+ });
479
+
480
+ // 标记状态 - 正在操作拐点
481
+ this.state.linkManualing = true;
482
+ })
483
+ this.render.stage.on('mousemove', () => {
484
+ if (circle.attrs.dragStart) {
485
+ // 正在操作
486
+ const pos = this.render.stage.getPointerPosition();
487
+ if (pos) {
488
+ // 磁贴
489
+ const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
490
+ // 移动拐点
491
+ circle.setAbsolutePosition(transformerPos);
492
+ // 正在拖动效果
493
+ const tempPoints = [...linkPoints];
494
+ tempPoints[circle.attrs.linkManualIndex] = [
495
+ this.render.toStageValue(transformerPos.x - stageState.x),
496
+ this.render.toStageValue(transformerPos.y - stageState.y)
497
+ ];
498
+ manualingLine.points(tempPoints.flat());
499
+ }
500
+ }
501
+ });
502
+ circle.on('mouseup', () => {
503
+ const pos = circle.getAbsolutePosition();
504
+ if (
505
+ Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
506
+ Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
507
+ ) {
508
+ // 操作移动距离达到阈值
509
+ const stageState = this.render.getStageState();
510
+ // 记录(更新)拐点
511
+ manualPoints[circle.attrs.linkManualIndex - 1] = {
512
+ x: this.render.toStageValue(pos.x - stageState.x),
513
+ y: this.render.toStageValue(pos.y - stageState.y)
514
+ };
515
+ manualPointsMap[pair.id] = manualPoints;
516
+ fromGroup.setAttr('manualPointsMap', manualPointsMap);
517
+ }
518
+ // 操作结束
519
+ circle.setAttrs({ dragStart: false });
520
+ // state 操作结束
521
+ this.state.linkManualing = false;
522
+ // 销毁
523
+ circle.destroy();
524
+ manualingLine.destroy();
525
+ // 更新历史
526
+ this.render.updateHistory();
527
+ // 对齐线清除
528
+ this.render.attractTool.alignLinesClear();
529
+ // 重绘
530
+ this.render.redraw([
531
+ Draws.LinkDraw.name,
532
+ Draws.RulerDraw.name,
533
+ Draws.PreviewDraw.name
534
+ ]);
535
+ })
536
+ this.group.add(circle);
537
+ }
538
+ }
539
+ }
540
+ } else if (pair.linkType === "curve") {
541
+ // 曲线
542
+ if (fromGroup && toGroup && fromPoint && toPoint) {
543
+ const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
544
+ const toAnchor = toGroup.findOne(`#${toPoint.id}`);
545
+ // 锚点信息
546
+ const fromAnchorPos = this.getAnchorPos(fromAnchor);
547
+ const toAnchorPos = this.getAnchorPos(toAnchor);
548
+ // 拐点(已拐)记录
549
+ const manualPointsMap: ManualPointsMap = fromGroup.getAttr('manualPointsMap') ?? ({} as ManualPointsMap);
550
+ const manualPoints = manualPointsMap[pair.id] ?? ([] as ManualPoint[]);
551
+ // 连接点 + 拐点
552
+ const linkPoints = [
553
+ [
554
+ this.render.toStageValue(fromAnchorPos.x),
555
+ this.render.toStageValue(fromAnchorPos.y)
556
+ ],
557
+ ...manualPoints.map((o) => [o.x, o.y]),
558
+ [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
559
+ ];
560
+
561
+ // 连接线
562
+ const linkLine = new Konva.Arrow({
563
+ name: 'link-line',
564
+ // 用于删除连接线
565
+ groupId: fromGroup.id(),
566
+ pointId: fromPoint.id,
567
+ pairId: pair.id,
568
+ linkType: pair.linkType,
569
+ points: linkPoints.flat(),
570
+ pointerAtBeginning: false,
571
+ pointerAtEnding: false
572
+ });
573
+ linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
574
+ linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
575
+ linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
576
+ linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
577
+ linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
578
+ linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
579
+ linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
580
+ linkLine.tension(this.render.getLinkSettings(linkLine).tension || 0);
581
+ if (!this.render.config.readonly) {
582
+ linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
583
+ linkLine.on('mouseenter', () => {
584
+ linkLine.opacity(0.5);
585
+ document.body.style.cursor = 'pointer';
586
+ });
587
+ linkLine.on('mouseleave', () => {
588
+ linkLine.opacity(1);
589
+ document.body.style.cursor = 'default';
590
+ });
591
+ }
592
+ this.group.add(linkLine);
593
+ if (!this.render.config.readonly) {
594
+ // 正在拖动效果
595
+ const manualingLine = new Konva.Arrow({
596
+ name: 'manualing-line',
597
+ stroke: '#ff0000',
598
+ strokeWidth: 2,
599
+ points: [],
600
+ dash: [4, 4],
601
+ pointerAtBeginning: false,
602
+ pointerAtEnding: false,
603
+ tension: this.render.getLinkSettings(linkLine).tension
604
+ });
605
+ this.group.add(manualingLine);
606
+ // 拐点(待拐)
607
+ for (let i = 0; i < linkPoints.length - 1; i++) {
608
+ const circle = new Konva.Circle({
609
+ name: 'link-manual-point',
610
+ id: nanoid(),
611
+ pairId: pair.id,
612
+ x: (linkPoints[i][0] + linkPoints[i + 1][0]) / 2,
613
+ y: (linkPoints[i][1] + linkPoints[i + 1][1]) / 2,
614
+ radius: this.render.toStageValue(this.render.bgSize / 2),
615
+ stroke: 'rgba(0,0,255,0.2)',
616
+ strokeWidth: this.render.toStageValue(1),
617
+ linkManualIndex: i // 当前拐点位置
618
+ });
619
+ // hover 效果
620
+ circle.on('mouseenter', () => {
621
+ circle.stroke('rgba(0,0,255,0.8)');
622
+ document.body.style.cursor = 'pointer';
623
+ });
624
+ circle.on('mouseleave', () => {
625
+ if (!circle.attrs.dragStart) {
626
+ circle.stroke('rgba(0,0,255,0.1)');
627
+ document.body.style.cursor = 'default';
628
+ }
629
+ });
630
+ // 拐点操作
631
+ circle.on('mousedown', () => {
632
+ const pos = circle.getAbsolutePosition();
633
+ // 记录操作开始状态
634
+ circle.setAttrs({
635
+ // 开始坐标
636
+ dragStartX: pos.x,
637
+ dragStartY: pos.y,
638
+ // 正在操作
639
+ dragStart: true
640
+ });
641
+ // 标记状态 - 正在操作拐点
642
+ this.state.linkManualing = true;
643
+ });
644
+ this.render.stage.on('mousemove', () => {
645
+ if (circle.attrs.dragStart) {
646
+ // 正在操作
647
+ const pos = this.render.stage.getPointerPosition();
648
+ if (pos) {
649
+ // 磁贴
650
+ const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
651
+ // 移动拐点
652
+ circle.setAbsolutePosition(transformerPos);
653
+ // 正在拖动效果
654
+ const tempPoints = [...linkPoints];
655
+ tempPoints.splice(circle.attrs.linkManualIndex + 1, 0, [
656
+ this.render.toStageValue(transformerPos.x - stageState.x),
657
+ this.render.toStageValue(transformerPos.y - stageState.y)
658
+ ]);
659
+ manualingLine.points(tempPoints.flat());
660
+ }
661
+ }
662
+ });
663
+ circle.on('mouseup', () => {
664
+ const pos = circle.getAbsolutePosition();
665
+ if (
666
+ Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
667
+ Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
668
+ ) {
669
+ // 操作移动距离达到阈值
670
+ const stageState = this.render.getStageState();
671
+ // 记录(插入)拐点
672
+ manualPoints.splice(circle.attrs.linkManualIndex, 0, {
673
+ x: this.render.toStageValue(pos.x - stageState.x),
674
+ y: this.render.toStageValue(pos.y - stageState.y)
675
+ });
676
+ manualPointsMap[pair.id] = manualPoints;
677
+ fromGroup.setAttr('manualPointsMap', manualPointsMap);
678
+ }
679
+ // 操作结束
680
+ circle.setAttrs({ dragStart: false });
681
+ // state 操作结束
682
+ this.state.linkManualing = false;
683
+ // 销毁
684
+ circle.destroy();
685
+ manualingLine.destroy();
686
+ // 更新历史
687
+ this.render.updateHistory();
688
+ // 对齐线清除
689
+ this.render.attractTool.alignLinesClear();
690
+ // 重绘
691
+ this.render.redraw([
692
+ Draws.LinkDraw.name,
693
+ Draws.RulerDraw.name,
694
+ Draws.PreviewDraw.name
695
+ ]);
696
+ });
697
+ this.group.add(circle);
698
+ }
699
+ // 拐点(已拐)
700
+ for (let i = 1; i < linkPoints.length - 1; i++) {
701
+ const circle = new Konva.Circle({
702
+ name: 'link-manual-point',
703
+ id: nanoid(),
704
+ pairId: pair.id,
705
+ x: linkPoints[i][0],
706
+ y: linkPoints[i][1],
707
+ radius: this.render.toStageValue(this.render.bgSize / 2),
708
+ stroke: 'rgba(0,100,0,0.2)',
709
+ strokeWidth: this.render.toStageValue(1),
710
+ linkManualIndex: i // 当前拐点位置
711
+ });
712
+ // hover 效果
713
+ circle.on('mouseenter', () => {
714
+ circle.stroke('rgba(0,100,0,1)');
715
+ document.body.style.cursor = 'pointer';
716
+ });
717
+ circle.on('mouseleave', () => {
718
+ if (!circle.attrs.dragStart) {
719
+ circle.stroke('rgba(0,100,0,0.1)');
720
+ document.body.style.cursor = 'default';
721
+ }
722
+ });
723
+ // 拐点操作
724
+ circle.on('mousedown', () => {
725
+ const pos = circle.getAbsolutePosition();
726
+ // 记录操作开始状态
727
+ circle.setAttrs({
728
+ dragStartX: pos.x,
729
+ dragStartY: pos.y,
730
+ dragStart: true
731
+ });
732
+ // 标记状态 - 正在操作拐点
733
+ this.state.linkManualing = true;
734
+ });
735
+ this.render.stage.on('mousemove', () => {
736
+ if (circle.attrs.dragStart) {
737
+ // 正在操作
738
+ const pos = this.render.stage.getPointerPosition();
739
+ if (pos) {
740
+ // 磁贴
741
+ const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
742
+ // 移动拐点
743
+ circle.setAbsolutePosition(transformerPos);
744
+ // 正在拖动效果
745
+ const tempPoints = [...linkPoints];
746
+ tempPoints[circle.attrs.linkManualIndex] = [
747
+ this.render.toStageValue(transformerPos.x - stageState.x),
748
+ this.render.toStageValue(transformerPos.y - stageState.y)
749
+ ];
750
+ manualingLine.points(tempPoints.flat());
751
+ }
752
+ }
753
+ });
754
+ circle.on('mouseup', () => {
755
+ const pos = circle.getAbsolutePosition();
756
+ if (
757
+ Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
758
+ Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
759
+ ) {
760
+ // 操作移动距离达到阈值
761
+ const stageState = this.render.getStageState();
762
+ // 记录(更新)拐点
763
+ manualPoints[circle.attrs.linkManualIndex - 1] = {
764
+ x: this.render.toStageValue(pos.x - stageState.x),
765
+ y: this.render.toStageValue(pos.y - stageState.y)
766
+ };
767
+ manualPointsMap[pair.id] = manualPoints;
768
+ fromGroup.setAttr('manualPointsMap', manualPointsMap);
769
+ }
770
+ // 操作结束
771
+ circle.setAttrs({ dragStart: false });
772
+ // state 操作结束
773
+ this.state.linkManualing = false;
774
+ // 销毁
775
+ circle.destroy();
776
+ manualingLine.destroy();
777
+ // 更新历史
778
+ this.render.updateHistory();
779
+ // 对齐线清除
780
+ this.render.attractTool.alignLinesClear();
781
+ // 重绘
782
+ this.render.redraw([
783
+ Draws.LinkDraw.name,
784
+ Draws.RulerDraw.name,
785
+ Draws.PreviewDraw.name
786
+ ]);
787
+ })
788
+ this.group.add(circle);
789
+ }
790
+ }
791
+ }
792
+ } else if (pair.linkType === "bezier") {
793
+ if (fromGroup && toGroup && fromPoint && toPoint) {
794
+ const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
795
+ const toAnchor = toGroup.findOne(`#${toPoint.id}`);
796
+ // 锚点信息
797
+ const fromAnchorPos = this.getAnchorPos(fromAnchor);
798
+ const toAnchorPos = this.getAnchorPos(toAnchor);
799
+ // 拐点(已拐)记录
800
+ const manualPointsMap: ManualPointsMap = fromGroup.getAttr('manualPointsMap') ?? ({} as ManualPointsMap);
801
+ const manualPoints = manualPointsMap[pair.id] ?? ([] as ManualPoint[]);
802
+ // 连接点 + 拐点
803
+ const linkPoints = [
804
+ [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)],
805
+ ...manualPoints.map((o) => [o.x, o.y]),
806
+ [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
807
+ ];
808
+ // 连接线
809
+ const linkLine = new Konva.Arrow({
810
+ name: 'link-line',
811
+ // 用于删除连接线
812
+ groupId: fromGroup.id(),
813
+ pointId: fromPoint.id,
814
+ pairId: pair.id,
815
+ linkType: pair.linkType,
816
+ points: linkPoints.flat(),
817
+ pointerAtBeginning: false,
818
+ pointerAtEnding: false,
819
+ sceneFunc: BezierSceneFunc
820
+ });
821
+ linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
822
+ linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
823
+ linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
824
+ linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
825
+ linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
826
+ linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
827
+ linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
828
+ if (!this.render.config.readonly) {
829
+ linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
830
+ linkLine.on('mouseenter', () => {
831
+ linkLine.opacity(0.5);
832
+ document.body.style.cursor = 'pointer';
833
+ });
834
+ linkLine.on('mouseleave', () => {
835
+ linkLine.opacity(1);
836
+ document.body.style.cursor = 'default';
837
+ });
838
+ }
839
+ this.group.add(linkLine);
840
+ if (!this.render.config.readonly) {
841
+ // 正在拖动效果
842
+ const manualingLine = new Konva.Arrow({
843
+ name: 'manualing-line',
844
+ stroke: '#ff0000',
845
+ strokeWidth: 2,
846
+ points: [],
847
+ dash: [4, 4],
848
+ pointerAtBeginning: false,
849
+ pointerAtEnding: false,
850
+ sceneFunc: BezierSceneFunc
851
+ });
852
+ this.group.add(manualingLine);
853
+ // 拐点
854
+ if (linkPoints.length <= 3) {
855
+ // 拐点(待拐)
856
+ for (let i = 0; i < linkPoints.length - 1; i++) {
857
+ const circle = new Konva.Circle({
858
+ name: 'link-manual-point',
859
+ id: nanoid(),
860
+ pairId: pair.id,
861
+ x: (linkPoints[i][0] + linkPoints[i + 1][0]) / 2,
862
+ y: (linkPoints[i][1] + linkPoints[i + 1][1]) / 2,
863
+ radius: this.render.toStageValue(this.render.bgSize / 2),
864
+ stroke: 'rgba(0,0,255,0.2)',
865
+ strokeWidth: this.render.toStageValue(1),
866
+ linkManualIndex: i // 当前拐点位置
867
+ });
868
+ // hover 效果
869
+ circle.on('mouseenter', () => {
870
+ circle.stroke('rgba(0,0,255,0.8)');
871
+ document.body.style.cursor = 'pointer';
872
+ });
873
+ circle.on('mouseleave', () => {
874
+ if (!circle.attrs.dragStart) {
875
+ circle.stroke('rgba(0,0,255,0.1)');
876
+ document.body.style.cursor = 'default';
877
+ }
878
+ });
879
+ // 拐点操作
880
+ circle.on('mousedown', () => {
881
+ const pos = circle.getAbsolutePosition();
882
+ // 记录操作开始状态
883
+ circle.setAttrs({
884
+ // 开始坐标
885
+ dragStartX: pos.x,
886
+ dragStartY: pos.y,
887
+ // 正在操作
888
+ dragStart: true
889
+ });
890
+ // 标记状态 - 正在操作拐点
891
+ this.state.linkManualing = true;
892
+ });
893
+ this.render.stage.on('mousemove', () => {
894
+ if (circle.attrs.dragStart) {
895
+ // 正在操作
896
+ const pos = this.render.stage.getPointerPosition();
897
+ if (pos) {
898
+ // 磁贴
899
+ const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
900
+ // 移动拐点
901
+ circle.setAbsolutePosition(transformerPos);
902
+ // 正在拖动效果
903
+ const tempPoints = [...linkPoints];
904
+ tempPoints.splice(circle.attrs.linkManualIndex + 1, 0, [
905
+ this.render.toStageValue(transformerPos.x - stageState.x),
906
+ this.render.toStageValue(transformerPos.y - stageState.y)
907
+ ]);
908
+ manualingLine.points(tempPoints.flat());
909
+ }
910
+ }
911
+ });
912
+ circle.on('mouseup', () => {
913
+ const pos = circle.getAbsolutePosition();
914
+ if (
915
+ Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
916
+ Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
917
+ ) {
918
+ // 操作移动距离达到阈值
919
+ const stageState = this.render.getStageState();
920
+ // 记录(插入)拐点
921
+ manualPoints.splice(circle.attrs.linkManualIndex, 0, {
922
+ x: this.render.toStageValue(pos.x - stageState.x),
923
+ y: this.render.toStageValue(pos.y - stageState.y)
924
+ });
925
+ manualPointsMap[pair.id] = manualPoints;
926
+ fromGroup.setAttr('manualPointsMap', manualPointsMap);
927
+ }
928
+ // 操作结束
929
+ circle.setAttrs({ dragStart: false });
930
+ // state 操作结束
931
+ this.state.linkManualing = false;
932
+ // 销毁
933
+ circle.destroy();
934
+ manualingLine.destroy();
935
+ // 更新历史
936
+ this.render.updateHistory();
937
+ // 对齐线清除
938
+ this.render.attractTool.alignLinesClear();
939
+ // 重绘
940
+ this.render.redraw([
941
+ Draws.LinkDraw.name,
942
+ Draws.RulerDraw.name,
943
+ Draws.PreviewDraw.name
944
+ ]);
945
+ });
946
+ this.group.add(circle);
947
+ }
948
+ }
949
+ // 拐点(已拐)
950
+ for (let i = 1; i < linkPoints.length - 1; i++) {
951
+ const circle = new Konva.Circle({
952
+ name: 'link-manual-point',
953
+ id: nanoid(),
954
+ pairId: pair.id,
955
+ x: linkPoints[i][0],
956
+ y: linkPoints[i][1],
957
+ radius: this.render.toStageValue(this.render.bgSize / 2),
958
+ stroke: 'rgba(0,100,0,0.2)',
959
+ strokeWidth: this.render.toStageValue(1),
960
+ linkManualIndex: i // 当前拐点位置
961
+ });
962
+ // hover 效果
963
+ circle.on('mouseenter', () => {
964
+ circle.stroke('rgba(0,100,0,1)');
965
+ document.body.style.cursor = 'pointer';
966
+ });
967
+ circle.on('mouseleave', () => {
968
+ if (!circle.attrs.dragStart) {
969
+ circle.stroke('rgba(0,100,0,0.1)');
970
+ document.body.style.cursor = 'default';
971
+ }
972
+ });
973
+ // 拐点操作
974
+ circle.on('mousedown', () => {
975
+ const pos = circle.getAbsolutePosition();
976
+ // 记录操作开始状态
977
+ circle.setAttrs({
978
+ dragStartX: pos.x,
979
+ dragStartY: pos.y,
980
+ dragStart: true
981
+ });
982
+ // 标记状态 - 正在操作拐点
983
+ this.state.linkManualing = true;
984
+ });
985
+ this.render.stage.on('mousemove', () => {
986
+ if (circle.attrs.dragStart) {
987
+ // 正在操作
988
+ const pos = this.render.stage.getPointerPosition();
989
+ if (pos) {
990
+ // 磁贴
991
+ const { pos: transformerPos } = this.render.attractTool.attractPoint(pos);
992
+ // 移动拐点
993
+ circle.setAbsolutePosition(transformerPos);
994
+ // 正在拖动效果
995
+ const tempPoints = [...linkPoints]
996
+ tempPoints[circle.attrs.linkManualIndex] = [
997
+ this.render.toStageValue(transformerPos.x - stageState.x),
998
+ this.render.toStageValue(transformerPos.y - stageState.y)
999
+ ];
1000
+ manualingLine.points(tempPoints.flat());
1001
+ }
1002
+ }
1003
+ });
1004
+ circle.on('mouseup', () => {
1005
+ const pos = circle.getAbsolutePosition();
1006
+ if (
1007
+ Math.abs(pos.x - circle.attrs.dragStartX) > this.option.size ||
1008
+ Math.abs(pos.y - circle.attrs.dragStartY) > this.option.size
1009
+ ) {
1010
+ // 操作移动距离达到阈值
1011
+ const stageState = this.render.getStageState();
1012
+ // 记录(更新)拐点
1013
+ manualPoints[circle.attrs.linkManualIndex - 1] = {
1014
+ x: this.render.toStageValue(pos.x - stageState.x),
1015
+ y: this.render.toStageValue(pos.y - stageState.y)
1016
+ };
1017
+ manualPointsMap[pair.id] = manualPoints;
1018
+ fromGroup.setAttr('manualPointsMap', manualPointsMap);
1019
+ }
1020
+ // 操作结束
1021
+ circle.setAttrs({ dragStart: false });
1022
+ // state 操作结束
1023
+ this.state.linkManualing = false;
1024
+ // 销毁
1025
+ circle.destroy();
1026
+ manualingLine.destroy();
1027
+ // 更新历史
1028
+ this.render.updateHistory();
1029
+ // 对齐线清除
1030
+ this.render.attractTool.alignLinesClear();
1031
+ // 重绘
1032
+ this.render.redraw([
1033
+ Draws.LinkDraw.name,
1034
+ Draws.RulerDraw.name,
1035
+ Draws.PreviewDraw.name
1036
+ ]);
1037
+ });
1038
+ this.group.add(circle);
1039
+ }
1040
+ }
1041
+ }
1042
+ } else if (pair.linkType === "straight") {
1043
+ // 直线
1044
+ if (fromGroup && toGroup && fromPoint && toPoint) {
1045
+ const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
1046
+ const toAnchor = toGroup.findOne(`#${toPoint.id}`);
1047
+ // 锚点信息
1048
+ const fromAnchorPos = this.getAnchorPos(fromAnchor);
1049
+ const toAnchorPos = this.getAnchorPos(toAnchor);
1050
+ const linkLine = new Konva.Arrow({
1051
+ name: 'link-line',
1052
+ // 用于删除连接线
1053
+ groupId: fromGroup.id(),
1054
+ pointId: fromPoint.id,
1055
+ pairId: pair.id,
1056
+ linkType: pair.linkType,
1057
+ points: [
1058
+ [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)],
1059
+ [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)]
1060
+ ].flat(),
1061
+ pointerAtBeginning: false,
1062
+ pointerAtEnding: false
1063
+ });
1064
+ linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
1065
+ linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
1066
+ linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
1067
+ linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
1068
+ linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
1069
+ linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
1070
+ linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
1071
+
1072
+ if (!this.render.config.readonly) {
1073
+ linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
1074
+ linkLine.on('mouseenter', () => {
1075
+ linkLine.opacity(0.5);
1076
+ document.body.style.cursor = 'pointer';
1077
+ });
1078
+ linkLine.on('mouseleave', () => {
1079
+ linkLine.opacity(1);
1080
+ document.body.style.cursor = 'default';
1081
+ });
1082
+ }
1083
+ this.group.add(linkLine);
1084
+ }
1085
+ } else {
1086
+ // 自动
1087
+ // 最小区域
1088
+ const fromGroupLinkArea = this.getGroupLinkArea(fromGroup);
1089
+ const toGroupLinkArea = this.getGroupLinkArea(toGroup);
1090
+ // 两区域的最短距离
1091
+ const groupDistance = this.getGroupPairDistance(fromGroupLinkArea, toGroupLinkArea);
1092
+ // 不可通过区域
1093
+ const fromGroupForbiddenArea = this.getGroupForbiddenArea(fromGroupLinkArea, groupDistance - 2);
1094
+ const toGroupForbiddenArea = this.getGroupForbiddenArea(toGroupLinkArea, groupDistance - 2);
1095
+ // 两区域扩展
1096
+ const groupForbiddenArea = this.getGroupPairArea(fromGroupForbiddenArea, toGroupForbiddenArea);
1097
+ // 连线通过区域
1098
+ const groupAccessArea = this.getGroupPairArea(this.getGroupAccessArea(fromGroupForbiddenArea, groupDistance), this.getGroupAccessArea(toGroupForbiddenArea, groupDistance));
1099
+ if (fromGroup && toGroup && fromPoint && toPoint) {
1100
+ const fromAnchor = fromGroup.findOne(`#${fromPoint.id}`);
1101
+ const toAnchor = toGroup.findOne(`#${toPoint.id}`);
1102
+ // 锚点信息
1103
+ const fromAnchorPos = this.getAnchorPos(fromAnchor);
1104
+ const toAnchorPos = this.getAnchorPos(toAnchor);
1105
+ if (fromAnchor && toAnchor) {
1106
+ // 连接出入口
1107
+ const fromEntry: Konva.Vector2d = this.getEntry(fromAnchor, fromGroupForbiddenArea, groupDistance);
1108
+ const toEntry: Konva.Vector2d = this.getEntry(toAnchor, toGroupForbiddenArea, groupDistance);
1109
+ type matrixPoint = { x: number, y: number, type?: 'from' | 'to' | 'from-entry' | 'to-entry' };
1110
+ // 可能点
1111
+ let matrixPoints: matrixPoint[] = [];
1112
+ // 通过区域 四角
1113
+ matrixPoints.push({ x: groupAccessArea.x1, y: groupAccessArea.y1 });
1114
+ matrixPoints.push({ x: groupAccessArea.x2, y: groupAccessArea.y2 });
1115
+ matrixPoints.push({ x: groupAccessArea.x1, y: groupAccessArea.y2 });
1116
+ matrixPoints.push({ x: groupAccessArea.x2, y: groupAccessArea.y1 });
1117
+ // 最小区域 四角
1118
+ matrixPoints.push({ x: groupForbiddenArea.x1, y: groupForbiddenArea.y1 });
1119
+ matrixPoints.push({ x: groupForbiddenArea.x2, y: groupForbiddenArea.y2 });
1120
+ matrixPoints.push({ x: groupForbiddenArea.x1, y: groupForbiddenArea.y2 });
1121
+ matrixPoints.push({ x: groupForbiddenArea.x2, y: groupForbiddenArea.y1 });
1122
+ // 起点
1123
+ matrixPoints.push({ ...fromAnchorPos, type: 'from' });
1124
+ // 起点 出口
1125
+ matrixPoints.push({ ...fromEntry, type: 'from-entry' });
1126
+ // 终点
1127
+ matrixPoints.push({ ...toAnchorPos, type: 'to' });
1128
+ // 终点 入口
1129
+ matrixPoints.push({ ...toEntry, type: 'to-entry' });
1130
+ // 通过区域 中点
1131
+ matrixPoints.push(this.getGroupPairCenter(fromGroupForbiddenArea, toGroupForbiddenArea));
1132
+ // 去重
1133
+ matrixPoints = matrixPoints.reduce(
1134
+ (arr, item) => {
1135
+ if (item.type === void 0) {
1136
+ if (arr.findIndex((o) => o.x === item.x && o.y === item.y) < 0) {
1137
+ arr.push(item);
1138
+ }
1139
+ } else {
1140
+ const idx = arr.findIndex((o) => o.x === item.x && o.y === item.y)
1141
+ if (idx > -1) {
1142
+ arr.splice(idx, 1);
1143
+ }
1144
+ arr.push(item);
1145
+ }
1146
+ return arr;
1147
+ },
1148
+ [] as typeof matrixPoints
1149
+ );
1150
+ const columns = [
1151
+ ...matrixPoints.map((o) => o.x),
1152
+ // 增加列
1153
+ fromGroupForbiddenArea.x1,
1154
+ fromGroupForbiddenArea.x2,
1155
+ toGroupForbiddenArea.x1,
1156
+ toGroupForbiddenArea.x2
1157
+ ].sort((a, b) => a - b);
1158
+ // 去重
1159
+ for (let x = columns.length - 1; x > 0; x--) {
1160
+ if (columns[x] === columns[x - 1]) {
1161
+ columns.splice(x, 1);
1162
+ }
1163
+ }
1164
+ const rows = [
1165
+ ...matrixPoints.map((o) => o.y),
1166
+ // 增加行
1167
+ fromGroupForbiddenArea.y1,
1168
+ fromGroupForbiddenArea.y2,
1169
+ toGroupForbiddenArea.y1,
1170
+ toGroupForbiddenArea.y2
1171
+ ].sort((a, b) => a - b);
1172
+ // 去重
1173
+ for (let y = rows.length - 1; y > 0; y--) {
1174
+ if (rows[y] === rows[y - 1]) {
1175
+ rows.splice(y, 1);
1176
+ }
1177
+ }
1178
+ // 屏蔽区域(序号)
1179
+ const columnFromStart = columns.findIndex((o) => o === fromGroupForbiddenArea.x1);
1180
+ const columnFromEnd = columns.findIndex((o) => o === fromGroupForbiddenArea.x2);
1181
+ const rowFromStart = rows.findIndex((o) => o === fromGroupForbiddenArea.y1);
1182
+ const rowFromEnd = rows.findIndex((o) => o === fromGroupForbiddenArea.y2);
1183
+ const columnToStart = columns.findIndex((o) => o === toGroupForbiddenArea.x1);
1184
+ const columnToEnd = columns.findIndex((o) => o === toGroupForbiddenArea.x2);
1185
+ const rowToStart = rows.findIndex((o) => o === toGroupForbiddenArea.y1);
1186
+ const rowToEnd = rows.findIndex((o) => o === toGroupForbiddenArea.y2);
1187
+ // 算法矩阵起点、终点
1188
+ let matrixStart: Konva.Vector2d | null = null;
1189
+ let matrixEnd: Konva.Vector2d | null = null;
1190
+ // 算法地图矩阵
1191
+ const matrix: Array<number[]> = [];
1192
+ for (let y = 0; y < rows.length; y++) {
1193
+ // 新增行
1194
+ if (matrix[y] === void 0) {
1195
+ matrix[y] = [];
1196
+ }
1197
+ for (let x = 0; x < columns.length; x++) {
1198
+ // 不可通过区域
1199
+ if (
1200
+ x >= columnFromStart &&
1201
+ x <= columnFromEnd &&
1202
+ y >= rowFromStart &&
1203
+ y <= rowFromEnd
1204
+ ) {
1205
+ matrix[y][x] = 2;
1206
+ } else if (
1207
+ x >= columnToStart &&
1208
+ x <= columnToEnd &&
1209
+ y >= rowToStart &&
1210
+ y <= rowToEnd
1211
+ ) {
1212
+ matrix[y][x] = 2;
1213
+ } else {
1214
+ // 可通过区域
1215
+ matrix[y][x] = 0;
1216
+ }
1217
+ // 出口、入口 -> 算法 起点、终点
1218
+ if (columns[x] === fromEntry.x && rows[y] === fromEntry.y) {
1219
+ matrix[y][x] = 1;
1220
+ matrixStart = { x, y };
1221
+ }
1222
+ if (columns[x] === toEntry.x && rows[y] === toEntry.y) {
1223
+ matrix[y][x] = 1;
1224
+ matrixEnd = { x, y };
1225
+ }
1226
+ // 没有定义方向(给于十字可通过区域)
1227
+ // 如,从:
1228
+ // 1 1 1
1229
+ // 1 0 1
1230
+ // 1 1 1
1231
+ // 变成:
1232
+ // 1 0 1
1233
+ // 0 0 0
1234
+ // 1 0 1
1235
+ if (!fromAnchor.attrs.direction) {
1236
+ if (columns[x] === fromEntry.x || rows[y] === fromEntry.y) {
1237
+ if (
1238
+ x >= columnFromStart &&
1239
+ x <= columnFromEnd &&
1240
+ y >= rowFromStart &&
1241
+ y <= rowFromEnd
1242
+ ) {
1243
+ matrix[y][x] = 1;
1244
+ }
1245
+ }
1246
+ }
1247
+ if (!toAnchor.attrs.direction) {
1248
+ if (columns[x] === toEntry.x || rows[y] === toEntry.y) {
1249
+ if (
1250
+ x >= columnToStart &&
1251
+ x <= columnToEnd &&
1252
+ y >= rowToStart &&
1253
+ y <= rowToEnd
1254
+ ) {
1255
+ matrix[y][x] = 1;
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ }
1261
+
1262
+ if (matrixStart && matrixEnd) {
1263
+ const way = aStar({ from: matrixStart, to: matrixEnd, matrix, maxCost: 2 });
1264
+ const linkLine = new Konva.Arrow({
1265
+ name: 'link-line',
1266
+ // 用于删除连接线
1267
+ groupId: fromGroup.id(),
1268
+ pointId: fromPoint.id,
1269
+ pairId: pair.id,
1270
+ linkType: pair.linkType, // 记录 连接线 类型
1271
+ points: [
1272
+ [this.render.toStageValue(fromAnchorPos.x), this.render.toStageValue(fromAnchorPos.y)], // 补充 起点
1273
+ ...way.map((o) => [this.render.toStageValue(columns[o.x]), this.render.toStageValue(rows[o.y])]),
1274
+ [this.render.toStageValue(toAnchorPos.x), this.render.toStageValue(toAnchorPos.y)] // 补充 终点
1275
+ ].flat(),
1276
+ pointerAtBeginning: false,
1277
+ pointerAtEnding: false
1278
+ });
1279
+ linkLine.stroke(this.render.getLinkSettings(linkLine).stroke);
1280
+ linkLine.strokeWidth(this.render.getLinkSettings(linkLine).strokeWidth);
1281
+ linkLine.hitStrokeWidth(Math.max(this.render.getLinkSettings().strokeWidth, 10));
1282
+ linkLine.dash(this.render.linkTool.linkCurrent?.attrs.pairId === pair.id ? [1, 1] : []);
1283
+ linkLine.fill(this.render.getLinkSettings(linkLine).stroke);
1284
+ linkLine.pointerAtBeginning(this.render.getLinkSettings(linkLine).arrowStart);
1285
+ linkLine.pointerAtEnding(this.render.getLinkSettings(linkLine).arrowEnd);
1286
+ if (!this.render.config.readonly) {
1287
+ linkLine.on('pointerclick', () => this.render.linkTool.select(linkLine));
1288
+ linkLine.on('mouseenter', () => {
1289
+ linkLine.opacity(0.5);
1290
+ document.body.style.cursor = 'pointer';
1291
+ });
1292
+ linkLine.on('mouseleave', () => {
1293
+ linkLine.opacity(1);
1294
+ document.body.style.cursor = 'default';
1295
+ });
1296
+ }
1297
+ this.group.add(linkLine);
1298
+ }
1299
+ }
1300
+ }
1301
+ }
1302
+ }
1303
+ }
1304
+
1305
+ if (!this.render.config.readonly) {
1306
+ // 连接点
1307
+ for (const point of points) {
1308
+ const group = groups.find((o) => o.id() === point.groupId);
1309
+ // 非 选择中
1310
+ if (group && !group.getAttr('selected')) {
1311
+ const anchor = this.render.layer.findOne(`#${point.id}`);
1312
+ if (anchor) {
1313
+ const circle = new Konva.Circle({
1314
+ name: 'link-point',
1315
+ id: point.id,
1316
+ groupId: group.id(),
1317
+ x: this.render.toStageValue(anchor.absolutePosition().x - stageState.x),
1318
+ y: this.render.toStageValue(anchor.absolutePosition().y - stageState.y),
1319
+ radius: this.render.toStageValue(this.option.size),
1320
+ stroke: 'rgba(255,0,0,0.2)',
1321
+ strokeWidth: this.render.toStageValue(1),
1322
+ // 调整中,不显示连接点
1323
+ opacity: point.visible && !(this.render.draws[Draws.GraphDraw.name] as Draws.GraphDraw).state.adjusting ? 1 : 0
1324
+ });
1325
+ // hover 效果
1326
+ circle.on('mouseenter', () => {
1327
+ circle.stroke('rgba(255,0,0,0.5)');
1328
+ circle.opacity(1);
1329
+ document.body.style.cursor = 'pointer';
1330
+ });
1331
+ circle.on('mouseleave', () => {
1332
+ circle.stroke('rgba(255,0,0,0.2)');
1333
+ circle.opacity(0);
1334
+ document.body.style.cursor = 'default';
1335
+ });
1336
+ circle.on('mousedown', () => {
1337
+ this.render.selectionTool.selectingClear();
1338
+ this.render.linkTool.selectingClear();
1339
+ const pos = this.render.stage.getPointerPosition();
1340
+ if (pos) {
1341
+ // 临时 连接线 画
1342
+ this.state.linkingLine = {
1343
+ group: group,
1344
+ circle: circle,
1345
+ line: new Konva.Line({
1346
+ name: 'linking-line',
1347
+ points: [
1348
+ [circle.x(), circle.y()],
1349
+ [
1350
+ this.render.toStageValue(pos.x - stageState.x),
1351
+ this.render.toStageValue(pos.y - stageState.y)
1352
+ ]
1353
+ ].flat(),
1354
+ stroke: 'blue',
1355
+ strokeWidth: 1
1356
+ })
1357
+ };
1358
+ this.layer.add(this.state.linkingLine.line);
1359
+ }
1360
+ });
1361
+ circle.on('mouseup', () => {
1362
+ if (this.state.linkingLine) {
1363
+ const line = this.state.linkingLine;
1364
+ // 不同连接点
1365
+ if (line.circle.id() !== circle.id()) {
1366
+ const toGroup = groups.find((o) => o.id() === circle.getAttr('groupId'));
1367
+ if (toGroup) {
1368
+ const fromPoint = points.find((o) => o.id === line.circle.id());
1369
+ if (fromPoint) {
1370
+ const toPoint = points.find((o) => o.id === line.circle.id());
1371
+ if (toPoint) {
1372
+ if (Array.isArray(fromPoint.pairs)) {
1373
+ fromPoint.pairs = [
1374
+ ...fromPoint.pairs,
1375
+ {
1376
+ id: nanoid(),
1377
+ from: {
1378
+ groupId: line.group.id(),
1379
+ pointId: line.circle.id()
1380
+ },
1381
+ to: {
1382
+ groupId: circle.getAttr('groupId'),
1383
+ pointId: circle.id()
1384
+ },
1385
+ linkType: this.state.linkType // 记录 连接线 类型
1386
+ }
1387
+ ];
1388
+ }
1389
+ // 更新历史
1390
+ this.render.updateHistory();
1391
+ // 对齐线清除
1392
+ this.render.attractTool.alignLinesClear();
1393
+ // 重绘
1394
+ this.render.redraw([
1395
+ Draws.LinkDraw.name,
1396
+ Draws.RulerDraw.name,
1397
+ Draws.PreviewDraw.name
1398
+ ]);
1399
+ }
1400
+ }
1401
+ }
1402
+ }
1403
+ // 临时 连接线 移除
1404
+ this.state.linkingLine?.line.destroy();
1405
+ this.state.linkingLine = null;
1406
+ }
1407
+ });
1408
+ this.group.add(circle);
1409
+ }
1410
+ }
1411
+ }
1412
+ }
1413
+ }
1414
+ }