aifastdb-devplan 1.5.0 → 1.6.1
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/autopilot.d.ts +58 -0
- package/dist/autopilot.d.ts.map +1 -0
- package/dist/autopilot.js +250 -0
- package/dist/autopilot.js.map +1 -0
- package/dist/dev-plan-document-store.d.ts +2 -0
- package/dist/dev-plan-document-store.d.ts.map +1 -1
- package/dist/dev-plan-document-store.js +3 -0
- package/dist/dev-plan-document-store.js.map +1 -1
- package/dist/dev-plan-factory.d.ts +69 -3
- package/dist/dev-plan-factory.d.ts.map +1 -1
- package/dist/dev-plan-factory.js +111 -18
- package/dist/dev-plan-factory.js.map +1 -1
- package/dist/dev-plan-graph-store.d.ts +15 -0
- package/dist/dev-plan-graph-store.d.ts.map +1 -1
- package/dist/dev-plan-graph-store.js +57 -2
- package/dist/dev-plan-graph-store.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/index.d.ts +3 -0
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +278 -4
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/types.d.ts +72 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -1
- package/dist/types.js.map +1 -1
- package/dist/visualize/graph-canvas/api-compat.d.ts +20 -0
- package/dist/visualize/graph-canvas/api-compat.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/api-compat.js +334 -0
- package/dist/visualize/graph-canvas/api-compat.js.map +1 -0
- package/dist/visualize/graph-canvas/clusterer.d.ts +16 -0
- package/dist/visualize/graph-canvas/clusterer.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/clusterer.js +460 -0
- package/dist/visualize/graph-canvas/clusterer.js.map +1 -0
- package/dist/visualize/graph-canvas/core.d.ts +11 -0
- package/dist/visualize/graph-canvas/core.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/core.js +844 -0
- package/dist/visualize/graph-canvas/core.js.map +1 -0
- package/dist/visualize/graph-canvas/index.d.ts +22 -0
- package/dist/visualize/graph-canvas/index.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/index.js +69 -0
- package/dist/visualize/graph-canvas/index.js.map +1 -0
- package/dist/visualize/graph-canvas/interaction.d.ts +13 -0
- package/dist/visualize/graph-canvas/interaction.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/interaction.js +446 -0
- package/dist/visualize/graph-canvas/interaction.js.map +1 -0
- package/dist/visualize/graph-canvas/layout-worker.d.ts +17 -0
- package/dist/visualize/graph-canvas/layout-worker.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/layout-worker.js +541 -0
- package/dist/visualize/graph-canvas/layout-worker.js.map +1 -0
- package/dist/visualize/graph-canvas/lod.d.ts +10 -0
- package/dist/visualize/graph-canvas/lod.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/lod.js +111 -0
- package/dist/visualize/graph-canvas/lod.js.map +1 -0
- package/dist/visualize/graph-canvas/renderer.d.ts +12 -0
- package/dist/visualize/graph-canvas/renderer.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/renderer.js +682 -0
- package/dist/visualize/graph-canvas/renderer.js.map +1 -0
- package/dist/visualize/graph-canvas/spatial-index.d.ts +13 -0
- package/dist/visualize/graph-canvas/spatial-index.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/spatial-index.js +482 -0
- package/dist/visualize/graph-canvas/spatial-index.js.map +1 -0
- package/dist/visualize/graph-canvas/styles.d.ts +11 -0
- package/dist/visualize/graph-canvas/styles.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/styles.js +137 -0
- package/dist/visualize/graph-canvas/styles.js.map +1 -0
- package/dist/visualize/graph-canvas/viewport.d.ts +17 -0
- package/dist/visualize/graph-canvas/viewport.d.ts.map +1 -0
- package/dist/visualize/graph-canvas/viewport.js +375 -0
- package/dist/visualize/graph-canvas/viewport.js.map +1 -0
- package/dist/visualize/server.js +619 -6
- package/dist/visualize/server.js.map +1 -1
- package/dist/visualize/template.d.ts.map +1 -1
- package/dist/visualize/template.js +604 -18
- package/dist/visualize/template.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/core.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAEH,sCA8zBC;AA9zBD,SAAgB,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4zBR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphCanvas — 高性能图谱渲染引擎
|
|
3
|
+
*
|
|
4
|
+
* 替换 vis-network 的自研 Canvas2D 渲染器。
|
|
5
|
+
* 导出为内联 JavaScript 字符串,由 template.ts 嵌入 HTML。
|
|
6
|
+
*
|
|
7
|
+
* 核心架构 (借鉴 LeaferJS):
|
|
8
|
+
* - GraphCanvas: 主引擎,管理 Canvas、渲染循环、数据
|
|
9
|
+
* - ViewportManager: 平移/缩放/坐标变换
|
|
10
|
+
* - SpatialIndex: R-tree 空间索引 (O(log n) 视口查询)
|
|
11
|
+
* - RenderPipeline: 视口裁剪 + 脏区域局部渲染
|
|
12
|
+
* - LODManager: 多级细节渲染
|
|
13
|
+
* - LayoutEngine: Web Worker 力导向布局
|
|
14
|
+
* - Clusterer: 节点聚合
|
|
15
|
+
* - InteractionManager: 高效 hit-test + hover/click/drag
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* 返回完整的 GraphCanvas 引擎 JavaScript 代码。
|
|
19
|
+
* 嵌入方式: `<script>${getGraphCanvasScript()}</script>`
|
|
20
|
+
*/
|
|
21
|
+
export declare function getGraphCanvasScript(): string;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAaH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAkC7C"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GraphCanvas — 高性能图谱渲染引擎
|
|
4
|
+
*
|
|
5
|
+
* 替换 vis-network 的自研 Canvas2D 渲染器。
|
|
6
|
+
* 导出为内联 JavaScript 字符串,由 template.ts 嵌入 HTML。
|
|
7
|
+
*
|
|
8
|
+
* 核心架构 (借鉴 LeaferJS):
|
|
9
|
+
* - GraphCanvas: 主引擎,管理 Canvas、渲染循环、数据
|
|
10
|
+
* - ViewportManager: 平移/缩放/坐标变换
|
|
11
|
+
* - SpatialIndex: R-tree 空间索引 (O(log n) 视口查询)
|
|
12
|
+
* - RenderPipeline: 视口裁剪 + 脏区域局部渲染
|
|
13
|
+
* - LODManager: 多级细节渲染
|
|
14
|
+
* - LayoutEngine: Web Worker 力导向布局
|
|
15
|
+
* - Clusterer: 节点聚合
|
|
16
|
+
* - InteractionManager: 高效 hit-test + hover/click/drag
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.getGraphCanvasScript = getGraphCanvasScript;
|
|
20
|
+
const core_1 = require("./core");
|
|
21
|
+
const viewport_1 = require("./viewport");
|
|
22
|
+
const spatial_index_1 = require("./spatial-index");
|
|
23
|
+
const renderer_1 = require("./renderer");
|
|
24
|
+
const lod_1 = require("./lod");
|
|
25
|
+
const interaction_1 = require("./interaction");
|
|
26
|
+
const layout_worker_1 = require("./layout-worker");
|
|
27
|
+
const clusterer_1 = require("./clusterer");
|
|
28
|
+
const styles_1 = require("./styles");
|
|
29
|
+
const api_compat_1 = require("./api-compat");
|
|
30
|
+
/**
|
|
31
|
+
* 返回完整的 GraphCanvas 引擎 JavaScript 代码。
|
|
32
|
+
* 嵌入方式: `<script>${getGraphCanvasScript()}</script>`
|
|
33
|
+
*/
|
|
34
|
+
function getGraphCanvasScript() {
|
|
35
|
+
return `
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// GraphCanvas Engine — 高性能图谱渲染引擎 v1.0
|
|
38
|
+
// ============================================================================
|
|
39
|
+
(function(global) {
|
|
40
|
+
'use strict';
|
|
41
|
+
|
|
42
|
+
${(0, core_1.getCoreScript)()}
|
|
43
|
+
|
|
44
|
+
${(0, viewport_1.getViewportScript)()}
|
|
45
|
+
|
|
46
|
+
${(0, spatial_index_1.getSpatialIndexScript)()}
|
|
47
|
+
|
|
48
|
+
${(0, renderer_1.getRendererScript)()}
|
|
49
|
+
|
|
50
|
+
${(0, lod_1.getLODScript)()}
|
|
51
|
+
|
|
52
|
+
${(0, interaction_1.getInteractionScript)()}
|
|
53
|
+
|
|
54
|
+
${(0, layout_worker_1.getLayoutWorkerScript)()}
|
|
55
|
+
|
|
56
|
+
${(0, clusterer_1.getClustererScript)()}
|
|
57
|
+
|
|
58
|
+
${(0, styles_1.getStylesScript)()}
|
|
59
|
+
|
|
60
|
+
${(0, api_compat_1.getApiCompatScript)()}
|
|
61
|
+
|
|
62
|
+
// Export to global
|
|
63
|
+
global.GraphCanvas = GraphCanvas;
|
|
64
|
+
global.DevPlanGraph = DevPlanGraph;
|
|
65
|
+
|
|
66
|
+
})(typeof window !== 'undefined' ? window : this);
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;AAiBH,oDAkCC;AAjDD,iCAAuC;AACvC,yCAA+C;AAC/C,mDAAwD;AACxD,yCAA+C;AAC/C,+BAAqC;AACrC,+CAAqD;AACrD,mDAAwD;AACxD,2CAAiD;AACjD,qCAA2C;AAC3C,6CAAkD;AAElD;;;GAGG;AACH,SAAgB,oBAAoB;IAClC,OAAO;;;;;;;EAOP,IAAA,oBAAa,GAAE;;EAEf,IAAA,4BAAiB,GAAE;;EAEnB,IAAA,qCAAqB,GAAE;;EAEvB,IAAA,4BAAiB,GAAE;;EAEnB,IAAA,kBAAY,GAAE;;EAEd,IAAA,kCAAoB,GAAE;;EAEtB,IAAA,qCAAqB,GAAE;;EAEvB,IAAA,8BAAkB,GAAE;;EAEpB,IAAA,wBAAe,GAAE;;EAEjB,IAAA,+BAAkB,GAAE;;;;;;;CAOrB,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InteractionManager — 交互管理器
|
|
3
|
+
*
|
|
4
|
+
* 基于空间索引实现 O(log n) hit-test:
|
|
5
|
+
* - hover: 鼠标悬停高亮 (脏区域重绘)
|
|
6
|
+
* - click: 选中节点 (高亮连接边)
|
|
7
|
+
* - drag (空白): 视口平移
|
|
8
|
+
* - drag (节点): 节点拖拽
|
|
9
|
+
* - 框选: 矩形范围选择
|
|
10
|
+
* - 双击: 聚焦节点
|
|
11
|
+
*/
|
|
12
|
+
export declare function getInteractionScript(): string;
|
|
13
|
+
//# sourceMappingURL=interaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/interaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wBAAgB,oBAAoB,IAAI,MAAM,CA8a7C"}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* InteractionManager — 交互管理器
|
|
4
|
+
*
|
|
5
|
+
* 基于空间索引实现 O(log n) hit-test:
|
|
6
|
+
* - hover: 鼠标悬停高亮 (脏区域重绘)
|
|
7
|
+
* - click: 选中节点 (高亮连接边)
|
|
8
|
+
* - drag (空白): 视口平移
|
|
9
|
+
* - drag (节点): 节点拖拽
|
|
10
|
+
* - 框选: 矩形范围选择
|
|
11
|
+
* - 双击: 聚焦节点
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.getInteractionScript = getInteractionScript;
|
|
15
|
+
function getInteractionScript() {
|
|
16
|
+
return `
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// InteractionManager — Hit-test + Mouse/Touch Events
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
function InteractionManager(engine) {
|
|
22
|
+
this._engine = engine;
|
|
23
|
+
|
|
24
|
+
// State
|
|
25
|
+
this._hoveredNode = null;
|
|
26
|
+
this._selectedNodes = [];
|
|
27
|
+
this._selectedNodeMap = {};
|
|
28
|
+
this._isDraggingNode = false;
|
|
29
|
+
this._isDraggingCanvas = false;
|
|
30
|
+
this._draggedNode = null;
|
|
31
|
+
this._dragStartScreenX = 0;
|
|
32
|
+
this._dragStartScreenY = 0;
|
|
33
|
+
this._dragStartWorldX = 0;
|
|
34
|
+
this._dragStartWorldY = 0;
|
|
35
|
+
this._lastMouseScreenX = 0;
|
|
36
|
+
this._lastMouseScreenY = 0;
|
|
37
|
+
this._lastDragTime = 0;
|
|
38
|
+
|
|
39
|
+
// Bind events
|
|
40
|
+
this._bindEvents();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
InteractionManager.prototype._bindEvents = function() {
|
|
44
|
+
var self = this;
|
|
45
|
+
var canvas = this._engine._canvas;
|
|
46
|
+
|
|
47
|
+
// ── Mousemove → Hover ──
|
|
48
|
+
canvas.addEventListener('mousemove', function(e) {
|
|
49
|
+
var rect = canvas.getBoundingClientRect();
|
|
50
|
+
var sx = e.clientX - rect.left;
|
|
51
|
+
var sy = e.clientY - rect.top;
|
|
52
|
+
|
|
53
|
+
if (self._isDraggingNode) {
|
|
54
|
+
self._handleNodeDrag(sx, sy);
|
|
55
|
+
} else if (self._isDraggingCanvas) {
|
|
56
|
+
self._handleCanvasDrag(sx, sy);
|
|
57
|
+
} else {
|
|
58
|
+
self._handleHover(sx, sy);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
self._lastMouseScreenX = sx;
|
|
62
|
+
self._lastMouseScreenY = sy;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ── Mousedown → Start drag ──
|
|
66
|
+
canvas.addEventListener('mousedown', function(e) {
|
|
67
|
+
if (e.button !== 0) return; // left button only
|
|
68
|
+
var rect = canvas.getBoundingClientRect();
|
|
69
|
+
var sx = e.clientX - rect.left;
|
|
70
|
+
var sy = e.clientY - rect.top;
|
|
71
|
+
|
|
72
|
+
self._dragStartScreenX = sx;
|
|
73
|
+
self._dragStartScreenY = sy;
|
|
74
|
+
|
|
75
|
+
// Hit-test to see if we're clicking on a node
|
|
76
|
+
var hitNode = self._hitTest(sx, sy);
|
|
77
|
+
|
|
78
|
+
if (hitNode) {
|
|
79
|
+
self._isDraggingNode = true;
|
|
80
|
+
self._draggedNode = hitNode;
|
|
81
|
+
hitNode._dragging = true;
|
|
82
|
+
self._dragStartWorldX = hitNode.x;
|
|
83
|
+
self._dragStartWorldY = hitNode.y;
|
|
84
|
+
self._engine._emit('dragStart', { nodeId: hitNode.id, node: hitNode });
|
|
85
|
+
} else {
|
|
86
|
+
self._isDraggingCanvas = true;
|
|
87
|
+
var viewport = self._engine._viewport;
|
|
88
|
+
viewport._dragOffsetStartX = viewport._offsetX;
|
|
89
|
+
viewport._dragOffsetStartY = viewport._offsetY;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
self._lastDragTime = performance.now();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ── Mouseup → End drag / Click ──
|
|
96
|
+
canvas.addEventListener('mouseup', function(e) {
|
|
97
|
+
var rect = canvas.getBoundingClientRect();
|
|
98
|
+
var sx = e.clientX - rect.left;
|
|
99
|
+
var sy = e.clientY - rect.top;
|
|
100
|
+
|
|
101
|
+
var wasDragging = self._isDraggingNode || self._isDraggingCanvas;
|
|
102
|
+
var dragDist = Math.hypot(sx - self._dragStartScreenX, sy - self._dragStartScreenY);
|
|
103
|
+
var isClick = dragDist < 5; // 5px threshold for distinguishing click from drag
|
|
104
|
+
|
|
105
|
+
if (self._isDraggingNode && self._draggedNode) {
|
|
106
|
+
self._draggedNode._dragging = false;
|
|
107
|
+
self._engine._emit('dragEnd', { nodeId: self._draggedNode.id, node: self._draggedNode });
|
|
108
|
+
// Update spatial indices after drag (final position)
|
|
109
|
+
self._engine._spatialIndex.updateNode(self._draggedNode);
|
|
110
|
+
self._engine._spatialIndex.updateEdgesForNode(
|
|
111
|
+
self._draggedNode.id, self._engine._nodeEdges, self._engine._nodeMap
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (self._isDraggingCanvas) {
|
|
116
|
+
// Start inertia
|
|
117
|
+
self._engine._viewport._startInertia();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
self._isDraggingNode = false;
|
|
121
|
+
self._isDraggingCanvas = false;
|
|
122
|
+
self._draggedNode = null;
|
|
123
|
+
|
|
124
|
+
// Handle click if it wasn't a drag
|
|
125
|
+
if (isClick) {
|
|
126
|
+
self._handleClick(sx, sy);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ── Mouse leave ──
|
|
131
|
+
canvas.addEventListener('mouseleave', function() {
|
|
132
|
+
if (self._hoveredNode) {
|
|
133
|
+
self._hoveredNode._hovered = false;
|
|
134
|
+
self._engine.markDirtyNode(self._hoveredNode);
|
|
135
|
+
self._hoveredNode = null;
|
|
136
|
+
canvas.style.cursor = 'default';
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ── Double click → Focus / Cluster drill-down ──
|
|
141
|
+
canvas.addEventListener('dblclick', function(e) {
|
|
142
|
+
var rect = canvas.getBoundingClientRect();
|
|
143
|
+
var sx = e.clientX - rect.left;
|
|
144
|
+
var sy = e.clientY - rect.top;
|
|
145
|
+
var hitNode = self._hitTest(sx, sy);
|
|
146
|
+
|
|
147
|
+
if (hitNode) {
|
|
148
|
+
self._engine._emit('doubleClick', {
|
|
149
|
+
nodeId: hitNode.id,
|
|
150
|
+
node: hitNode,
|
|
151
|
+
pointer: { screen: { x: sx, y: sy } },
|
|
152
|
+
});
|
|
153
|
+
// Zoom to node
|
|
154
|
+
self._engine.focusNode(hitNode.id, {
|
|
155
|
+
scale: Math.max(self._engine._viewport.getScale() * 2, 0.5),
|
|
156
|
+
animation: { duration: 600 },
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
// Check cluster double-click → expand + zoom
|
|
160
|
+
var clusterer = self._engine._clusterer;
|
|
161
|
+
if (clusterer && clusterer.isActive()) {
|
|
162
|
+
var world = self._engine._viewport.screenToWorld(sx, sy);
|
|
163
|
+
var hitCluster = clusterer.hitTest(world.x, world.y);
|
|
164
|
+
if (hitCluster) {
|
|
165
|
+
clusterer.expandCluster(hitCluster.id, { animation: true, fitAfter: false });
|
|
166
|
+
// Zoom into expanded cluster area
|
|
167
|
+
self._engine._viewport.fitToNodes(hitCluster.nodes, {
|
|
168
|
+
padding: 80,
|
|
169
|
+
animation: { duration: 700 },
|
|
170
|
+
});
|
|
171
|
+
self._engine._emit('clusterDoubleClick', {
|
|
172
|
+
clusterId: hitCluster.id,
|
|
173
|
+
nodeCount: hitCluster.count,
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Double click on empty → fit all
|
|
179
|
+
self._engine.fit({ animation: { duration: 800 } });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ── Context menu (right click) ──
|
|
184
|
+
canvas.addEventListener('contextmenu', function(e) {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
var rect = canvas.getBoundingClientRect();
|
|
187
|
+
var sx = e.clientX - rect.left;
|
|
188
|
+
var sy = e.clientY - rect.top;
|
|
189
|
+
var hitNode = self._hitTest(sx, sy);
|
|
190
|
+
self._engine._emit('contextMenu', {
|
|
191
|
+
nodeId: hitNode ? hitNode.id : null,
|
|
192
|
+
node: hitNode,
|
|
193
|
+
pointer: { screen: { x: sx, y: sy } },
|
|
194
|
+
domEvent: e,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// ── Hit-Test ──────────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Find the topmost node at screen position (sx, sy).
|
|
203
|
+
* Uses spatial index for O(log n) query.
|
|
204
|
+
*/
|
|
205
|
+
InteractionManager.prototype._hitTest = function(sx, sy) {
|
|
206
|
+
var world = this._engine._viewport.screenToWorld(sx, sy);
|
|
207
|
+
var candidates = this._engine._spatialIndex.pointQuery(world.x, world.y);
|
|
208
|
+
|
|
209
|
+
if (candidates.length === 0) return null;
|
|
210
|
+
|
|
211
|
+
// Find the closest node to the query point (by center distance)
|
|
212
|
+
var best = null;
|
|
213
|
+
var bestDist = Infinity;
|
|
214
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
215
|
+
var node = candidates[i]._node;
|
|
216
|
+
if (!node) continue;
|
|
217
|
+
var dx = node.x - world.x;
|
|
218
|
+
var dy = node.y - world.y;
|
|
219
|
+
var dist = dx * dx + dy * dy;
|
|
220
|
+
var r = node._radius || 10;
|
|
221
|
+
// Check if point is inside the node shape
|
|
222
|
+
if (dist <= r * r) {
|
|
223
|
+
if (dist < bestDist) {
|
|
224
|
+
bestDist = dist;
|
|
225
|
+
best = node;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// If no exact hit, check if we're close enough (expanded hit area)
|
|
231
|
+
if (!best && candidates.length > 0) {
|
|
232
|
+
var expandFactor = 1.5; // 50% larger hit area for easier clicking
|
|
233
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
234
|
+
var node = candidates[i]._node;
|
|
235
|
+
if (!node) continue;
|
|
236
|
+
var dx = node.x - world.x;
|
|
237
|
+
var dy = node.y - world.y;
|
|
238
|
+
var dist = dx * dx + dy * dy;
|
|
239
|
+
var r = (node._radius || 10) * expandFactor;
|
|
240
|
+
if (dist <= r * r && dist < bestDist) {
|
|
241
|
+
bestDist = dist;
|
|
242
|
+
best = node;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return best;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// ── Hover ─────────────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
InteractionManager.prototype._handleHover = function(sx, sy) {
|
|
253
|
+
var hitNode = this._hitTest(sx, sy);
|
|
254
|
+
var prevHovered = this._hoveredNode;
|
|
255
|
+
|
|
256
|
+
// Check cluster hover if no node hit
|
|
257
|
+
var clusterer = this._engine._clusterer;
|
|
258
|
+
if (!hitNode && clusterer && clusterer.isActive()) {
|
|
259
|
+
var world = this._engine._viewport.screenToWorld(sx, sy);
|
|
260
|
+
var hitCluster = clusterer.hitTest(world.x, world.y);
|
|
261
|
+
if (hitCluster) {
|
|
262
|
+
// Show cluster tooltip
|
|
263
|
+
this._engine._canvas.style.cursor = 'pointer';
|
|
264
|
+
this._engine._canvas.title = hitCluster.count + ' nodes (' +
|
|
265
|
+
(hitCluster.dominantType || 'mixed') + ', ' +
|
|
266
|
+
(hitCluster.dominantStatus || 'mixed') + ')';
|
|
267
|
+
if (prevHovered) {
|
|
268
|
+
prevHovered._hovered = false;
|
|
269
|
+
this._engine.markDirtyNode(prevHovered);
|
|
270
|
+
this._hoveredNode = null;
|
|
271
|
+
}
|
|
272
|
+
this._engine._emit('hoverCluster', {
|
|
273
|
+
clusterId: hitCluster.id,
|
|
274
|
+
count: hitCluster.count,
|
|
275
|
+
dominantType: hitCluster.dominantType,
|
|
276
|
+
});
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Clear cluster tooltip
|
|
282
|
+
this._engine._canvas.title = '';
|
|
283
|
+
|
|
284
|
+
if (hitNode === prevHovered) return; // no change
|
|
285
|
+
|
|
286
|
+
if (prevHovered) {
|
|
287
|
+
prevHovered._hovered = false;
|
|
288
|
+
// Dirty only the old hovered node's region
|
|
289
|
+
this._engine.markDirtyNode(prevHovered);
|
|
290
|
+
}
|
|
291
|
+
if (hitNode) {
|
|
292
|
+
hitNode._hovered = true;
|
|
293
|
+
this._engine._canvas.style.cursor = 'pointer';
|
|
294
|
+
// Dirty only the new hovered node's region
|
|
295
|
+
this._engine.markDirtyNode(hitNode);
|
|
296
|
+
} else {
|
|
297
|
+
this._engine._canvas.style.cursor = 'default';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this._hoveredNode = hitNode;
|
|
301
|
+
|
|
302
|
+
this._engine._emit('hover', {
|
|
303
|
+
nodeId: hitNode ? hitNode.id : null,
|
|
304
|
+
node: hitNode,
|
|
305
|
+
previousNodeId: prevHovered ? prevHovered.id : null,
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// ── Click ─────────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
InteractionManager.prototype._handleClick = function(sx, sy) {
|
|
312
|
+
var hitNode = this._hitTest(sx, sy);
|
|
313
|
+
|
|
314
|
+
if (hitNode) {
|
|
315
|
+
// Select this node
|
|
316
|
+
this._selectNode(hitNode);
|
|
317
|
+
this._engine._emit('click', {
|
|
318
|
+
nodes: [hitNode.id],
|
|
319
|
+
nodeId: hitNode.id,
|
|
320
|
+
node: hitNode,
|
|
321
|
+
pointer: { screen: { x: sx, y: sy }, canvas: this._engine._viewport.screenToWorld(sx, sy) },
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
// Check if clicking on a cluster
|
|
325
|
+
var clusterer = this._engine._clusterer;
|
|
326
|
+
if (clusterer && clusterer.isActive()) {
|
|
327
|
+
var world = this._engine._viewport.screenToWorld(sx, sy);
|
|
328
|
+
var hitCluster = clusterer.hitTest(world.x, world.y);
|
|
329
|
+
if (hitCluster) {
|
|
330
|
+
// Click on cluster → expand it
|
|
331
|
+
clusterer.expandCluster(hitCluster.id, { animation: true, fitAfter: true });
|
|
332
|
+
this._engine._emit('clusterClick', {
|
|
333
|
+
clusterId: hitCluster.id,
|
|
334
|
+
nodeCount: hitCluster.count,
|
|
335
|
+
pointer: { screen: { x: sx, y: sy }, canvas: world },
|
|
336
|
+
});
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Deselect all
|
|
342
|
+
this._deselectAll();
|
|
343
|
+
this._engine._emit('click', {
|
|
344
|
+
nodes: [],
|
|
345
|
+
nodeId: null,
|
|
346
|
+
node: null,
|
|
347
|
+
pointer: { screen: { x: sx, y: sy }, canvas: this._engine._viewport.screenToWorld(sx, sy) },
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
InteractionManager.prototype._selectNode = function(node) {
|
|
353
|
+
// Deselect previous (dirties old selection's regions)
|
|
354
|
+
this._deselectAll();
|
|
355
|
+
|
|
356
|
+
node._selected = true;
|
|
357
|
+
this._selectedNodes = [node];
|
|
358
|
+
this._selectedNodeMap = {};
|
|
359
|
+
this._selectedNodeMap[node.id] = true;
|
|
360
|
+
|
|
361
|
+
// Highlight connected edges
|
|
362
|
+
var edges = this._engine._nodeEdges[node.id] || [];
|
|
363
|
+
for (var i = 0; i < edges.length; i++) {
|
|
364
|
+
edges[i]._highlighted = true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Dirty the selected node and its connected nodes (for edge highlighting)
|
|
368
|
+
this._engine.markDirtyNode(node);
|
|
369
|
+
this._engine._emit('select', { nodeIds: [node.id] });
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
InteractionManager.prototype._deselectAll = function() {
|
|
373
|
+
// Dirty each previously selected node's region
|
|
374
|
+
for (var i = 0; i < this._selectedNodes.length; i++) {
|
|
375
|
+
this._selectedNodes[i]._selected = false;
|
|
376
|
+
this._engine.markDirtyNode(this._selectedNodes[i]);
|
|
377
|
+
}
|
|
378
|
+
// Reset all edge highlights
|
|
379
|
+
var edges = this._engine._edges;
|
|
380
|
+
for (var i = 0; i < edges.length; i++) {
|
|
381
|
+
edges[i]._highlighted = false;
|
|
382
|
+
}
|
|
383
|
+
this._selectedNodes = [];
|
|
384
|
+
this._selectedNodeMap = {};
|
|
385
|
+
this._engine._emit('deselect', {});
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// ── Node Drag ─────────────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
InteractionManager.prototype._handleNodeDrag = function(sx, sy) {
|
|
391
|
+
if (!this._draggedNode) return;
|
|
392
|
+
|
|
393
|
+
var world = this._engine._viewport.screenToWorld(sx, sy);
|
|
394
|
+
var prevPos = { x: this._draggedNode.x, y: this._draggedNode.y };
|
|
395
|
+
this._draggedNode.x = world.x;
|
|
396
|
+
this._draggedNode.y = world.y;
|
|
397
|
+
|
|
398
|
+
// Update spatial indices for the dragged node + connected edges
|
|
399
|
+
this._engine._spatialIndex.updateNode(this._draggedNode);
|
|
400
|
+
this._engine._spatialIndex.updateEdgesForNode(
|
|
401
|
+
this._draggedNode.id, this._engine._nodeEdges, this._engine._nodeMap
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Only dirty the affected regions (old + new position)
|
|
405
|
+
this._engine.markDirtyNode(this._draggedNode, prevPos);
|
|
406
|
+
this._engine._emit('dragging', {
|
|
407
|
+
nodeId: this._draggedNode.id,
|
|
408
|
+
node: this._draggedNode,
|
|
409
|
+
x: world.x,
|
|
410
|
+
y: world.y,
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// ── Canvas Drag (pan) ─────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
InteractionManager.prototype._handleCanvasDrag = function(sx, sy) {
|
|
417
|
+
var dx = sx - this._lastMouseScreenX;
|
|
418
|
+
var dy = sy - this._lastMouseScreenY;
|
|
419
|
+
|
|
420
|
+
var viewport = this._engine._viewport;
|
|
421
|
+
viewport._offsetX += dx;
|
|
422
|
+
viewport._offsetY += dy;
|
|
423
|
+
|
|
424
|
+
// Track velocity for inertia
|
|
425
|
+
var now = performance.now();
|
|
426
|
+
var dt = now - this._lastDragTime;
|
|
427
|
+
if (dt > 0) {
|
|
428
|
+
viewport._velocityX = dx * (16 / dt); // normalize to ~60fps
|
|
429
|
+
viewport._velocityY = dy * (16 / dt);
|
|
430
|
+
}
|
|
431
|
+
this._lastDragTime = now;
|
|
432
|
+
|
|
433
|
+
this._engine.markDirty();
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// ── Cleanup ───────────────────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
InteractionManager.prototype.destroy = function() {
|
|
439
|
+
// Event listeners are on the canvas which will be removed
|
|
440
|
+
this._hoveredNode = null;
|
|
441
|
+
this._selectedNodes = [];
|
|
442
|
+
this._draggedNode = null;
|
|
443
|
+
};
|
|
444
|
+
`;
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=interaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.js","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/interaction.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAEH,oDA8aC;AA9aD,SAAgB,oBAAoB;IAClC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4aR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LayoutEngine — Web Worker 力导向布局
|
|
3
|
+
*
|
|
4
|
+
* 将力导向布局移入 Web Worker,避免阻塞 UI 主线程。
|
|
5
|
+
* Worker 每迭代一批后通过 postMessage 发送节点位置。
|
|
6
|
+
* 主线程增量更新渲染。
|
|
7
|
+
*
|
|
8
|
+
* Phase 8B 升级:
|
|
9
|
+
* - Barnes-Hut 四叉树: O(n log n) 排斥力近似 (θ=0.8)
|
|
10
|
+
* - 自适应算法: <1K 用原始 FR, >1K 用 Barnes-Hut
|
|
11
|
+
* - 提前终止: 能量阈值收敛检测
|
|
12
|
+
* - 进度报告: 百分比 + 剩余时间估算
|
|
13
|
+
*
|
|
14
|
+
* 当前版本: 内联 Worker (Blob URL),无外部依赖。
|
|
15
|
+
*/
|
|
16
|
+
export declare function getLayoutWorkerScript(): string;
|
|
17
|
+
//# sourceMappingURL=layout-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-worker.d.ts","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/layout-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,wBAAgB,qBAAqB,IAAI,MAAM,CAygB9C"}
|