@363045841yyt/klinechart-core 0.8.10-alpha.2 → 0.8.10
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/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/data-fetchers/dataBuffer.d.ts +0 -1
- package/dist/data-fetchers/dataBuffer.d.ts.map +1 -1
- package/dist/data-fetchers/dataBuffer.effects.d.ts +21 -0
- package/dist/data-fetchers/dataBuffer.effects.d.ts.map +1 -0
- package/dist/data-fetchers/dataBuffer.effects.js +55 -0
- package/dist/data-fetchers/dataBuffer.effects.js.map +1 -0
- package/dist/data-fetchers/dataBuffer.js +58 -93
- package/dist/data-fetchers/dataBuffer.js.map +1 -1
- package/dist/data-fetchers/index.d.ts +1 -0
- package/dist/data-fetchers/index.d.ts.map +1 -1
- package/dist/data-fetchers/index.js +1 -0
- package/dist/data-fetchers/index.js.map +1 -1
- package/dist/data-fetchers/timeShareBuffer.d.ts +2 -1
- package/dist/data-fetchers/timeShareBuffer.d.ts.map +1 -1
- package/dist/data-fetchers/timeShareBuffer.js +36 -14
- package/dist/data-fetchers/timeShareBuffer.js.map +1 -1
- package/dist/engine/data/chartDataManager.d.ts.map +1 -1
- package/dist/engine/data/chartDataManager.js +2 -1
- package/dist/engine/data/chartDataManager.js.map +1 -1
- package/dist/engine/drawing/AnchorCollector.d.ts +26 -0
- package/dist/engine/drawing/AnchorCollector.d.ts.map +1 -0
- package/dist/engine/drawing/AnchorCollector.js +47 -0
- package/dist/engine/drawing/AnchorCollector.js.map +1 -0
- package/dist/engine/drawing/DragHandler.d.ts +38 -0
- package/dist/engine/drawing/DragHandler.d.ts.map +1 -0
- package/dist/engine/drawing/DragHandler.js +92 -0
- package/dist/engine/drawing/DragHandler.js.map +1 -0
- package/dist/engine/drawing/DrawingState.d.ts +51 -0
- package/dist/engine/drawing/DrawingState.d.ts.map +1 -0
- package/dist/engine/drawing/DrawingState.js +115 -0
- package/dist/engine/drawing/DrawingState.js.map +1 -0
- package/dist/engine/drawing/HitTester.d.ts +59 -0
- package/dist/engine/drawing/HitTester.d.ts.map +1 -0
- package/dist/engine/drawing/HitTester.js +219 -0
- package/dist/engine/drawing/HitTester.js.map +1 -0
- package/dist/engine/drawing/PreviewRenderer.d.ts +26 -0
- package/dist/engine/drawing/PreviewRenderer.d.ts.map +1 -0
- package/dist/engine/drawing/PreviewRenderer.js +131 -0
- package/dist/engine/drawing/PreviewRenderer.js.map +1 -0
- package/dist/engine/drawing/coordinateUtils.d.ts +57 -0
- package/dist/engine/drawing/coordinateUtils.d.ts.map +1 -0
- package/dist/engine/drawing/coordinateUtils.js +103 -0
- package/dist/engine/drawing/coordinateUtils.js.map +1 -0
- package/dist/engine/drawing/index.d.ts.map +1 -1
- package/dist/engine/drawing/index.js +11 -3
- package/dist/engine/drawing/index.js.map +1 -1
- package/dist/engine/drawing/interaction.d.ts +44 -40
- package/dist/engine/drawing/interaction.d.ts.map +1 -1
- package/dist/engine/drawing/interaction.js +132 -571
- package/dist/engine/drawing/interaction.js.map +1 -1
- package/dist/engine/drawing/toolConfig.d.ts +24 -0
- package/dist/engine/drawing/toolConfig.d.ts.map +1 -0
- package/dist/engine/drawing/toolConfig.js +76 -0
- package/dist/engine/drawing/toolConfig.js.map +1 -0
- package/dist/plugin/types.d.ts +1 -0
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +4 -1
- package/src/controllers/index.ts +1 -0
- package/src/data-fetchers/__tests__/dataBuffer.test.ts +1 -3
- package/src/data-fetchers/dataBuffer.effects.ts +118 -0
- package/src/data-fetchers/dataBuffer.ts +45 -86
- package/src/data-fetchers/index.ts +7 -0
- package/src/data-fetchers/timeShareBuffer.ts +58 -19
- package/src/engine/__tests__/paneRenderer.resize.test.ts +3 -0
- package/src/engine/__tests__/subPaneManager.test.ts +13 -3
- package/src/engine/data/chartDataManager.ts +2 -1
- package/src/engine/drawing/AnchorCollector.ts +57 -0
- package/src/engine/drawing/DragHandler.ts +121 -0
- package/src/engine/drawing/DrawingState.ts +132 -0
- package/src/engine/drawing/HitTester.ts +288 -0
- package/src/engine/drawing/PreviewRenderer.ts +157 -0
- package/src/engine/drawing/coordinateUtils.ts +139 -0
- package/src/engine/drawing/index.ts +10 -3
- package/src/engine/drawing/interaction.ts +177 -687
- package/src/engine/drawing/toolConfig.ts +103 -0
- package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +1 -0
- package/src/engine/indicators/__tests__/stateComposer.test.ts +5 -4
- package/src/engine/renderers/Indicator/__tests__/createSubIndicatorRenderer.test.ts +1 -0
- package/src/plugin/types.ts +1 -0
- package/src/tokens/__tests__/tokens.test.ts +2 -1
- package/src/version.ts +1 -1
|
@@ -1,615 +1,203 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { DrawingState } from './DrawingState';
|
|
2
|
+
import { AnchorCollector } from './AnchorCollector';
|
|
3
|
+
import { PreviewRenderer } from './PreviewRenderer';
|
|
4
|
+
import { HitTester } from './HitTester';
|
|
5
|
+
import { DragHandler } from './DragHandler';
|
|
6
|
+
import { resolveAnchorFromPointer } from './coordinateUtils';
|
|
7
|
+
import { getAnchorCountForTool, getDrawingKind, CHANNEL_KINDS } from './toolConfig';
|
|
5
8
|
/**
|
|
6
|
-
* 绘图交互控制器
|
|
7
|
-
*
|
|
9
|
+
* 绘图交互控制器 v2 — 精简事件路由,组合子模块。
|
|
10
|
+
*
|
|
11
|
+
* ┌─────────────────────────────────────┐
|
|
12
|
+
* │ DrawingInteractionController │
|
|
13
|
+
* │ ├─ DrawingState (图元 CRUD) │
|
|
14
|
+
* │ ├─ AnchorCollector (锚点累积) │
|
|
15
|
+
* │ ├─ PreviewRenderer (预览构建) │
|
|
16
|
+
* │ ├─ HitTester (命中检测) │
|
|
17
|
+
* │ └─ DragHandler (拖拽管理) │
|
|
18
|
+
* └─────────────────────────────────────┘
|
|
8
19
|
*/
|
|
9
20
|
export class DrawingInteractionController {
|
|
10
|
-
adapter;
|
|
11
21
|
activeTool = 'cursor';
|
|
12
|
-
|
|
13
|
-
drawings = [];
|
|
22
|
+
adapter;
|
|
14
23
|
callbacks = {};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
'h-line',
|
|
21
|
-
'h-ray',
|
|
22
|
-
'v-line',
|
|
23
|
-
'crosshair-line',
|
|
24
|
-
];
|
|
25
|
-
// 双锚点工具列表
|
|
26
|
-
static DOUBLE_ANCHOR_TOOLS = [
|
|
27
|
-
'trend-line',
|
|
28
|
-
'ray',
|
|
29
|
-
'info-line',
|
|
30
|
-
'regression-channel',
|
|
31
|
-
];
|
|
32
|
-
// 三锚点工具列表
|
|
33
|
-
static TRIPLE_ANCHOR_TOOLS = [
|
|
34
|
-
'parallel-channel',
|
|
35
|
-
'flat-line',
|
|
36
|
-
'disjoint-channel',
|
|
37
|
-
];
|
|
24
|
+
drawingState;
|
|
25
|
+
anchorCollector;
|
|
26
|
+
previewRenderer;
|
|
27
|
+
hitTester;
|
|
28
|
+
dragHandler;
|
|
38
29
|
constructor(adapter) {
|
|
39
30
|
this.adapter = adapter;
|
|
40
|
-
|
|
31
|
+
this.drawingState = new DrawingState(adapter);
|
|
32
|
+
this.anchorCollector = new AnchorCollector();
|
|
33
|
+
this.previewRenderer = new PreviewRenderer();
|
|
34
|
+
this.hitTester = new HitTester();
|
|
35
|
+
this.dragHandler = new DragHandler();
|
|
36
|
+
}
|
|
37
|
+
// ============ 配置 ============
|
|
38
|
+
/** 注册回调(创建/选中/工具切换事件通知) */
|
|
41
39
|
setCallbacks(callbacks) {
|
|
42
40
|
this.callbacks = callbacks;
|
|
43
41
|
}
|
|
42
|
+
// ============ 工具状态 ============
|
|
43
|
+
/** 返回当前激活的绘图工具 ID */
|
|
44
44
|
getActiveTool() {
|
|
45
45
|
return this.activeTool;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* 切换绘图工具。
|
|
49
|
+
* 切换时自动清空锚点累积、移除预览、结束拖拽、清除选中,并通知外部。
|
|
50
|
+
*/
|
|
47
51
|
setTool(toolId) {
|
|
48
52
|
this.activeTool = toolId;
|
|
49
|
-
this.
|
|
50
|
-
this.removePreview();
|
|
51
|
-
this.
|
|
53
|
+
this.anchorCollector.reset();
|
|
54
|
+
this.drawingState.removePreview();
|
|
55
|
+
this.dragHandler.endDrag();
|
|
52
56
|
this.setSelected(null);
|
|
53
57
|
this.callbacks.onToolChange?.(toolId);
|
|
54
58
|
}
|
|
59
|
+
// ============ 图元 CRUD(委托 DrawingState) ============
|
|
60
|
+
/** 返回所有图元(含预览) */
|
|
55
61
|
getDrawings() {
|
|
56
|
-
return this.
|
|
62
|
+
return this.drawingState.getAll();
|
|
57
63
|
}
|
|
64
|
+
/** 整体替换图元列表 */
|
|
58
65
|
setDrawings(drawings) {
|
|
59
|
-
this.drawings
|
|
60
|
-
this.adapter.setDrawings(drawings);
|
|
66
|
+
this.drawingState.setDrawings(drawings);
|
|
61
67
|
}
|
|
68
|
+
/** 清空锚点累积、预览、拖拽状态及所有图元 */
|
|
62
69
|
clear() {
|
|
63
|
-
this.
|
|
64
|
-
this.removePreview();
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
67
|
-
}
|
|
68
|
-
getSelectedDrawing() {
|
|
69
|
-
if (!this.selectedDrawingId)
|
|
70
|
-
return null;
|
|
71
|
-
return this.drawings.find((d) => d.id === this.selectedDrawingId) ?? null;
|
|
70
|
+
this.anchorCollector.reset();
|
|
71
|
+
this.drawingState.removePreview();
|
|
72
|
+
this.dragHandler.endDrag();
|
|
73
|
+
this.drawingState.clear();
|
|
72
74
|
}
|
|
75
|
+
/** 更新指定图元的样式(合并) */
|
|
73
76
|
updateDrawingStyle(drawingId, style) {
|
|
74
|
-
this.
|
|
75
|
-
this.adapter.setDrawings(this.drawings);
|
|
77
|
+
this.drawingState.updateDrawingStyle(drawingId, style);
|
|
76
78
|
}
|
|
79
|
+
/** 删除指定图元 */
|
|
77
80
|
removeDrawing(drawingId) {
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
this.drawingState.removeDrawing(drawingId);
|
|
82
|
+
}
|
|
83
|
+
// ============ 选中状态 ============
|
|
84
|
+
/** 返回当前选中的图元 */
|
|
85
|
+
getSelectedDrawing() {
|
|
86
|
+
return this.drawingState.getSelected();
|
|
83
87
|
}
|
|
88
|
+
// ============ 事件处理 ============
|
|
84
89
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
90
|
+
* 指针移动事件入口。
|
|
91
|
+
* 拖拽中 → 委托 DragHandler 更新锚点;绘图模式 → 构建预览图元。
|
|
92
|
+
* @returns true 表示事件已消费,需要重绘
|
|
87
93
|
*/
|
|
88
94
|
onPointerMove(e, container) {
|
|
89
|
-
//
|
|
90
|
-
if (this.
|
|
91
|
-
|
|
95
|
+
// 1) 正在拖拽
|
|
96
|
+
if (this.dragHandler.isDragging()) {
|
|
97
|
+
const drawing = this.drawingState.getById(this.dragHandler.getDraggingDrawingId() ?? '');
|
|
98
|
+
if (!drawing) {
|
|
99
|
+
this.dragHandler.endDrag();
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const updated = this.dragHandler.handleDragMove(drawing, e, container, this.adapter);
|
|
103
|
+
if (!updated)
|
|
104
|
+
return false;
|
|
105
|
+
this.drawingState.addOrUpdate(updated);
|
|
106
|
+
return true;
|
|
92
107
|
}
|
|
93
|
-
//
|
|
108
|
+
// 2) 绘图工具预览
|
|
94
109
|
if (this.activeTool !== 'cursor') {
|
|
95
|
-
|
|
110
|
+
const anchor = resolveAnchorFromPointer(e, container, this.adapter);
|
|
111
|
+
if (!anchor) {
|
|
112
|
+
this.drawingState.removePreview();
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const preview = this.previewRenderer.buildPreview(this.activeTool, this.anchorCollector.pendingAnchors, anchor);
|
|
116
|
+
if (!preview) {
|
|
117
|
+
this.drawingState.removePreview();
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
this.drawingState.setPreview(preview);
|
|
121
|
+
return true;
|
|
96
122
|
}
|
|
97
123
|
return false;
|
|
98
124
|
}
|
|
99
125
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
126
|
+
* 指针按下事件入口。
|
|
127
|
+
* 光标模式 → 命中检测 + 选中 + 拖拽开始;绘图模式 → 创建或累积锚点。
|
|
128
|
+
* @returns true 表示事件已消费
|
|
102
129
|
*/
|
|
103
130
|
onPointerDown(e, container) {
|
|
104
|
-
//
|
|
131
|
+
// 光标模式:命中检测 → 选中 → 开始拖拽
|
|
105
132
|
if (this.activeTool === 'cursor') {
|
|
106
133
|
return this.handleCursorDown(e, container);
|
|
107
134
|
}
|
|
108
|
-
|
|
135
|
+
// 绘图模式
|
|
136
|
+
const anchor = resolveAnchorFromPointer(e, container, this.adapter);
|
|
109
137
|
if (!anchor)
|
|
110
138
|
return false;
|
|
111
|
-
|
|
112
|
-
|
|
139
|
+
const anchorCount = getAnchorCountForTool(this.activeTool);
|
|
140
|
+
// 单锚点工具:点击即创建
|
|
141
|
+
if (anchorCount === 1) {
|
|
113
142
|
this.createSingleAnchorDrawing(anchor);
|
|
114
143
|
return true;
|
|
115
144
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (this.pendingAnchors.length >= requiredAnchors) {
|
|
124
|
-
this.createMultiAnchorDrawing(this.pendingAnchors);
|
|
125
|
-
this.pendingAnchors = [];
|
|
145
|
+
// 多锚点工具:累积
|
|
146
|
+
if (anchorCount === 2 || anchorCount === 3) {
|
|
147
|
+
const result = this.anchorCollector.addAnchor(anchor, this.activeTool);
|
|
148
|
+
if (result) {
|
|
149
|
+
this.createMultiAnchorDrawing(result);
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
126
152
|
}
|
|
127
|
-
return
|
|
153
|
+
return false;
|
|
128
154
|
}
|
|
129
155
|
/**
|
|
130
|
-
*
|
|
131
|
-
* @returns
|
|
156
|
+
* 指针抬起事件入口。结束拖拽。
|
|
157
|
+
* @returns true 表示事件已消费
|
|
132
158
|
*/
|
|
133
159
|
onPointerUp(_e, _container) {
|
|
134
|
-
if (!this.
|
|
160
|
+
if (!this.dragHandler.isDragging())
|
|
135
161
|
return false;
|
|
136
|
-
this.
|
|
162
|
+
this.dragHandler.endDrag();
|
|
137
163
|
return true;
|
|
138
164
|
}
|
|
139
|
-
// ============
|
|
165
|
+
// ============ 私有方法 ============
|
|
166
|
+
/** 光标模式下指针按下:命中检测 → 选中 → 开始拖拽 */
|
|
140
167
|
handleCursorDown(e, container) {
|
|
141
168
|
const rect = container.getBoundingClientRect();
|
|
142
169
|
const mouseX = e.clientX - rect.left;
|
|
143
170
|
const mouseY = e.clientY - rect.top;
|
|
144
|
-
const hit = this.hitTest(mouseX, mouseY);
|
|
171
|
+
const hit = this.hitTester.hitTest(mouseX, mouseY, this.drawingState.getNonPreview(), this.adapter);
|
|
145
172
|
if (!hit) {
|
|
146
173
|
this.setSelected(null);
|
|
147
174
|
return false;
|
|
148
175
|
}
|
|
149
176
|
this.setSelected(hit.drawing);
|
|
150
|
-
this.
|
|
151
|
-
drawingId: hit.drawing.id,
|
|
152
|
-
anchorIndex: 'anchorIndex' in hit ? hit.anchorIndex : undefined,
|
|
153
|
-
snapshot: hit.drawing.anchors.map((a) => ({ ...a })),
|
|
154
|
-
startMouse: { x: mouseX, y: mouseY },
|
|
155
|
-
};
|
|
177
|
+
this.dragHandler.startDrag(hit.drawing, 'anchorIndex' in hit ? hit.anchorIndex : undefined, mouseX, mouseY);
|
|
156
178
|
return true;
|
|
157
179
|
}
|
|
158
|
-
|
|
159
|
-
if (!this.dragState)
|
|
160
|
-
return false;
|
|
161
|
-
const drawing = this.drawings.find((d) => d.id === this.dragState.drawingId);
|
|
162
|
-
if (!drawing) {
|
|
163
|
-
this.dragState = null;
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
const newAnchor = this.resolveAnchorFromPointer(e, container);
|
|
167
|
-
if (this.dragState.anchorIndex !== undefined) {
|
|
168
|
-
// 拖拽单个锚点
|
|
169
|
-
if (newAnchor) {
|
|
170
|
-
const idx = this.dragState.anchorIndex;
|
|
171
|
-
drawing.anchors[idx] = {
|
|
172
|
-
...drawing.anchors[idx],
|
|
173
|
-
index: newAnchor.index,
|
|
174
|
-
time: newAnchor.time,
|
|
175
|
-
price: newAnchor.price,
|
|
176
|
-
};
|
|
177
|
-
// flat-line:第三个锚点的 index/time 始终跟随第二个锚点
|
|
178
|
-
if (drawing.kind === 'flat-line' && idx === 1 && drawing.anchors.length >= 3) {
|
|
179
|
-
drawing.anchors[2] = {
|
|
180
|
-
...drawing.anchors[2],
|
|
181
|
-
index: newAnchor.index,
|
|
182
|
-
time: newAnchor.time,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
// 拖拽整条线:基于鼠标偏移量移动所有锚点
|
|
189
|
-
const rect = container.getBoundingClientRect();
|
|
190
|
-
const mouseX = e.clientX - rect.left;
|
|
191
|
-
const mouseY = e.clientY - rect.top;
|
|
192
|
-
const dx = mouseX - this.dragState.startMouse.x;
|
|
193
|
-
const dy = mouseY - this.dragState.startMouse.y;
|
|
194
|
-
for (let i = 0; i < drawing.anchors.length; i++) {
|
|
195
|
-
const snap = this.dragState.snapshot[i];
|
|
196
|
-
const snapScreen = this.anchorToScreen(snap);
|
|
197
|
-
if (!snapScreen)
|
|
198
|
-
continue;
|
|
199
|
-
const targetX = snapScreen.x + dx;
|
|
200
|
-
const targetY = snapScreen.y + dy;
|
|
201
|
-
const newFromScreen = this.screenToAnchor(targetX, targetY);
|
|
202
|
-
if (newFromScreen) {
|
|
203
|
-
drawing.anchors[i] = {
|
|
204
|
-
...drawing.anchors[i],
|
|
205
|
-
index: newFromScreen.index,
|
|
206
|
-
time: newFromScreen.time,
|
|
207
|
-
price: newFromScreen.price,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
this.adapter.setDrawings([...this.drawings]);
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
// ============ 预览模式 ============
|
|
216
|
-
handlePreviewMove(e, container) {
|
|
217
|
-
const anchor = this.resolveAnchorFromPointer(e, container);
|
|
218
|
-
if (!anchor) {
|
|
219
|
-
this.removePreview();
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
const isSingle = DrawingInteractionController.SINGLE_ANCHOR_TOOLS.includes(this.activeTool);
|
|
223
|
-
const isDouble = DrawingInteractionController.DOUBLE_ANCHOR_TOOLS.includes(this.activeTool);
|
|
224
|
-
const isTriple = DrawingInteractionController.TRIPLE_ANCHOR_TOOLS.includes(this.activeTool);
|
|
225
|
-
if (!isSingle && !isDouble && !isTriple)
|
|
226
|
-
return false;
|
|
227
|
-
let preview;
|
|
228
|
-
if (isSingle) {
|
|
229
|
-
preview = {
|
|
230
|
-
id: this.previewDrawingId,
|
|
231
|
-
kind: this.getDrawingKind(this.activeTool),
|
|
232
|
-
paneId: 'main',
|
|
233
|
-
visible: true,
|
|
234
|
-
anchors: [{ id: `${this.previewDrawingId}-a`, index: anchor.index, time: anchor.time, price: anchor.price }],
|
|
235
|
-
params: {},
|
|
236
|
-
style: {
|
|
237
|
-
stroke: '#2962ff',
|
|
238
|
-
strokeWidth: 1,
|
|
239
|
-
strokeStyle: 'dashed',
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
else if (isDouble && this.pendingAnchors.length >= 1) {
|
|
244
|
-
preview = {
|
|
245
|
-
id: this.previewDrawingId,
|
|
246
|
-
kind: this.getDrawingKind(this.activeTool),
|
|
247
|
-
paneId: 'main',
|
|
248
|
-
visible: true,
|
|
249
|
-
anchors: [
|
|
250
|
-
{ id: `${this.previewDrawingId}-a`, index: this.pendingAnchors[0].index, time: this.pendingAnchors[0].time, price: this.pendingAnchors[0].price },
|
|
251
|
-
{ id: `${this.previewDrawingId}-b`, index: anchor.index, time: anchor.time, price: anchor.price },
|
|
252
|
-
],
|
|
253
|
-
params: this.activeTool === 'regression-channel' ? { sigma: 2 } : {},
|
|
254
|
-
style: {
|
|
255
|
-
stroke: '#2962ff',
|
|
256
|
-
strokeWidth: 1,
|
|
257
|
-
strokeStyle: 'dashed',
|
|
258
|
-
...(this.activeTool === 'regression-channel' ? { fillOpacity: 0.1 } : {}),
|
|
259
|
-
},
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
else if (isTriple) {
|
|
263
|
-
if (this.pendingAnchors.length === 0)
|
|
264
|
-
return false;
|
|
265
|
-
if (this.pendingAnchors.length === 1) {
|
|
266
|
-
// 修复:用 trend-line 渲染线段预览(2 个锚点),三锚点工具的 definition 需要 3 个锚点才能渲染
|
|
267
|
-
preview = {
|
|
268
|
-
id: this.previewDrawingId,
|
|
269
|
-
kind: 'trend-line',
|
|
270
|
-
paneId: 'main',
|
|
271
|
-
visible: true,
|
|
272
|
-
anchors: [
|
|
273
|
-
{ id: `${this.previewDrawingId}-a`, index: this.pendingAnchors[0].index, time: this.pendingAnchors[0].time, price: this.pendingAnchors[0].price },
|
|
274
|
-
{ id: `${this.previewDrawingId}-b`, index: anchor.index, time: anchor.time, price: anchor.price },
|
|
275
|
-
],
|
|
276
|
-
params: {},
|
|
277
|
-
style: {
|
|
278
|
-
stroke: '#2962ff',
|
|
279
|
-
strokeWidth: 1,
|
|
280
|
-
strokeStyle: 'dashed',
|
|
281
|
-
},
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
const thirdAnchor = this.activeTool === 'flat-line'
|
|
286
|
-
? {
|
|
287
|
-
id: `${this.previewDrawingId}-c`,
|
|
288
|
-
index: this.pendingAnchors[1].index,
|
|
289
|
-
time: this.pendingAnchors[1].time,
|
|
290
|
-
price: anchor.price,
|
|
291
|
-
}
|
|
292
|
-
: {
|
|
293
|
-
id: `${this.previewDrawingId}-c`,
|
|
294
|
-
index: anchor.index,
|
|
295
|
-
time: anchor.time,
|
|
296
|
-
price: anchor.price,
|
|
297
|
-
};
|
|
298
|
-
preview = {
|
|
299
|
-
id: this.previewDrawingId,
|
|
300
|
-
kind: this.getDrawingKind(this.activeTool),
|
|
301
|
-
paneId: 'main',
|
|
302
|
-
visible: true,
|
|
303
|
-
anchors: [
|
|
304
|
-
{ id: `${this.previewDrawingId}-a`, index: this.pendingAnchors[0].index, time: this.pendingAnchors[0].time, price: this.pendingAnchors[0].price },
|
|
305
|
-
{ id: `${this.previewDrawingId}-b`, index: this.pendingAnchors[1].index, time: this.pendingAnchors[1].time, price: this.pendingAnchors[1].price },
|
|
306
|
-
thirdAnchor,
|
|
307
|
-
],
|
|
308
|
-
params: {},
|
|
309
|
-
style: {
|
|
310
|
-
stroke: '#2962ff',
|
|
311
|
-
strokeWidth: 1,
|
|
312
|
-
strokeStyle: 'dashed',
|
|
313
|
-
fillOpacity: 0.1,
|
|
314
|
-
},
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
this.drawings = this.drawings.filter((d) => d.id !== this.previewDrawingId);
|
|
322
|
-
this.drawings = [...this.drawings, preview];
|
|
323
|
-
this.adapter.setDrawings(this.drawings);
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
// ============ 命中检测 ============
|
|
327
|
-
hitTest(mouseX, mouseY) {
|
|
328
|
-
const drawings = this.drawings.filter((d) => d.id !== this.previewDrawingId && d.visible);
|
|
329
|
-
const regressionGeometryCache = new Map();
|
|
330
|
-
// 锚点优先
|
|
331
|
-
for (const drawing of drawings) {
|
|
332
|
-
// regression-channel:回归线端点也是可拖拽区域
|
|
333
|
-
if (drawing.kind === 'regression-channel' && drawing.anchors.length >= 2) {
|
|
334
|
-
const hit = this.hitTestRegressionEndpoints(drawing, mouseX, mouseY, regressionGeometryCache);
|
|
335
|
-
if (hit)
|
|
336
|
-
return hit;
|
|
337
|
-
}
|
|
338
|
-
for (let i = 0; i < drawing.anchors.length; i++) {
|
|
339
|
-
const screen = this.anchorToScreen(drawing.anchors[i]);
|
|
340
|
-
if (!screen)
|
|
341
|
-
continue;
|
|
342
|
-
const dist = Math.hypot(mouseX - screen.x, mouseY - screen.y);
|
|
343
|
-
if (dist <= ANCHOR_HIT_RADIUS) {
|
|
344
|
-
return { drawing, anchorIndex: i };
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
// 线条其次
|
|
349
|
-
for (const drawing of drawings) {
|
|
350
|
-
const segments = this.getDrawingLineSegments(drawing, regressionGeometryCache);
|
|
351
|
-
for (const seg of segments) {
|
|
352
|
-
const dist = pointToSegmentDist(mouseX, mouseY, seg.a, seg.b);
|
|
353
|
-
if (dist <= LINE_HIT_RADIUS) {
|
|
354
|
-
return { drawing };
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
getDrawingLineSegments(drawing, regressionGeometryCache) {
|
|
361
|
-
const viewport = this.adapter.getViewport();
|
|
362
|
-
if (!viewport)
|
|
363
|
-
return [];
|
|
364
|
-
if (drawing.kind === 'regression-channel') {
|
|
365
|
-
return this.getRegressionChannelGeometry(drawing, regressionGeometryCache)?.segments ?? [];
|
|
366
|
-
}
|
|
367
|
-
// 单锚点图元:根据 kind 构造屏幕线段
|
|
368
|
-
if (drawing.anchors.length === 1) {
|
|
369
|
-
const screen = this.anchorToScreen(drawing.anchors[0]);
|
|
370
|
-
if (!screen)
|
|
371
|
-
return [];
|
|
372
|
-
const paneInfo = this.adapter.getPaneInfo('main');
|
|
373
|
-
if (!paneInfo)
|
|
374
|
-
return [];
|
|
375
|
-
const right = viewport.plotWidth;
|
|
376
|
-
const bottom = paneInfo.height;
|
|
377
|
-
switch (drawing.kind) {
|
|
378
|
-
case 'horizontal-line':
|
|
379
|
-
return [{ a: { x: 0, y: screen.y }, b: { x: right, y: screen.y } }];
|
|
380
|
-
case 'horizontal-ray':
|
|
381
|
-
return [{ a: screen, b: { x: right, y: screen.y } }];
|
|
382
|
-
case 'vertical-line':
|
|
383
|
-
return [{ a: { x: screen.x, y: 0 }, b: { x: screen.x, y: bottom } }];
|
|
384
|
-
case 'cross-line':
|
|
385
|
-
return [
|
|
386
|
-
{ a: { x: 0, y: screen.y }, b: { x: right, y: screen.y } },
|
|
387
|
-
{ a: { x: screen.x, y: 0 }, b: { x: screen.x, y: bottom } },
|
|
388
|
-
];
|
|
389
|
-
default:
|
|
390
|
-
return [];
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
// 多锚点图元:按 kind 特殊处理
|
|
394
|
-
const points = drawing.anchors.map((a) => this.anchorToScreen(a)).filter(Boolean);
|
|
395
|
-
if (points.length < 2)
|
|
396
|
-
return [];
|
|
397
|
-
const segments = [];
|
|
398
|
-
if (points.length === 2) {
|
|
399
|
-
const a = points[0];
|
|
400
|
-
const b = points[1];
|
|
401
|
-
// 其他双锚点工具:标准线段
|
|
402
|
-
const dx = b.x - a.x;
|
|
403
|
-
const dy = b.y - a.y;
|
|
404
|
-
let start = a;
|
|
405
|
-
let end = b;
|
|
406
|
-
const extend = this.getExtendMode(drawing);
|
|
407
|
-
const maxLen = Math.max(viewport.plotWidth, viewport.plotHeight) * 4;
|
|
408
|
-
if (extend === 'right' || extend === 'both') {
|
|
409
|
-
end = { x: b.x + dx * maxLen, y: b.y + dy * maxLen };
|
|
410
|
-
}
|
|
411
|
-
if (extend === 'left' || extend === 'both') {
|
|
412
|
-
start = { x: a.x - dx * maxLen, y: a.y - dy * maxLen };
|
|
413
|
-
}
|
|
414
|
-
segments.push({ a: start, b: end });
|
|
415
|
-
}
|
|
416
|
-
else if (points.length >= 3) {
|
|
417
|
-
switch (drawing.kind) {
|
|
418
|
-
case 'parallel-channel': {
|
|
419
|
-
const [p1, p2, p3] = points;
|
|
420
|
-
const dx = p2.x - p1.x;
|
|
421
|
-
const dy = p2.y - p1.y;
|
|
422
|
-
const p4 = { x: p3.x + dx, y: p3.y + dy };
|
|
423
|
-
segments.push({ a: p1, b: p2 }, { a: p3, b: p4 });
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
426
|
-
case 'flat-line': {
|
|
427
|
-
const [p1, p2, p3] = points;
|
|
428
|
-
const h1 = { x: p1.x, y: p3.y };
|
|
429
|
-
const h2 = { x: p2.x, y: p3.y };
|
|
430
|
-
segments.push({ a: p1, b: p2 });
|
|
431
|
-
segments.push({ a: h1, b: h2 });
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
case 'disjoint-channel': {
|
|
435
|
-
const [p1, p2, p3] = points;
|
|
436
|
-
const dx = p2.x - p1.x;
|
|
437
|
-
const dy = p2.y - p1.y;
|
|
438
|
-
const p4 = { x: p3.x + dx, y: p3.y - dy };
|
|
439
|
-
segments.push({ a: p1, b: p2 });
|
|
440
|
-
segments.push({ a: p3, b: p4 });
|
|
441
|
-
break;
|
|
442
|
-
}
|
|
443
|
-
default:
|
|
444
|
-
for (let i = 0; i < points.length - 1; i++) {
|
|
445
|
-
segments.push({ a: points[i], b: points[i + 1] });
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return segments;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* regression-channel 专用:回归线端点也是可拖拽的锚点区域
|
|
453
|
-
* 回归线端点可能远离存储的锚点,需要额外检测
|
|
454
|
-
*/
|
|
455
|
-
hitTestRegressionEndpoints(drawing, mouseX, mouseY, regressionGeometryCache) {
|
|
456
|
-
const geometry = this.getRegressionChannelGeometry(drawing, regressionGeometryCache);
|
|
457
|
-
if (!geometry)
|
|
458
|
-
return null;
|
|
459
|
-
for (const endpoint of geometry.endpoints) {
|
|
460
|
-
const dist = Math.hypot(mouseX - endpoint.point.x, mouseY - endpoint.point.y);
|
|
461
|
-
if (dist <= ANCHOR_HIT_RADIUS) {
|
|
462
|
-
return { drawing, anchorIndex: endpoint.anchorIndex };
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return null;
|
|
466
|
-
}
|
|
467
|
-
getRegressionChannelGeometry(drawing, regressionGeometryCache) {
|
|
468
|
-
const cached = regressionGeometryCache?.get(drawing.id);
|
|
469
|
-
if (cached !== undefined)
|
|
470
|
-
return cached;
|
|
471
|
-
const data = this.adapter.getData();
|
|
472
|
-
if (data.length === 0 || drawing.anchors.length < 2) {
|
|
473
|
-
regressionGeometryCache?.set(drawing.id, null);
|
|
474
|
-
return null;
|
|
475
|
-
}
|
|
476
|
-
const firstIndex = Math.round(drawing.anchors[0].index);
|
|
477
|
-
const secondIndex = Math.round(drawing.anchors[1].index);
|
|
478
|
-
const clampedFirst = Math.min(Math.max(firstIndex, 0), data.length - 1);
|
|
479
|
-
const clampedSecond = Math.min(Math.max(secondIndex, 0), data.length - 1);
|
|
480
|
-
const startIndex = Math.min(clampedFirst, clampedSecond);
|
|
481
|
-
const endIndex = Math.max(clampedFirst, clampedSecond);
|
|
482
|
-
const slice = data.slice(startIndex, endIndex + 1);
|
|
483
|
-
const regression = computeLinearRegression(slice.map((item) => item.close));
|
|
484
|
-
if (!regression) {
|
|
485
|
-
regressionGeometryCache?.set(drawing.id, null);
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
const sigma = drawing.params?.sigma ?? 2;
|
|
489
|
-
const offset = regression.stdDev * sigma;
|
|
490
|
-
const firstValue = regression.intercept;
|
|
491
|
-
const lastValue = regression.intercept + regression.slope * (slice.length - 1);
|
|
492
|
-
const middleStart = this.anchorToScreen({ id: '', index: firstIndex, price: firstValue });
|
|
493
|
-
const middleEnd = this.anchorToScreen({ id: '', index: secondIndex, price: lastValue });
|
|
494
|
-
const upperStart = this.anchorToScreen({ id: '', index: firstIndex, price: firstValue + offset });
|
|
495
|
-
const upperEnd = this.anchorToScreen({ id: '', index: secondIndex, price: lastValue + offset });
|
|
496
|
-
const lowerStart = this.anchorToScreen({ id: '', index: firstIndex, price: firstValue - offset });
|
|
497
|
-
const lowerEnd = this.anchorToScreen({ id: '', index: secondIndex, price: lastValue - offset });
|
|
498
|
-
const segments = [];
|
|
499
|
-
if (middleStart && middleEnd)
|
|
500
|
-
segments.push({ a: middleStart, b: middleEnd });
|
|
501
|
-
if (upperStart && upperEnd)
|
|
502
|
-
segments.push({ a: upperStart, b: upperEnd });
|
|
503
|
-
if (lowerStart && lowerEnd)
|
|
504
|
-
segments.push({ a: lowerStart, b: lowerEnd });
|
|
505
|
-
const endpoints = [];
|
|
506
|
-
if (middleStart)
|
|
507
|
-
endpoints.push({ point: middleStart, anchorIndex: 0 });
|
|
508
|
-
if (middleEnd)
|
|
509
|
-
endpoints.push({ point: middleEnd, anchorIndex: 1 });
|
|
510
|
-
if (upperStart)
|
|
511
|
-
endpoints.push({ point: upperStart, anchorIndex: 0 });
|
|
512
|
-
if (upperEnd)
|
|
513
|
-
endpoints.push({ point: upperEnd, anchorIndex: 1 });
|
|
514
|
-
if (lowerStart)
|
|
515
|
-
endpoints.push({ point: lowerStart, anchorIndex: 0 });
|
|
516
|
-
if (lowerEnd)
|
|
517
|
-
endpoints.push({ point: lowerEnd, anchorIndex: 1 });
|
|
518
|
-
const geometry = { segments, endpoints };
|
|
519
|
-
regressionGeometryCache?.set(drawing.id, geometry);
|
|
520
|
-
return geometry;
|
|
521
|
-
}
|
|
522
|
-
getExtendMode(drawing) {
|
|
523
|
-
switch (drawing.kind) {
|
|
524
|
-
case 'ray':
|
|
525
|
-
return 'right';
|
|
526
|
-
case 'extended-line':
|
|
527
|
-
return 'both';
|
|
528
|
-
default:
|
|
529
|
-
return 'none';
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
// ============ 坐标转换 ============
|
|
533
|
-
anchorToScreen(anchor) {
|
|
534
|
-
const viewport = this.adapter.getViewport();
|
|
535
|
-
if (!viewport)
|
|
536
|
-
return null;
|
|
537
|
-
const { kWidth, kGap } = this.adapter.getKWidthKGap();
|
|
538
|
-
const dpr = this.adapter.getCurrentDpr();
|
|
539
|
-
const { startXPx, unitPx } = getPhysicalKLineConfig(kWidth, kGap, dpr);
|
|
540
|
-
if (!Number.isFinite(anchor.index))
|
|
541
|
-
return null;
|
|
542
|
-
const x = (startXPx + anchor.index * unitPx + (unitPx - 1) / 2) / dpr - viewport.scrollLeft;
|
|
543
|
-
const y = this.adapter.priceToY('main', anchor.price);
|
|
544
|
-
return { x, y };
|
|
545
|
-
}
|
|
546
|
-
screenToAnchor(screenX, screenY) {
|
|
547
|
-
const data = this.adapter.getData();
|
|
548
|
-
const viewport = this.adapter.getViewport();
|
|
549
|
-
if (!viewport || data.length === 0)
|
|
550
|
-
return null;
|
|
551
|
-
const logicalIndex = this.adapter.getLogicalIndexAtX(screenX);
|
|
552
|
-
if (logicalIndex === null)
|
|
553
|
-
return null;
|
|
554
|
-
const paneInfo = this.adapter.getPaneInfo('main');
|
|
555
|
-
if (!paneInfo)
|
|
556
|
-
return null;
|
|
557
|
-
const timestamp = this.adapter.getTimestampAtLogicalIndex(logicalIndex) ?? undefined;
|
|
558
|
-
return {
|
|
559
|
-
index: logicalIndex,
|
|
560
|
-
time: timestamp ?? undefined,
|
|
561
|
-
price: this.adapter.yToPrice('main', screenY - paneInfo.top),
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
// ============ 工具方法 ============
|
|
180
|
+
/** 设置选中图元并通知回调 */
|
|
565
181
|
setSelected(drawing) {
|
|
566
|
-
|
|
567
|
-
if (this.selectedDrawingId === newId)
|
|
568
|
-
return;
|
|
569
|
-
this.selectedDrawingId = newId;
|
|
570
|
-
this.adapter.setSelectedDrawingId(newId);
|
|
182
|
+
this.drawingState.setSelected(drawing);
|
|
571
183
|
this.callbacks.onDrawingSelected?.(drawing);
|
|
572
184
|
}
|
|
573
|
-
|
|
574
|
-
if (!this.drawings.some((d) => d.id === this.previewDrawingId))
|
|
575
|
-
return;
|
|
576
|
-
this.drawings = this.drawings.filter((d) => d.id !== this.previewDrawingId);
|
|
577
|
-
this.adapter.setDrawings(this.drawings);
|
|
578
|
-
}
|
|
579
|
-
resolveAnchorFromPointer(e, container) {
|
|
580
|
-
const data = this.adapter.getData();
|
|
581
|
-
const viewport = this.adapter.getViewport();
|
|
582
|
-
if (!viewport || data.length === 0)
|
|
583
|
-
return null;
|
|
584
|
-
const rect = container.getBoundingClientRect();
|
|
585
|
-
const mouseX = e.clientX - rect.left;
|
|
586
|
-
const mouseY = e.clientY - rect.top;
|
|
587
|
-
if (mouseX < 0 || mouseY < 0 || mouseX > viewport.plotWidth || mouseY > viewport.plotHeight) {
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
const paneInfo = this.adapter.getPaneInfo('main');
|
|
591
|
-
if (!paneInfo)
|
|
592
|
-
return null;
|
|
593
|
-
if (mouseY < paneInfo.top || mouseY > paneInfo.top + paneInfo.height)
|
|
594
|
-
return null;
|
|
595
|
-
const logicalIndex = this.adapter.getLogicalIndexAtX(mouseX);
|
|
596
|
-
if (logicalIndex === null)
|
|
597
|
-
return null;
|
|
598
|
-
const timestamp = this.adapter.getTimestampAtLogicalIndex(logicalIndex) ?? undefined;
|
|
599
|
-
return {
|
|
600
|
-
index: logicalIndex,
|
|
601
|
-
time: timestamp ?? undefined,
|
|
602
|
-
price: this.adapter.yToPrice('main', mouseY - paneInfo.top),
|
|
603
|
-
};
|
|
604
|
-
}
|
|
185
|
+
/** 单锚点工具:点击即创建图元,完成后切回光标模式 */
|
|
605
186
|
createSingleAnchorDrawing(anchor) {
|
|
606
|
-
this.
|
|
187
|
+
this.drawingState.removePreview();
|
|
607
188
|
const drawing = {
|
|
608
189
|
id: `drawing-${Date.now()}`,
|
|
609
|
-
kind:
|
|
190
|
+
kind: getDrawingKind(this.activeTool),
|
|
610
191
|
paneId: 'main',
|
|
611
192
|
visible: true,
|
|
612
|
-
anchors: [
|
|
193
|
+
anchors: [
|
|
194
|
+
{
|
|
195
|
+
id: `${Date.now()}-a`,
|
|
196
|
+
index: anchor.index,
|
|
197
|
+
time: anchor.time,
|
|
198
|
+
price: anchor.price,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
613
201
|
params: {},
|
|
614
202
|
style: {
|
|
615
203
|
stroke: '#2962ff',
|
|
@@ -617,15 +205,15 @@ export class DrawingInteractionController {
|
|
|
617
205
|
strokeStyle: 'solid',
|
|
618
206
|
},
|
|
619
207
|
};
|
|
620
|
-
this.
|
|
621
|
-
this.adapter.setDrawings(this.drawings);
|
|
208
|
+
this.drawingState.addOrUpdate(drawing);
|
|
622
209
|
this.callbacks.onDrawingCreated?.(drawing);
|
|
623
210
|
this.activeTool = 'cursor';
|
|
624
211
|
this.callbacks.onToolChange?.('cursor');
|
|
625
212
|
}
|
|
213
|
+
/** 多锚点工具(2-3 锚点):锚点累积满后创建图元,完成后切回光标模式 */
|
|
626
214
|
createMultiAnchorDrawing(anchors) {
|
|
627
|
-
this.
|
|
628
|
-
const kind =
|
|
215
|
+
this.drawingState.removePreview();
|
|
216
|
+
const kind = getDrawingKind(this.activeTool);
|
|
629
217
|
const params = kind === 'regression-channel' ? { sigma: 2 } : {};
|
|
630
218
|
const normalizedAnchors = kind === 'flat-line' && anchors.length >= 3
|
|
631
219
|
? [
|
|
@@ -638,7 +226,7 @@ export class DrawingInteractionController {
|
|
|
638
226
|
},
|
|
639
227
|
]
|
|
640
228
|
: anchors;
|
|
641
|
-
const isChannel =
|
|
229
|
+
const isChannel = CHANNEL_KINDS.includes(kind);
|
|
642
230
|
const drawing = {
|
|
643
231
|
id: `drawing-${Date.now()}`,
|
|
644
232
|
kind,
|
|
@@ -658,37 +246,10 @@ export class DrawingInteractionController {
|
|
|
658
246
|
...(isChannel ? { fillOpacity: 0.1 } : {}),
|
|
659
247
|
},
|
|
660
248
|
};
|
|
661
|
-
this.
|
|
662
|
-
this.adapter.setDrawings(this.drawings);
|
|
249
|
+
this.drawingState.addOrUpdate(drawing);
|
|
663
250
|
this.callbacks.onDrawingCreated?.(drawing);
|
|
664
251
|
this.activeTool = 'cursor';
|
|
665
252
|
this.callbacks.onToolChange?.('cursor');
|
|
666
253
|
}
|
|
667
|
-
getDrawingKind(toolId) {
|
|
668
|
-
switch (toolId) {
|
|
669
|
-
case 'cursor':
|
|
670
|
-
throw new Error('cursor is not a drawing kind');
|
|
671
|
-
case 'h-line':
|
|
672
|
-
return 'horizontal-line';
|
|
673
|
-
case 'h-ray':
|
|
674
|
-
return 'horizontal-ray';
|
|
675
|
-
case 'v-line':
|
|
676
|
-
return 'vertical-line';
|
|
677
|
-
case 'crosshair-line':
|
|
678
|
-
return 'cross-line';
|
|
679
|
-
default:
|
|
680
|
-
return toolId;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
function pointToSegmentDist(px, py, a, b) {
|
|
685
|
-
const dx = b.x - a.x;
|
|
686
|
-
const dy = b.y - a.y;
|
|
687
|
-
const lenSq = dx * dx + dy * dy;
|
|
688
|
-
if (lenSq === 0)
|
|
689
|
-
return Math.hypot(px - a.x, py - a.y);
|
|
690
|
-
let t = ((px - a.x) * dx + (py - a.y) * dy) / lenSq;
|
|
691
|
-
t = Math.max(0, Math.min(1, t));
|
|
692
|
-
return Math.hypot(px - (a.x + t * dx), py - (a.y + t * dy));
|
|
693
254
|
}
|
|
694
255
|
//# sourceMappingURL=interaction.js.map
|