@logicflow/core 2.2.0-alpha.2 → 2.2.0-alpha.4
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.
- package/.turbo/turbo-build$colon$dev.log +2 -2
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +16 -0
- package/dist/index.css +3 -2
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/es/LogicFlow.d.ts +9 -0
- package/es/constant/index.d.ts +1 -1
- package/es/constant/index.js +1 -1
- package/es/constant/theme.d.ts +136 -0
- package/es/constant/theme.js +680 -0
- package/es/index.css +3 -2
- package/es/model/GraphModel.d.ts +9 -2
- package/es/model/GraphModel.js +45 -12
- package/es/model/TransformModel.js +9 -9
- package/es/model/edge/BaseEdgeModel.js +7 -2
- package/es/model/edge/PolylineEdgeModel.d.ts +6 -0
- package/es/model/edge/PolylineEdgeModel.js +23 -1
- package/es/model/node/BaseNodeModel.d.ts +12 -1
- package/es/model/node/BaseNodeModel.js +6 -1
- package/es/model/node/HtmlNodeModel.d.ts +12 -0
- package/es/model/node/HtmlNodeModel.js +19 -0
- package/es/model/node/PolygonNodeModel.js +3 -3
- package/es/options.d.ts +1 -1
- package/es/style/index.css +3 -2
- package/es/style/index.less +3 -2
- package/es/style/raw.d.ts +1 -1
- package/es/style/raw.js +1 -1
- package/es/tool/MultipleSelectTool.js +10 -5
- package/es/util/edge.d.ts +1 -1
- package/es/util/edge.js +2 -2
- package/es/util/geometry.d.ts +8 -0
- package/es/util/geometry.js +79 -0
- package/es/util/theme.d.ts +2 -65
- package/es/util/theme.js +4 -281
- package/es/view/Control.d.ts +5 -0
- package/es/view/Control.js +44 -57
- package/es/view/edge/PolylineEdge.js +13 -2
- package/es/view/node/BaseNode.d.ts +1 -0
- package/es/view/node/BaseNode.js +23 -10
- package/es/view/node/HtmlNode.js +2 -4
- package/es/view/overlay/CanvasOverlay.js +5 -2
- package/es/view/overlay/Grid.d.ts +12 -1
- package/es/view/overlay/Grid.js +85 -23
- package/es/view/overlay/OutlineOverlay.d.ts +1 -0
- package/es/view/overlay/OutlineOverlay.js +17 -16
- package/es/view/overlay/gridConfig.d.ts +46 -0
- package/es/view/overlay/gridConfig.js +99 -0
- package/es/view/shape/Polygon.d.ts +0 -7
- package/es/view/shape/Polygon.js +12 -43
- package/lib/LogicFlow.d.ts +9 -0
- package/lib/constant/index.d.ts +1 -1
- package/lib/constant/index.js +16 -2
- package/lib/constant/theme.d.ts +136 -0
- package/lib/constant/theme.js +683 -0
- package/lib/index.css +3 -2
- package/lib/model/GraphModel.d.ts +9 -2
- package/lib/model/GraphModel.js +46 -13
- package/lib/model/TransformModel.js +9 -9
- package/lib/model/edge/BaseEdgeModel.js +7 -2
- package/lib/model/edge/PolylineEdgeModel.d.ts +6 -0
- package/lib/model/edge/PolylineEdgeModel.js +23 -1
- package/lib/model/node/BaseNodeModel.d.ts +12 -1
- package/lib/model/node/BaseNodeModel.js +6 -1
- package/lib/model/node/HtmlNodeModel.d.ts +12 -0
- package/lib/model/node/HtmlNodeModel.js +19 -0
- package/lib/model/node/PolygonNodeModel.js +3 -3
- package/lib/options.d.ts +1 -1
- package/lib/style/index.css +3 -2
- package/lib/style/index.less +3 -2
- package/lib/style/raw.d.ts +1 -1
- package/lib/style/raw.js +1 -1
- package/lib/tool/MultipleSelectTool.js +10 -5
- package/lib/util/edge.d.ts +1 -1
- package/lib/util/edge.js +2 -2
- package/lib/util/geometry.d.ts +8 -0
- package/lib/util/geometry.js +81 -1
- package/lib/util/theme.d.ts +2 -65
- package/lib/util/theme.js +15 -292
- package/lib/view/Control.d.ts +5 -0
- package/lib/view/Control.js +44 -57
- package/lib/view/edge/PolylineEdge.js +13 -2
- package/lib/view/node/BaseNode.d.ts +1 -0
- package/lib/view/node/BaseNode.js +23 -10
- package/lib/view/node/HtmlNode.js +1 -3
- package/lib/view/overlay/CanvasOverlay.js +5 -2
- package/lib/view/overlay/Grid.d.ts +12 -1
- package/lib/view/overlay/Grid.js +83 -21
- package/lib/view/overlay/OutlineOverlay.d.ts +1 -0
- package/lib/view/overlay/OutlineOverlay.js +17 -16
- package/lib/view/overlay/gridConfig.d.ts +46 -0
- package/lib/view/overlay/gridConfig.js +104 -0
- package/lib/view/shape/Polygon.d.ts +0 -7
- package/lib/view/shape/Polygon.js +13 -45
- package/package.json +1 -1
- package/src/LogicFlow.tsx +10 -0
- package/src/constant/index.ts +2 -2
- package/src/constant/theme.ts +708 -0
- package/src/model/EditConfigModel.ts +4 -4
- package/src/model/GraphModel.ts +48 -16
- package/src/model/TransformModel.ts +9 -9
- package/src/model/edge/BaseEdgeModel.ts +10 -2
- package/src/model/edge/PolylineEdgeModel.ts +26 -1
- package/src/model/node/BaseNodeModel.ts +12 -5
- package/src/model/node/HtmlNodeModel.ts +14 -0
- package/src/model/node/PolygonNodeModel.ts +2 -0
- package/src/options.ts +1 -1
- package/src/style/index.less +3 -2
- package/src/style/raw.ts +3 -2
- package/src/tool/MultipleSelectTool.tsx +13 -5
- package/src/util/edge.ts +2 -1
- package/src/util/geometry.ts +99 -0
- package/src/util/theme.ts +12 -303
- package/src/view/Control.tsx +61 -63
- package/src/view/edge/PolylineEdge.tsx +14 -2
- package/src/view/node/BaseNode.tsx +18 -3
- package/src/view/node/HtmlNode.tsx +27 -10
- package/src/view/overlay/CanvasOverlay.tsx +4 -2
- package/src/view/overlay/Grid.tsx +187 -30
- package/src/view/overlay/OutlineOverlay.tsx +35 -47
- package/src/view/overlay/gridConfig.ts +103 -0
- package/src/view/shape/Polygon.tsx +12 -49
- package/stats.html +1 -1
package/src/model/GraphModel.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assign,
|
|
2
3
|
find,
|
|
3
4
|
forEach,
|
|
4
5
|
map,
|
|
@@ -26,6 +27,8 @@ import {
|
|
|
26
27
|
ModelType,
|
|
27
28
|
OverlapMode,
|
|
28
29
|
TextMode,
|
|
30
|
+
backgroundModeMap,
|
|
31
|
+
gridModeMap,
|
|
29
32
|
} from '../constant'
|
|
30
33
|
import LogicFlow from '../LogicFlow'
|
|
31
34
|
import { Options as LFOptions } from '../options'
|
|
@@ -43,8 +46,6 @@ import {
|
|
|
43
46
|
setupTheme,
|
|
44
47
|
snapToGrid,
|
|
45
48
|
updateTheme,
|
|
46
|
-
backgroundModeMap,
|
|
47
|
-
gridModeMap,
|
|
48
49
|
} from '../util'
|
|
49
50
|
import EventEmitter from '../event/eventEmitter'
|
|
50
51
|
import { Grid } from '../view/overlay'
|
|
@@ -69,8 +70,9 @@ export class GraphModel {
|
|
|
69
70
|
|
|
70
71
|
// 流程图主题配置
|
|
71
72
|
@observable theme: LogicFlow.Theme
|
|
73
|
+
@observable themeMode: LogicFlow.ThemeMode | string = 'default'
|
|
72
74
|
// 初始化样式
|
|
73
|
-
customStyles:
|
|
75
|
+
customStyles: LogicFlow.Theme
|
|
74
76
|
// 网格配置
|
|
75
77
|
@observable grid: Grid.GridOptions
|
|
76
78
|
// 事件中心
|
|
@@ -167,6 +169,10 @@ export class GraphModel {
|
|
|
167
169
|
customTrajectory,
|
|
168
170
|
customTargetAnchor,
|
|
169
171
|
} = options
|
|
172
|
+
this.themeMode = options.themeMode || 'default'
|
|
173
|
+
const initialGrid = gridModeMap[this.themeMode] || gridModeMap['default']
|
|
174
|
+
const initialBackground =
|
|
175
|
+
backgroundModeMap[this.themeMode] || backgroundModeMap['default']
|
|
170
176
|
this.rootEl = container
|
|
171
177
|
this.partial = !!partial
|
|
172
178
|
this.background = background
|
|
@@ -175,11 +181,16 @@ export class GraphModel {
|
|
|
175
181
|
// TODO:需要让用户设置成 0 吗?后面可以讨论一下
|
|
176
182
|
this.gridSize = grid.size || 1 // 默认 gridSize 设置为 1
|
|
177
183
|
}
|
|
178
|
-
this.customStyles = options.style || {}
|
|
179
|
-
this.grid = Grid.getGridOptions(grid ?? false)
|
|
184
|
+
this.customStyles = (options.style || {}) as LogicFlow.Theme
|
|
180
185
|
this.theme = setupTheme(options.style, options.themeMode)
|
|
186
|
+
this.grid = Grid.getGridOptions(assign({}, initialGrid, grid))
|
|
181
187
|
this.theme.grid = cloneDeep(this.grid)
|
|
182
|
-
|
|
188
|
+
if (background) {
|
|
189
|
+
this.background = cloneDeep(assign({}, initialBackground, background))
|
|
190
|
+
this.theme.background = cloneDeep(
|
|
191
|
+
assign({}, initialBackground, background),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
183
194
|
this.edgeType = options.edgeType || 'polyline'
|
|
184
195
|
this.animation = setupAnimation(animation)
|
|
185
196
|
this.overlapMode = options.overlapMode || OverlapMode.DEFAULT
|
|
@@ -195,6 +206,9 @@ export class GraphModel {
|
|
|
195
206
|
((entries) => {
|
|
196
207
|
for (const entry of entries) {
|
|
197
208
|
if (entry.target === this.rootEl) {
|
|
209
|
+
// 检查元素是否仍在DOM中
|
|
210
|
+
const isElementInDOM = document.body.contains(this.rootEl)
|
|
211
|
+
if (!isElementInDOM) return
|
|
198
212
|
this.resize()
|
|
199
213
|
this.eventCenter.emit('graph:resize', {
|
|
200
214
|
target: this.rootEl,
|
|
@@ -1516,9 +1530,10 @@ export class GraphModel {
|
|
|
1516
1530
|
*/
|
|
1517
1531
|
@action setTheme(
|
|
1518
1532
|
style: Partial<LogicFlow.Theme>,
|
|
1519
|
-
themeMode?:
|
|
1533
|
+
themeMode?: LogicFlow.ThemeMode | string,
|
|
1520
1534
|
) {
|
|
1521
1535
|
if (themeMode) {
|
|
1536
|
+
this.themeMode = themeMode
|
|
1522
1537
|
// 修改背景颜色
|
|
1523
1538
|
backgroundModeMap[themeMode] &&
|
|
1524
1539
|
this.updateBackgroundOptions({
|
|
@@ -1589,15 +1604,32 @@ export class GraphModel {
|
|
|
1589
1604
|
* 重新设置画布的宽高
|
|
1590
1605
|
*/
|
|
1591
1606
|
@action resize(width?: number, height?: number): void {
|
|
1592
|
-
|
|
1593
|
-
this.
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
if (!
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1607
|
+
// 检查当前实例是否已被销毁或rootEl不存在
|
|
1608
|
+
if (!this.rootEl) return
|
|
1609
|
+
|
|
1610
|
+
// 检查元素是否仍在DOM中
|
|
1611
|
+
const isElementInDOM = document.body.contains(this.rootEl)
|
|
1612
|
+
if (!isElementInDOM) return
|
|
1613
|
+
|
|
1614
|
+
// 检查元素是否可见
|
|
1615
|
+
const isVisible = this.rootEl.offsetParent !== null
|
|
1616
|
+
if (!isVisible) return
|
|
1617
|
+
|
|
1618
|
+
try {
|
|
1619
|
+
this.width = width ?? this.rootEl.getBoundingClientRect().width
|
|
1620
|
+
this.isContainerWidth = isNil(width)
|
|
1621
|
+
this.height = height ?? this.rootEl.getBoundingClientRect().height
|
|
1622
|
+
this.isContainerHeight = isNil(height)
|
|
1623
|
+
|
|
1624
|
+
// 只有在元素可见且应该有宽高的情况下才显示警告
|
|
1625
|
+
if (isVisible && (!this.width || !this.height)) {
|
|
1626
|
+
console.warn(
|
|
1627
|
+
'渲染画布的时候无法获取画布宽高,请确认在container已挂载到DOM。@see https://github.com/didi/LogicFlow/issues/675',
|
|
1628
|
+
)
|
|
1629
|
+
}
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
// 捕获可能的DOM操作错误
|
|
1632
|
+
console.warn('获取画布宽高时发生错误:', error)
|
|
1601
1633
|
}
|
|
1602
1634
|
}
|
|
1603
1635
|
|
|
@@ -46,15 +46,15 @@ const translateLimitsMap = {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export class TransformModel implements TransformInterface {
|
|
49
|
-
MINI_SCALE_SIZE = 0.2
|
|
50
|
-
MAX_SCALE_SIZE = 16
|
|
51
|
-
@observable SCALE_X = 1
|
|
52
|
-
@observable SKEW_Y = 0
|
|
53
|
-
@observable SKEW_X = 0
|
|
54
|
-
@observable SCALE_Y = 1
|
|
55
|
-
@observable TRANSLATE_X = 0
|
|
56
|
-
@observable TRANSLATE_Y = 0
|
|
57
|
-
@observable ZOOM_SIZE = 0.04
|
|
49
|
+
MINI_SCALE_SIZE = 0.2 // 缩小的最小值
|
|
50
|
+
MAX_SCALE_SIZE = 16 // 放大的最大值
|
|
51
|
+
@observable SCALE_X = 1 // x轴缩放比例
|
|
52
|
+
@observable SKEW_Y = 0 // y轴倾斜角度
|
|
53
|
+
@observable SKEW_X = 0 // x轴倾斜角度
|
|
54
|
+
@observable SCALE_Y = 1 // y轴缩放比例
|
|
55
|
+
@observable TRANSLATE_X = 0 // x轴平移距离
|
|
56
|
+
@observable TRANSLATE_Y = 0 // y轴平移距离
|
|
57
|
+
@observable ZOOM_SIZE = 0.04 // 缩放比例变化量
|
|
58
58
|
eventCenter: EventEmitter
|
|
59
59
|
|
|
60
60
|
// 限制画布可移动区域
|
|
@@ -265,8 +265,16 @@ export class BaseEdgeModel<P extends PropertiesType = PropertiesType>
|
|
|
265
265
|
*/
|
|
266
266
|
getOutlineStyle(): LogicFlow.OutlineTheme {
|
|
267
267
|
const { graphModel } = this
|
|
268
|
-
const {
|
|
269
|
-
|
|
268
|
+
const { edgeOutline } = graphModel.theme
|
|
269
|
+
let attributes = { ...edgeOutline }
|
|
270
|
+
if (this.isHovered) {
|
|
271
|
+
const hoverStyle = edgeOutline.hover || {}
|
|
272
|
+
attributes = {
|
|
273
|
+
...attributes,
|
|
274
|
+
...hoverStyle,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return cloneDeep(attributes)
|
|
270
278
|
}
|
|
271
279
|
|
|
272
280
|
/**
|
|
@@ -34,13 +34,38 @@ export class PolylineEdgeModel extends BaseEdgeModel {
|
|
|
34
34
|
@observable dbClickPosition?: Point
|
|
35
35
|
|
|
36
36
|
initEdgeData(data: LogicFlow.EdgeConfig): void {
|
|
37
|
-
|
|
37
|
+
const providedOffset = get(data, 'properties.offset')
|
|
38
|
+
// 当用户未传入 offset 时,按“箭头与折线重叠长度 + 5”作为默认值
|
|
39
|
+
// 其中“重叠长度”采用箭头样式中的 offset(沿边方向的长度)
|
|
40
|
+
this.offset =
|
|
41
|
+
typeof providedOffset === 'number'
|
|
42
|
+
? providedOffset
|
|
43
|
+
: this.getDefaultOffset()
|
|
38
44
|
if (data.pointsList) {
|
|
39
45
|
this.pointsList = data.pointsList
|
|
40
46
|
}
|
|
41
47
|
super.initEdgeData(data)
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
setAttributes() {
|
|
51
|
+
const { offset: newOffset } = this.properties
|
|
52
|
+
if (newOffset && newOffset !== this.offset) {
|
|
53
|
+
this.offset = newOffset
|
|
54
|
+
this.updatePoints()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 计算默认 offset:箭头与折线重叠长度 + 5
|
|
60
|
+
* 重叠长度采用箭头样式中的 offset(沿边方向的长度)
|
|
61
|
+
*/
|
|
62
|
+
private getDefaultOffset(): number {
|
|
63
|
+
const arrowStyle = this.getArrowStyle()
|
|
64
|
+
const arrowOverlap =
|
|
65
|
+
typeof arrowStyle.offset === 'number' ? arrowStyle.offset : 0
|
|
66
|
+
return arrowOverlap + 5
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
getEdgeStyle() {
|
|
45
70
|
const { polyline } = this.graphModel.theme
|
|
46
71
|
const style = super.getEdgeStyle()
|
|
@@ -57,8 +57,7 @@ export interface IBaseNodeModel<P extends PropertiesType>
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export class BaseNodeModel<P extends PropertiesType = PropertiesType>
|
|
60
|
-
implements IBaseNodeModel<P>
|
|
61
|
-
{
|
|
60
|
+
implements IBaseNodeModel<P> {
|
|
62
61
|
readonly BaseType = ElementType.NODE
|
|
63
62
|
static BaseType: ElementType = ElementType.NODE
|
|
64
63
|
|
|
@@ -226,7 +225,7 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
|
|
|
226
225
|
*
|
|
227
226
|
* @overridable 支持重写
|
|
228
227
|
*/
|
|
229
|
-
public setAttributes() {}
|
|
228
|
+
public setAttributes() { }
|
|
230
229
|
|
|
231
230
|
/**
|
|
232
231
|
* @overridable 支持重写,自定义此类型节点默认生成方式
|
|
@@ -307,7 +306,7 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
|
|
|
307
306
|
}
|
|
308
307
|
|
|
309
308
|
// TODO: 等比例缩放
|
|
310
|
-
proportionalResize() {}
|
|
309
|
+
proportionalResize() { }
|
|
311
310
|
|
|
312
311
|
/**
|
|
313
312
|
* 获取被保存时返回的数据
|
|
@@ -433,7 +432,15 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
|
|
|
433
432
|
|
|
434
433
|
getResizeOutlineStyle() {
|
|
435
434
|
const { resizeOutline } = this.graphModel.theme
|
|
436
|
-
|
|
435
|
+
let attributes = { ...resizeOutline }
|
|
436
|
+
if (this.isHovered) {
|
|
437
|
+
const hoverStyle = resizeOutline.hover || {}
|
|
438
|
+
attributes = {
|
|
439
|
+
...attributes,
|
|
440
|
+
...hoverStyle,
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return cloneDeep(attributes)
|
|
437
444
|
}
|
|
438
445
|
|
|
439
446
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cloneDeep } from 'lodash-es'
|
|
1
2
|
import BaseNodeModel from './BaseNodeModel'
|
|
2
3
|
import { Model } from '../BaseModel'
|
|
3
4
|
import { ModelType } from '../../constant'
|
|
@@ -45,6 +46,19 @@ export class HtmlNodeModel<
|
|
|
45
46
|
{ x: x - width / 2, y, id: `${this.id}_3` },
|
|
46
47
|
]
|
|
47
48
|
}
|
|
49
|
+
|
|
50
|
+
getNodeStyle() {
|
|
51
|
+
const style = super.getNodeStyle()
|
|
52
|
+
const { baseNode, html } = this.graphModel.theme
|
|
53
|
+
const { style: customStyle = {} } = this.properties
|
|
54
|
+
const finalStyle = {
|
|
55
|
+
...style,
|
|
56
|
+
...cloneDeep(baseNode),
|
|
57
|
+
...cloneDeep(html),
|
|
58
|
+
...cloneDeep(customStyle),
|
|
59
|
+
}
|
|
60
|
+
return finalStyle
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
export default HtmlNodeModel
|
|
@@ -67,12 +67,14 @@ export class PolygonNodeModel<
|
|
|
67
67
|
const {
|
|
68
68
|
graphModel: {
|
|
69
69
|
theme: { polygon },
|
|
70
|
+
customStyles: { polygon: customPolygon },
|
|
70
71
|
},
|
|
71
72
|
} = this
|
|
72
73
|
const { style: customStyle = {} } = this.properties
|
|
73
74
|
return {
|
|
74
75
|
...style,
|
|
75
76
|
...cloneDeep(polygon),
|
|
77
|
+
...cloneDeep(customPolygon),
|
|
76
78
|
...cloneDeep(customStyle),
|
|
77
79
|
}
|
|
78
80
|
}
|
package/src/options.ts
CHANGED
|
@@ -110,7 +110,7 @@ export namespace Options {
|
|
|
110
110
|
|
|
111
111
|
customTargetAnchor?: customTargetAnchorType
|
|
112
112
|
customTrajectory?: (props: CustomAnchorLineProps) => h.JSX.Element
|
|
113
|
-
themeMode?:
|
|
113
|
+
themeMode?: LogicFlow.ThemeMode // 主题模式
|
|
114
114
|
|
|
115
115
|
parentTransform?: TransformModel // 父级变换模型,用于嵌套变换
|
|
116
116
|
|
package/src/style/index.less
CHANGED
|
@@ -215,8 +215,9 @@
|
|
|
215
215
|
|
|
216
216
|
.lf-multiple-select {
|
|
217
217
|
position: absolute;
|
|
218
|
-
border: 2px dashed #
|
|
219
|
-
|
|
218
|
+
border: 2px dashed #4271dfcc;
|
|
219
|
+
border-radius: 12px;
|
|
220
|
+
box-shadow: 0 0 3px 0 #4271df80;
|
|
220
221
|
cursor: move;
|
|
221
222
|
}
|
|
222
223
|
|
package/src/style/raw.ts
CHANGED
|
@@ -183,8 +183,9 @@ export const content = `.lf-graph {
|
|
|
183
183
|
}
|
|
184
184
|
.lf-multiple-select {
|
|
185
185
|
position: absolute;
|
|
186
|
-
border: 2px dashed #
|
|
187
|
-
|
|
186
|
+
border: 2px dashed #4271dfcc;
|
|
187
|
+
border-radius: 12px;
|
|
188
|
+
box-shadow: 0 0 3px 0 #4271df80;
|
|
188
189
|
cursor: move;
|
|
189
190
|
}
|
|
190
191
|
.lf-edge-adjust-point {
|
|
@@ -27,6 +27,13 @@ export default class MultipleSelect extends Component<IToolProps> {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
handleMouseDown = (ev: PointerEvent) => {
|
|
30
|
+
// 多选区域的拖拽步长随缩放变化
|
|
31
|
+
const {
|
|
32
|
+
graphModel: { gridSize },
|
|
33
|
+
lf,
|
|
34
|
+
} = this.props
|
|
35
|
+
const { SCALE_X } = lf.getTransform()
|
|
36
|
+
this.stepDrag.setStep(gridSize * SCALE_X)
|
|
30
37
|
this.stepDrag.handleMouseDown(ev)
|
|
31
38
|
}
|
|
32
39
|
// 使多选区域的滚轮事件可以触发画布的滚轮事件
|
|
@@ -86,9 +93,10 @@ export default class MultipleSelect extends Component<IToolProps> {
|
|
|
86
93
|
|
|
87
94
|
render() {
|
|
88
95
|
const {
|
|
89
|
-
graphModel: { selectElements, transformModel },
|
|
96
|
+
graphModel: { selectElements, transformModel, theme },
|
|
90
97
|
} = this.props
|
|
91
98
|
const { SCALE_X, SCALE_Y } = this.props.lf.getTransform()
|
|
99
|
+
const { xPadding = 8, yPadding = 8 } = theme.multiSelect || {}
|
|
92
100
|
if (selectElements.size <= 1) return
|
|
93
101
|
let x = Number.MAX_SAFE_INTEGER
|
|
94
102
|
let y = Number.MAX_SAFE_INTEGER
|
|
@@ -113,10 +121,10 @@ export default class MultipleSelect extends Component<IToolProps> {
|
|
|
113
121
|
;[x, y] = transformModel.CanvasPointToHtmlPoint([x, y])
|
|
114
122
|
;[x1, y1] = transformModel.CanvasPointToHtmlPoint([x1, y1])
|
|
115
123
|
const style = {
|
|
116
|
-
left: `${x - (20 * SCALE_X) / 2}px`,
|
|
117
|
-
top: `${y - (20 * SCALE_Y) / 2}px`,
|
|
118
|
-
width: `${x1 - x + 20 * SCALE_X}px`,
|
|
119
|
-
height: `${y1 - y + 20 * SCALE_Y}px`,
|
|
124
|
+
left: `${x - (20 * SCALE_X) / 2 - xPadding / 2}px`,
|
|
125
|
+
top: `${y - (20 * SCALE_Y) / 2 - yPadding / 2}px`,
|
|
126
|
+
width: `${x1 - x + 20 * SCALE_X + xPadding}px`,
|
|
127
|
+
height: `${y1 - y + 20 * SCALE_Y + yPadding}px`,
|
|
120
128
|
'border-width': `${2 * SCALE_X}px`,
|
|
121
129
|
}
|
|
122
130
|
return (
|
package/src/util/edge.ts
CHANGED
|
@@ -159,6 +159,7 @@ export const mergeBBox = (b1: BoxBounds, b2: BoxBounds): BoxBounds => {
|
|
|
159
159
|
export const getBBoxOfPoints = (
|
|
160
160
|
points: Point[] = [],
|
|
161
161
|
offset?: number,
|
|
162
|
+
heightOffset?: number,
|
|
162
163
|
): BoxBounds => {
|
|
163
164
|
const xList: number[] = []
|
|
164
165
|
const yList: number[] = []
|
|
@@ -174,7 +175,7 @@ export const getBBoxOfPoints = (
|
|
|
174
175
|
let height = maxY - minY
|
|
175
176
|
if (offset) {
|
|
176
177
|
width += offset
|
|
177
|
-
height += offset
|
|
178
|
+
height += heightOffset || offset
|
|
178
179
|
}
|
|
179
180
|
return {
|
|
180
181
|
centerX: (minX + maxX) / 2,
|
package/src/util/geometry.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import LogicFlow from '../LogicFlow'
|
|
2
2
|
import PointTuple = LogicFlow.PointTuple
|
|
3
|
+
import Point = LogicFlow.Point
|
|
3
4
|
|
|
4
5
|
export function snapToGrid(point: number, gridSize: number, snapGrid: boolean) {
|
|
5
6
|
// 开启节网格对齐时才根据网格尺寸校准坐标
|
|
@@ -53,3 +54,101 @@ export function normalizePolygon(
|
|
|
53
54
|
// 缩放顶点
|
|
54
55
|
return translatedPoints.map(([x, y]) => [x * scaleFactor, y * scaleFactor])
|
|
55
56
|
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 通用圆角生成:为菱形、多边形、折线在转折处生成与矩形视觉一致的圆角
|
|
60
|
+
* - 圆角基于角平分线,切点距顶点的距离 t = r * tan(theta/2)
|
|
61
|
+
* - 半径会根据相邻边长度进行钳制,避免超过边长造成断裂
|
|
62
|
+
* - 多边形/菱形保持闭合;折线保持开口
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
export const generateRoundedCorners = (
|
|
66
|
+
points: Point[],
|
|
67
|
+
radius: number,
|
|
68
|
+
isClosedShape: boolean, // 是否是闭合图形
|
|
69
|
+
): Point[] => {
|
|
70
|
+
const n = points.length
|
|
71
|
+
if (n < 2 || radius <= 0) return points.slice()
|
|
72
|
+
|
|
73
|
+
const toVec = (a: Point, b: Point) => ({ x: b.x - a.x, y: b.y - a.y })
|
|
74
|
+
const len = (v: { x: number; y: number }) => Math.hypot(v.x, v.y)
|
|
75
|
+
const norm = (v: { x: number; y: number }) => {
|
|
76
|
+
const l = len(v) || 1
|
|
77
|
+
return { x: v.x / l, y: v.y / l }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const result: Point[] = []
|
|
81
|
+
|
|
82
|
+
// 用二次贝塞尔近似圆角,控制点取角点,避免复杂圆心计算
|
|
83
|
+
const makeRoundCorner = (prev: Point, curr: Point, next: Point): Point[] => {
|
|
84
|
+
const vPrev = toVec(curr, prev)
|
|
85
|
+
const vNext = toVec(curr, next)
|
|
86
|
+
const dPrev = len(vPrev)
|
|
87
|
+
const dNext = len(vNext)
|
|
88
|
+
if (dPrev < 1e-6 || dNext < 1e-6) return [curr]
|
|
89
|
+
|
|
90
|
+
const uPrev = norm(vPrev)
|
|
91
|
+
const uNext = norm(vNext)
|
|
92
|
+
const t = Math.min(radius, dPrev * 0.45, dNext * 0.45)
|
|
93
|
+
|
|
94
|
+
const start = { x: curr.x + uPrev.x * t, y: curr.y + uPrev.y * t }
|
|
95
|
+
const end = { x: curr.x + uNext.x * t, y: curr.y + uNext.y * t }
|
|
96
|
+
|
|
97
|
+
// 二次贝塞尔采样:B(s) = (1-s)^2*start + 2(1-s)s*curr + s^2*end
|
|
98
|
+
const steps = 10 // 3段近似,简洁且效果稳定
|
|
99
|
+
const pts: Point[] = [start]
|
|
100
|
+
for (let k = 1; k < steps; k++) {
|
|
101
|
+
const s = k / steps
|
|
102
|
+
const a = 1 - s
|
|
103
|
+
pts.push({
|
|
104
|
+
x: a * a * start.x + 2 * a * s * curr.x + s * s * end.x,
|
|
105
|
+
y: a * a * start.y + 2 * a * s * curr.y + s * s * end.y,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
pts.push(end)
|
|
109
|
+
return pts
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < n; i++) {
|
|
113
|
+
const prevIdx = i === 0 ? (isClosedShape ? n - 1 : 0) : i - 1
|
|
114
|
+
const nextIdx = i === n - 1 ? (isClosedShape ? 0 : n - 1) : i + 1
|
|
115
|
+
const prev = points[prevIdx]
|
|
116
|
+
const curr = points[i]
|
|
117
|
+
const next = points[nextIdx]
|
|
118
|
+
|
|
119
|
+
const isEndpoint = !isClosedShape && (i === 0 || i === n - 1)
|
|
120
|
+
if (isEndpoint) {
|
|
121
|
+
// 折线两端不处理圆角
|
|
122
|
+
result.push(curr)
|
|
123
|
+
} else {
|
|
124
|
+
const arc = makeRoundCorner(prev, curr, next)
|
|
125
|
+
arc.forEach((p) => result.push(p))
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 去重处理:避免连续重复点
|
|
130
|
+
const dedup: Point[] = []
|
|
131
|
+
for (let i = 0; i < result.length; i++) {
|
|
132
|
+
const p = result[i]
|
|
133
|
+
if (
|
|
134
|
+
dedup.length === 0 ||
|
|
135
|
+
Math.hypot(
|
|
136
|
+
p.x - dedup[dedup.length - 1].x,
|
|
137
|
+
p.y - dedup[dedup.length - 1].y,
|
|
138
|
+
) > 1e-6
|
|
139
|
+
) {
|
|
140
|
+
dedup.push(p)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 闭合图形:确保首尾不重复闭合
|
|
145
|
+
if (isClosedShape && dedup.length > 1) {
|
|
146
|
+
const first = dedup[0]
|
|
147
|
+
const last = dedup[dedup.length - 1]
|
|
148
|
+
if (Math.hypot(first.x - last.x, first.y - last.y) < 1e-6) {
|
|
149
|
+
dedup.pop()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return dedup
|
|
154
|
+
}
|