@logicflow/core 2.2.0-alpha.2 → 2.2.0-alpha.3
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 +6 -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 +17 -6
- 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 +6 -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 +14 -10
- package/es/view/node/HtmlNode.js +2 -4
- 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 +18 -7
- 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 +6 -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 +14 -10
- package/lib/view/node/HtmlNode.js +1 -3
- 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/GraphModel.ts +19 -7
- 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 +9 -1
- 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 +6 -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 +8 -3
- package/src/view/node/HtmlNode.tsx +27 -10
- 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
|
|
@@ -1516,9 +1527,10 @@ export class GraphModel {
|
|
|
1516
1527
|
*/
|
|
1517
1528
|
@action setTheme(
|
|
1518
1529
|
style: Partial<LogicFlow.Theme>,
|
|
1519
|
-
themeMode?:
|
|
1530
|
+
themeMode?: LogicFlow.ThemeMode | string,
|
|
1520
1531
|
) {
|
|
1521
1532
|
if (themeMode) {
|
|
1533
|
+
this.themeMode = themeMode
|
|
1522
1534
|
// 修改背景颜色
|
|
1523
1535
|
backgroundModeMap[themeMode] &&
|
|
1524
1536
|
this.updateBackgroundOptions({
|
|
@@ -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()
|
|
@@ -433,7 +433,15 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
|
|
|
433
433
|
|
|
434
434
|
getResizeOutlineStyle() {
|
|
435
435
|
const { resizeOutline } = this.graphModel.theme
|
|
436
|
-
|
|
436
|
+
let attributes = { ...resizeOutline }
|
|
437
|
+
if (this.isHovered) {
|
|
438
|
+
const hoverStyle = resizeOutline.hover || {}
|
|
439
|
+
attributes = {
|
|
440
|
+
...attributes,
|
|
441
|
+
...hoverStyle,
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return cloneDeep(attributes)
|
|
437
445
|
}
|
|
438
446
|
|
|
439
447
|
/**
|
|
@@ -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 {
|
|
@@ -86,9 +86,10 @@ export default class MultipleSelect extends Component<IToolProps> {
|
|
|
86
86
|
|
|
87
87
|
render() {
|
|
88
88
|
const {
|
|
89
|
-
graphModel: { selectElements, transformModel },
|
|
89
|
+
graphModel: { selectElements, transformModel, theme },
|
|
90
90
|
} = this.props
|
|
91
91
|
const { SCALE_X, SCALE_Y } = this.props.lf.getTransform()
|
|
92
|
+
const { xPadding = 8, yPadding = 8 } = theme.multiSelect || {}
|
|
92
93
|
if (selectElements.size <= 1) return
|
|
93
94
|
let x = Number.MAX_SAFE_INTEGER
|
|
94
95
|
let y = Number.MAX_SAFE_INTEGER
|
|
@@ -113,10 +114,10 @@ export default class MultipleSelect extends Component<IToolProps> {
|
|
|
113
114
|
;[x, y] = transformModel.CanvasPointToHtmlPoint([x, y])
|
|
114
115
|
;[x1, y1] = transformModel.CanvasPointToHtmlPoint([x1, y1])
|
|
115
116
|
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`,
|
|
117
|
+
left: `${x - (20 * SCALE_X) / 2 - xPadding / 2}px`,
|
|
118
|
+
top: `${y - (20 * SCALE_Y) / 2 - yPadding / 2}px`,
|
|
119
|
+
width: `${x1 - x + 20 * SCALE_X + xPadding}px`,
|
|
120
|
+
height: `${y1 - y + 20 * SCALE_Y + yPadding}px`,
|
|
120
121
|
'border-width': `${2 * SCALE_X}px`,
|
|
121
122
|
}
|
|
122
123
|
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
|
+
}
|