aifastdb-devplan 1.5.0 → 1.6.2
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 +15 -1
- package/dist/dev-plan-document-store.d.ts.map +1 -1
- package/dist/dev-plan-document-store.js +122 -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 +113 -19
- package/dist/dev-plan-factory.js.map +1 -1
- package/dist/dev-plan-graph-store.d.ts +79 -1
- package/dist/dev-plan-graph-store.d.ts.map +1 -1
- package/dist/dev-plan-graph-store.js +420 -3
- package/dist/dev-plan-graph-store.js.map +1 -1
- package/dist/dev-plan-interface.d.ts +24 -1
- package/dist/dev-plan-interface.d.ts.map +1 -1
- package/dist/dev-plan-migrate.d.ts +1 -0
- package/dist/dev-plan-migrate.d.ts.map +1 -1
- package/dist/dev-plan-migrate.js +28 -2
- package/dist/dev-plan-migrate.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 +397 -4
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/types.d.ts +160 -1
- 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 +344 -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 +1136 -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 +457 -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 +577 -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 +813 -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 +152 -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 +385 -0
- package/dist/visualize/graph-canvas/viewport.js.map +1 -0
- package/dist/visualize/server.js +737 -7
- package/dist/visualize/server.js.map +1 -1
- package/dist/visualize/template-core.d.ts +9 -0
- package/dist/visualize/template-core.d.ts.map +1 -0
- package/dist/visualize/template-core.js +714 -0
- package/dist/visualize/template-core.js.map +1 -0
- package/dist/visualize/template-data-loading.d.ts +7 -0
- package/dist/visualize/template-data-loading.d.ts.map +1 -0
- package/dist/visualize/template-data-loading.js +677 -0
- package/dist/visualize/template-data-loading.js.map +1 -0
- package/dist/visualize/template-detail-panel.d.ts +14 -0
- package/dist/visualize/template-detail-panel.d.ts.map +1 -0
- package/dist/visualize/template-detail-panel.js +553 -0
- package/dist/visualize/template-detail-panel.js.map +1 -0
- package/dist/visualize/template-graph-3d.d.ts +7 -0
- package/dist/visualize/template-graph-3d.d.ts.map +1 -0
- package/dist/visualize/template-graph-3d.js +1112 -0
- package/dist/visualize/template-graph-3d.js.map +1 -0
- package/dist/visualize/template-graph-vis.d.ts +8 -0
- package/dist/visualize/template-graph-vis.d.ts.map +1 -0
- package/dist/visualize/template-graph-vis.js +1204 -0
- package/dist/visualize/template-graph-vis.js.map +1 -0
- package/dist/visualize/template-html.d.ts +9 -0
- package/dist/visualize/template-html.d.ts.map +1 -0
- package/dist/visualize/template-html.js +484 -0
- package/dist/visualize/template-html.js.map +1 -0
- package/dist/visualize/template-pages.d.ts +7 -0
- package/dist/visualize/template-pages.d.ts.map +1 -0
- package/dist/visualize/template-pages.js +806 -0
- package/dist/visualize/template-pages.js.map +1 -0
- package/dist/visualize/template-stats-modal.d.ts +7 -0
- package/dist/visualize/template-stats-modal.d.ts.map +1 -0
- package/dist/visualize/template-stats-modal.js +406 -0
- package/dist/visualize/template-stats-modal.js.map +1 -0
- package/dist/visualize/template-styles.d.ts +9 -0
- package/dist/visualize/template-styles.d.ts.map +1 -0
- package/dist/visualize/template-styles.js +487 -0
- package/dist/visualize/template-styles.js.map +1 -0
- package/dist/visualize/template.d.ts +14 -3
- package/dist/visualize/template.d.ts.map +1 -1
- package/dist/visualize/template.js +38 -2889
- package/dist/visualize/template.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Clusterer — 节点聚合器 (完整实现)
|
|
4
|
+
*
|
|
5
|
+
* 在极低缩放级别时,将空间邻近的节点聚合为 cluster 节点。
|
|
6
|
+
* 使用网格聚合 (grid-based clustering) 算法:
|
|
7
|
+
* 1. 将世界空间划分为网格
|
|
8
|
+
* 2. 每个网格内的多个节点聚合为一个 cluster
|
|
9
|
+
* 3. cluster 显示计数标签、主类型颜色、状态饼图
|
|
10
|
+
* 4. 缩放到阈值以上时自动展开
|
|
11
|
+
* 5. Spring 展开/收起过渡动画
|
|
12
|
+
* 6. 多级递归聚合
|
|
13
|
+
*
|
|
14
|
+
* Phase 8B 完整实现
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.getClustererScript = getClustererScript;
|
|
18
|
+
function getClustererScript() {
|
|
19
|
+
return `
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Clusterer — Grid-based Node Aggregation (Full Implementation)
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
function Clusterer(engine) {
|
|
25
|
+
this._engine = engine;
|
|
26
|
+
this._enabled = false;
|
|
27
|
+
this._clusters = []; // [{id, nodes, x, y, count, radius, ...}, ...]
|
|
28
|
+
this._clusterMap = {}; // clusterId → cluster
|
|
29
|
+
this._nodeToCluster = {}; // nodeId → clusterId
|
|
30
|
+
this._gridSize = 200; // world-space grid cell size (base)
|
|
31
|
+
this._clusterThreshold = 0.08; // scale below which clustering activates
|
|
32
|
+
this._minClusterSize = 2; // minimum nodes to form a cluster
|
|
33
|
+
this._lastRebuildScale = -1; // last scale at which clusters were rebuilt
|
|
34
|
+
this._rebuildThresholdRatio = 1.5; // rebuild when scale changes by this factor
|
|
35
|
+
|
|
36
|
+
// Animation state
|
|
37
|
+
this._animating = false;
|
|
38
|
+
this._animations = []; // [{node, startX, startY, targetX, targetY, t, duration}, ...]
|
|
39
|
+
this._animRafId = null;
|
|
40
|
+
|
|
41
|
+
// Multi-level: clusters of clusters
|
|
42
|
+
this._level = 0; // current aggregation level
|
|
43
|
+
this._parentClusters = []; // level-2 super-clusters
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Enable/disable clustering.
|
|
48
|
+
*/
|
|
49
|
+
Clusterer.prototype.setEnabled = function(enabled) {
|
|
50
|
+
this._enabled = enabled;
|
|
51
|
+
if (enabled) {
|
|
52
|
+
this.rebuild();
|
|
53
|
+
} else {
|
|
54
|
+
this._clearAllClusters();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
Clusterer.prototype._clearAllClusters = function() {
|
|
59
|
+
// Restore visibility to all nodes
|
|
60
|
+
var nodes = this._engine._nodes;
|
|
61
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
62
|
+
nodes[i]._visible = true;
|
|
63
|
+
nodes[i]._clustered = false;
|
|
64
|
+
}
|
|
65
|
+
this._clusters = [];
|
|
66
|
+
this._clusterMap = {};
|
|
67
|
+
this._nodeToCluster = {};
|
|
68
|
+
this._parentClusters = [];
|
|
69
|
+
this._level = 0;
|
|
70
|
+
this._lastRebuildScale = -1;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if clustering should be active at the current zoom level.
|
|
75
|
+
*/
|
|
76
|
+
Clusterer.prototype.isActive = function() {
|
|
77
|
+
if (!this._enabled) return false;
|
|
78
|
+
return this._engine._viewport.getScale() < this._clusterThreshold;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if clusters need rebuilding based on scale change.
|
|
83
|
+
*/
|
|
84
|
+
Clusterer.prototype.needsRebuild = function() {
|
|
85
|
+
if (!this._enabled) return false;
|
|
86
|
+
var scale = this._engine._viewport.getScale();
|
|
87
|
+
if (this._lastRebuildScale <= 0) return true;
|
|
88
|
+
var ratio = Math.max(scale / this._lastRebuildScale, this._lastRebuildScale / scale);
|
|
89
|
+
return ratio > this._rebuildThresholdRatio;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Rebuild clusters based on current node positions and zoom level.
|
|
94
|
+
* Uses adaptive grid-based clustering for O(n) performance.
|
|
95
|
+
* Grid size adapts to zoom: zoomed further out → larger grids → more aggregation.
|
|
96
|
+
*/
|
|
97
|
+
Clusterer.prototype.rebuild = function() {
|
|
98
|
+
if (!this._enabled) return;
|
|
99
|
+
|
|
100
|
+
var engine = this._engine;
|
|
101
|
+
var nodes = engine._nodes;
|
|
102
|
+
var scale = engine._viewport.getScale();
|
|
103
|
+
this._lastRebuildScale = scale;
|
|
104
|
+
|
|
105
|
+
// Adaptive grid size: smaller scale → larger grid → more aggregation
|
|
106
|
+
var baseGrid = this._gridSize;
|
|
107
|
+
var adaptiveGrid = baseGrid / Math.max(scale * 5, 0.01);
|
|
108
|
+
adaptiveGrid = Math.max(adaptiveGrid, 100); // minimum grid size
|
|
109
|
+
adaptiveGrid = Math.min(adaptiveGrid, 5000); // maximum grid size
|
|
110
|
+
|
|
111
|
+
var grid = {}; // "gx:gy" → [node, ...]
|
|
112
|
+
|
|
113
|
+
// Assign nodes to grid cells
|
|
114
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
115
|
+
var n = nodes[i];
|
|
116
|
+
var gx = Math.floor(n.x / adaptiveGrid);
|
|
117
|
+
var gy = Math.floor(n.y / adaptiveGrid);
|
|
118
|
+
var key = gx + ':' + gy;
|
|
119
|
+
if (!grid[key]) grid[key] = [];
|
|
120
|
+
grid[key].push(n);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Reset old state
|
|
124
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
125
|
+
nodes[i]._visible = true;
|
|
126
|
+
nodes[i]._clustered = false;
|
|
127
|
+
}
|
|
128
|
+
this._clusters = [];
|
|
129
|
+
this._clusterMap = {};
|
|
130
|
+
this._nodeToCluster = {};
|
|
131
|
+
var clusterId = 0;
|
|
132
|
+
|
|
133
|
+
var keys = Object.keys(grid);
|
|
134
|
+
for (var i = 0; i < keys.length; i++) {
|
|
135
|
+
var cellNodes = grid[keys[i]];
|
|
136
|
+
if (cellNodes.length < this._minClusterSize) continue; // too few, show individually
|
|
137
|
+
|
|
138
|
+
// Compute centroid
|
|
139
|
+
var cx = 0, cy = 0;
|
|
140
|
+
for (var j = 0; j < cellNodes.length; j++) {
|
|
141
|
+
cx += cellNodes[j].x;
|
|
142
|
+
cy += cellNodes[j].y;
|
|
143
|
+
}
|
|
144
|
+
cx /= cellNodes.length;
|
|
145
|
+
cy /= cellNodes.length;
|
|
146
|
+
|
|
147
|
+
// Determine dominant type and status
|
|
148
|
+
var typeCount = {};
|
|
149
|
+
var statusCount = {};
|
|
150
|
+
for (var j = 0; j < cellNodes.length; j++) {
|
|
151
|
+
var t = cellNodes[j].type || 'default';
|
|
152
|
+
var s = (cellNodes[j].properties || {}).status || 'pending';
|
|
153
|
+
typeCount[t] = (typeCount[t] || 0) + 1;
|
|
154
|
+
statusCount[s] = (statusCount[s] || 0) + 1;
|
|
155
|
+
}
|
|
156
|
+
var dominantType = this._getDominant(typeCount);
|
|
157
|
+
var dominantStatus = this._getDominant(statusCount);
|
|
158
|
+
|
|
159
|
+
// Get style for the cluster based on dominant type
|
|
160
|
+
var styles = engine._styles;
|
|
161
|
+
var mockNode = { type: dominantType, properties: { status: dominantStatus }, degree: cellNodes.length };
|
|
162
|
+
var clusterStyle = styles.getNodeStyle(mockNode);
|
|
163
|
+
|
|
164
|
+
var cluster = {
|
|
165
|
+
id: 'cluster_' + (clusterId++),
|
|
166
|
+
nodes: cellNodes,
|
|
167
|
+
nodeIds: [],
|
|
168
|
+
x: cx,
|
|
169
|
+
y: cy,
|
|
170
|
+
count: cellNodes.length,
|
|
171
|
+
radius: Math.max(18, Math.min(Math.sqrt(cellNodes.length) * 6, 60)),
|
|
172
|
+
dominantType: dominantType,
|
|
173
|
+
dominantStatus: dominantStatus,
|
|
174
|
+
bgColor: clusterStyle.bgColor,
|
|
175
|
+
borderColor: clusterStyle.borderColor,
|
|
176
|
+
fontColor: clusterStyle.fontColor || '#fff',
|
|
177
|
+
statusBreakdown: statusCount,
|
|
178
|
+
_aabb: null, // will be set below
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
for (var j = 0; j < cellNodes.length; j++) {
|
|
182
|
+
cluster.nodeIds.push(cellNodes[j].id);
|
|
183
|
+
this._nodeToCluster[cellNodes[j].id] = cluster.id;
|
|
184
|
+
cellNodes[j]._visible = false; // hide individual nodes
|
|
185
|
+
cellNodes[j]._clustered = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Set AABB for cluster hit-test
|
|
189
|
+
cluster._aabb = {
|
|
190
|
+
minX: cx - cluster.radius,
|
|
191
|
+
minY: cy - cluster.radius,
|
|
192
|
+
maxX: cx + cluster.radius,
|
|
193
|
+
maxY: cy + cluster.radius,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
this._clusters.push(cluster);
|
|
197
|
+
this._clusterMap[cluster.id] = cluster;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Multi-level: if there are still too many clusters, create super-clusters
|
|
201
|
+
this._parentClusters = [];
|
|
202
|
+
if (this._clusters.length > 200 && scale < this._clusterThreshold * 0.3) {
|
|
203
|
+
this._buildSuperClusters(adaptiveGrid * 3);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Build level-2 super-clusters (clusters of clusters).
|
|
209
|
+
*/
|
|
210
|
+
Clusterer.prototype._buildSuperClusters = function(superGridSize) {
|
|
211
|
+
var grid = {};
|
|
212
|
+
for (var i = 0; i < this._clusters.length; i++) {
|
|
213
|
+
var c = this._clusters[i];
|
|
214
|
+
var gx = Math.floor(c.x / superGridSize);
|
|
215
|
+
var gy = Math.floor(c.y / superGridSize);
|
|
216
|
+
var key = gx + ':' + gy;
|
|
217
|
+
if (!grid[key]) grid[key] = [];
|
|
218
|
+
grid[key].push(c);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this._parentClusters = [];
|
|
222
|
+
var keys = Object.keys(grid);
|
|
223
|
+
for (var i = 0; i < keys.length; i++) {
|
|
224
|
+
var subClusters = grid[keys[i]];
|
|
225
|
+
if (subClusters.length < 2) continue;
|
|
226
|
+
|
|
227
|
+
var cx = 0, cy = 0, totalCount = 0;
|
|
228
|
+
for (var j = 0; j < subClusters.length; j++) {
|
|
229
|
+
cx += subClusters[j].x * subClusters[j].count;
|
|
230
|
+
cy += subClusters[j].y * subClusters[j].count;
|
|
231
|
+
totalCount += subClusters[j].count;
|
|
232
|
+
}
|
|
233
|
+
cx /= totalCount;
|
|
234
|
+
cy /= totalCount;
|
|
235
|
+
|
|
236
|
+
this._parentClusters.push({
|
|
237
|
+
id: 'super_' + i,
|
|
238
|
+
subClusters: subClusters,
|
|
239
|
+
x: cx,
|
|
240
|
+
y: cy,
|
|
241
|
+
count: totalCount,
|
|
242
|
+
radius: Math.max(25, Math.min(Math.sqrt(totalCount) * 4, 80)),
|
|
243
|
+
bgColor: '#6366f1',
|
|
244
|
+
borderColor: '#4f46e5',
|
|
245
|
+
fontColor: '#fff',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Mark sub-clusters as hidden
|
|
249
|
+
for (var j = 0; j < subClusters.length; j++) {
|
|
250
|
+
subClusters[j]._superClustered = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
Clusterer.prototype._getDominant = function(countMap) {
|
|
256
|
+
var maxKey = null, maxCount = 0;
|
|
257
|
+
var keys = Object.keys(countMap);
|
|
258
|
+
for (var i = 0; i < keys.length; i++) {
|
|
259
|
+
if (countMap[keys[i]] > maxCount) {
|
|
260
|
+
maxCount = countMap[keys[i]];
|
|
261
|
+
maxKey = keys[i];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return maxKey;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get clusters for rendering (filters out super-clustered ones).
|
|
269
|
+
*/
|
|
270
|
+
Clusterer.prototype.getClusters = function() {
|
|
271
|
+
if (this._parentClusters.length > 0) {
|
|
272
|
+
// Return only non-super-clustered clusters + super-clusters
|
|
273
|
+
var visible = [];
|
|
274
|
+
for (var i = 0; i < this._clusters.length; i++) {
|
|
275
|
+
if (!this._clusters[i]._superClustered) visible.push(this._clusters[i]);
|
|
276
|
+
}
|
|
277
|
+
return visible.concat(this._parentClusters);
|
|
278
|
+
}
|
|
279
|
+
return this._clusters;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Check if a node is currently inside a cluster.
|
|
284
|
+
*/
|
|
285
|
+
Clusterer.prototype.isNodeClustered = function(nodeId) {
|
|
286
|
+
return !!this._nodeToCluster[nodeId];
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Hit-test: find cluster at world coordinates.
|
|
291
|
+
*/
|
|
292
|
+
Clusterer.prototype.hitTest = function(worldX, worldY) {
|
|
293
|
+
var clusters = this.getClusters();
|
|
294
|
+
for (var i = clusters.length - 1; i >= 0; i--) {
|
|
295
|
+
var c = clusters[i];
|
|
296
|
+
var dx = worldX - c.x;
|
|
297
|
+
var dy = worldY - c.y;
|
|
298
|
+
if (dx * dx + dy * dy <= c.radius * c.radius) {
|
|
299
|
+
return c;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Expand a cluster with Spring animation.
|
|
307
|
+
* @param {string} clusterId
|
|
308
|
+
* @param {Object} [options] — { animation: boolean, fitAfter: boolean }
|
|
309
|
+
*/
|
|
310
|
+
Clusterer.prototype.expandCluster = function(clusterId, options) {
|
|
311
|
+
var cluster = this._clusterMap[clusterId];
|
|
312
|
+
if (!cluster) return;
|
|
313
|
+
|
|
314
|
+
var opts = options || {};
|
|
315
|
+
var animate = opts.animation !== false;
|
|
316
|
+
var fitAfter = opts.fitAfter !== false;
|
|
317
|
+
var engine = this._engine;
|
|
318
|
+
|
|
319
|
+
// Remove cluster
|
|
320
|
+
var idx = this._clusters.indexOf(cluster);
|
|
321
|
+
if (idx >= 0) this._clusters.splice(idx, 1);
|
|
322
|
+
delete this._clusterMap[clusterId];
|
|
323
|
+
|
|
324
|
+
// Restore nodes
|
|
325
|
+
for (var i = 0; i < cluster.nodes.length; i++) {
|
|
326
|
+
var node = cluster.nodes[i];
|
|
327
|
+
delete this._nodeToCluster[node.id];
|
|
328
|
+
node._visible = true;
|
|
329
|
+
node._clustered = false;
|
|
330
|
+
|
|
331
|
+
if (animate) {
|
|
332
|
+
// Start all nodes at cluster center, animate to their real positions
|
|
333
|
+
var realX = node.x;
|
|
334
|
+
var realY = node.y;
|
|
335
|
+
node.x = cluster.x;
|
|
336
|
+
node.y = cluster.y;
|
|
337
|
+
this._animations.push({
|
|
338
|
+
node: node,
|
|
339
|
+
startX: cluster.x,
|
|
340
|
+
startY: cluster.y,
|
|
341
|
+
targetX: realX,
|
|
342
|
+
targetY: realY,
|
|
343
|
+
t: 0,
|
|
344
|
+
duration: 400 + Math.random() * 200, // stagger slightly
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (animate && this._animations.length > 0) {
|
|
350
|
+
this._startAnimation();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Rebuild spatial index with restored nodes
|
|
354
|
+
engine._spatialIndex.buildFromNodes(engine._nodes);
|
|
355
|
+
engine._spatialIndex.buildEdgeIndex(engine._edges, engine._nodeMap);
|
|
356
|
+
engine.markDirty();
|
|
357
|
+
|
|
358
|
+
// Emit event
|
|
359
|
+
engine._emit('clusterExpanded', { clusterId: clusterId, nodeCount: cluster.nodes.length });
|
|
360
|
+
|
|
361
|
+
// Fit to expanded nodes after animation
|
|
362
|
+
if (fitAfter && !animate) {
|
|
363
|
+
engine._viewport.fitToNodes(cluster.nodes, { padding: 80, animation: { duration: 600 } });
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Collapse nodes back into cluster (reverse of expand).
|
|
369
|
+
* @param {Array} nodeIds — nodes to re-cluster
|
|
370
|
+
*/
|
|
371
|
+
Clusterer.prototype.collapseNodes = function(nodeIds) {
|
|
372
|
+
// Simply rebuild clusters — the auto-clustering will re-aggregate
|
|
373
|
+
this.rebuild();
|
|
374
|
+
this._engine.markDirty();
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// ── Spring Animation ──────────────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Start the expand/collapse animation loop.
|
|
381
|
+
*/
|
|
382
|
+
Clusterer.prototype._startAnimation = function() {
|
|
383
|
+
if (this._animating) return;
|
|
384
|
+
this._animating = true;
|
|
385
|
+
|
|
386
|
+
var self = this;
|
|
387
|
+
var engine = this._engine;
|
|
388
|
+
var startTime = performance.now();
|
|
389
|
+
|
|
390
|
+
function animate(now) {
|
|
391
|
+
var allDone = true;
|
|
392
|
+
var elapsed = now - startTime;
|
|
393
|
+
|
|
394
|
+
for (var i = self._animations.length - 1; i >= 0; i--) {
|
|
395
|
+
var a = self._animations[i];
|
|
396
|
+
a.t = Math.min(elapsed / a.duration, 1);
|
|
397
|
+
|
|
398
|
+
// Spring easing function
|
|
399
|
+
var progress = self._springEase(a.t);
|
|
400
|
+
|
|
401
|
+
a.node.x = a.startX + (a.targetX - a.startX) * progress;
|
|
402
|
+
a.node.y = a.startY + (a.targetY - a.startY) * progress;
|
|
403
|
+
|
|
404
|
+
if (a.t >= 1) {
|
|
405
|
+
// Ensure final position
|
|
406
|
+
a.node.x = a.targetX;
|
|
407
|
+
a.node.y = a.targetY;
|
|
408
|
+
self._animations.splice(i, 1);
|
|
409
|
+
} else {
|
|
410
|
+
allDone = false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Rebuild spatial index during animation
|
|
415
|
+
engine._spatialIndex.buildFromNodes(engine._nodes);
|
|
416
|
+
engine._spatialIndex.buildEdgeIndex(engine._edges, engine._nodeMap);
|
|
417
|
+
engine.markDirty();
|
|
418
|
+
|
|
419
|
+
if (allDone) {
|
|
420
|
+
self._animating = false;
|
|
421
|
+
self._animRafId = null;
|
|
422
|
+
// Fit to expanded nodes
|
|
423
|
+
var expandedNodes = engine._nodes.filter(function(n) { return n._visible; });
|
|
424
|
+
// Don't auto-fit — let user control viewport
|
|
425
|
+
} else {
|
|
426
|
+
self._animRafId = requestAnimationFrame(animate);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
this._animRafId = requestAnimationFrame(animate);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Spring easing: overshoot + settle.
|
|
435
|
+
* Based on a critically damped spring model.
|
|
436
|
+
*/
|
|
437
|
+
Clusterer.prototype._springEase = function(t) {
|
|
438
|
+
// Spring parameters
|
|
439
|
+
var frequency = 4.5; // oscillation frequency
|
|
440
|
+
var damping = 0.7; // damping ratio
|
|
441
|
+
if (t >= 1) return 1;
|
|
442
|
+
return 1 - Math.exp(-damping * t * 10) * Math.cos(frequency * t * Math.PI);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Set clustering threshold.
|
|
447
|
+
*/
|
|
448
|
+
Clusterer.prototype.setThreshold = function(threshold) {
|
|
449
|
+
this._clusterThreshold = threshold;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Set grid size.
|
|
454
|
+
*/
|
|
455
|
+
Clusterer.prototype.setGridSize = function(size) {
|
|
456
|
+
this._gridSize = size;
|
|
457
|
+
};
|
|
458
|
+
`;
|
|
459
|
+
}
|
|
460
|
+
//# sourceMappingURL=clusterer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clusterer.js","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/clusterer.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAEH,gDAybC;AAzbD,SAAgB,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAubR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphCanvas Core — Canvas2D 渲染器骨架
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* - 创建/管理 Canvas 元素
|
|
6
|
+
* - 维护节点/边数据
|
|
7
|
+
* - requestAnimationFrame 渲染循环
|
|
8
|
+
* - 协调各子模块 (Viewport, SpatialIndex, Renderer, etc.)
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCoreScript(): string;
|
|
11
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,aAAa,IAAI,MAAM,CAkmCtC"}
|