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,541 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LayoutEngine — Web Worker 力导向布局
|
|
4
|
+
*
|
|
5
|
+
* 将力导向布局移入 Web Worker,避免阻塞 UI 主线程。
|
|
6
|
+
* Worker 每迭代一批后通过 postMessage 发送节点位置。
|
|
7
|
+
* 主线程增量更新渲染。
|
|
8
|
+
*
|
|
9
|
+
* Phase 8B 升级:
|
|
10
|
+
* - Barnes-Hut 四叉树: O(n log n) 排斥力近似 (θ=0.8)
|
|
11
|
+
* - 自适应算法: <1K 用原始 FR, >1K 用 Barnes-Hut
|
|
12
|
+
* - 提前终止: 能量阈值收敛检测
|
|
13
|
+
* - 进度报告: 百分比 + 剩余时间估算
|
|
14
|
+
*
|
|
15
|
+
* 当前版本: 内联 Worker (Blob URL),无外部依赖。
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.getLayoutWorkerScript = getLayoutWorkerScript;
|
|
19
|
+
function getLayoutWorkerScript() {
|
|
20
|
+
return `
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// LayoutEngine — Web Worker Force-Directed Layout (Barnes-Hut Optimized)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
function LayoutEngine(engine) {
|
|
26
|
+
this._engine = engine;
|
|
27
|
+
this._worker = null;
|
|
28
|
+
this._isRunning = false;
|
|
29
|
+
this._iterationCount = 0;
|
|
30
|
+
this._maxIterations = 300;
|
|
31
|
+
this._onStabilized = null;
|
|
32
|
+
this._startTime = 0;
|
|
33
|
+
|
|
34
|
+
// Layout parameters
|
|
35
|
+
this._options = {
|
|
36
|
+
gravity: 0.015,
|
|
37
|
+
repulsion: -80,
|
|
38
|
+
springLength: 150,
|
|
39
|
+
springConstant: 0.05,
|
|
40
|
+
damping: 0.4,
|
|
41
|
+
batchSize: 10, // iterations per message
|
|
42
|
+
idealEdgeLength: 120,
|
|
43
|
+
theta: 0.8, // Barnes-Hut approximation parameter
|
|
44
|
+
earlyTerminationThreshold: 0.5, // stop when energy below this
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Start the force-directed layout.
|
|
50
|
+
* Automatically selects Barnes-Hut for >1000 nodes.
|
|
51
|
+
*/
|
|
52
|
+
LayoutEngine.prototype.start = function(options) {
|
|
53
|
+
if (this._isRunning) this.stop();
|
|
54
|
+
|
|
55
|
+
var opts = Object.assign({}, this._options, options || {});
|
|
56
|
+
this._maxIterations = opts.maxIterations || 300;
|
|
57
|
+
this._iterationCount = 0;
|
|
58
|
+
this._isRunning = true;
|
|
59
|
+
this._startTime = performance.now();
|
|
60
|
+
|
|
61
|
+
var engine = this._engine;
|
|
62
|
+
var nodes = engine._nodes;
|
|
63
|
+
var edges = engine._edges;
|
|
64
|
+
|
|
65
|
+
// Auto-select algorithm based on node count
|
|
66
|
+
var useBarnesHut = nodes.length > 1000;
|
|
67
|
+
opts.useBarnesHut = useBarnesHut;
|
|
68
|
+
|
|
69
|
+
// Adapt max iterations for large graphs
|
|
70
|
+
if (nodes.length > 5000) {
|
|
71
|
+
opts.maxIterations = Math.min(opts.maxIterations, 150);
|
|
72
|
+
opts.batchSize = 5;
|
|
73
|
+
} else if (nodes.length > 10000) {
|
|
74
|
+
opts.maxIterations = Math.min(opts.maxIterations, 80);
|
|
75
|
+
opts.batchSize = 3;
|
|
76
|
+
}
|
|
77
|
+
this._maxIterations = opts.maxIterations;
|
|
78
|
+
|
|
79
|
+
// Prepare node/edge data for worker
|
|
80
|
+
var nodeData = [];
|
|
81
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
82
|
+
nodeData.push({
|
|
83
|
+
id: nodes[i].id,
|
|
84
|
+
x: nodes[i].x,
|
|
85
|
+
y: nodes[i].y,
|
|
86
|
+
radius: nodes[i]._radius || 10,
|
|
87
|
+
degree: nodes[i].degree || 0,
|
|
88
|
+
type: nodes[i].type,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
var edgeData = [];
|
|
92
|
+
for (var i = 0; i < edges.length; i++) {
|
|
93
|
+
edgeData.push({ from: edges[i].from, to: edges[i].to });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create inline Worker
|
|
97
|
+
var workerCode = this._getWorkerCode();
|
|
98
|
+
var blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
99
|
+
var workerUrl = URL.createObjectURL(blob);
|
|
100
|
+
this._worker = new Worker(workerUrl);
|
|
101
|
+
URL.revokeObjectURL(workerUrl);
|
|
102
|
+
|
|
103
|
+
var self = this;
|
|
104
|
+
|
|
105
|
+
this._worker.onmessage = function(e) {
|
|
106
|
+
var msg = e.data;
|
|
107
|
+
|
|
108
|
+
if (msg.type === 'positions') {
|
|
109
|
+
// ── Phase-8C: Zero-copy Float32Array path ──
|
|
110
|
+
if (msg.positionBuffer && msg.positionBuffer instanceof Float32Array) {
|
|
111
|
+
var buf = msg.positionBuffer;
|
|
112
|
+
// Bulk update from typed array (stride=2: x0,y0,x1,y1,...)
|
|
113
|
+
for (var i = 0; i < engine._nodes.length && i * 2 + 1 < buf.length; i++) {
|
|
114
|
+
engine._nodes[i].x = buf[i * 2];
|
|
115
|
+
engine._nodes[i].y = buf[i * 2 + 1];
|
|
116
|
+
}
|
|
117
|
+
// Sync to engine's own TypedArray
|
|
118
|
+
if (engine._positionArray && engine._positionArray.length === buf.length) {
|
|
119
|
+
engine._positionArray.set(buf);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Legacy JS object array path
|
|
123
|
+
var positions = msg.positions;
|
|
124
|
+
for (var i = 0; i < positions.length; i++) {
|
|
125
|
+
var p = positions[i];
|
|
126
|
+
var node = engine._nodeMap[p.id];
|
|
127
|
+
if (node) {
|
|
128
|
+
node.x = p.x;
|
|
129
|
+
node.y = p.y;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Rebuild spatial indices (nodes + edges)
|
|
135
|
+
engine._spatialIndex.buildFromNodes(engine._nodes);
|
|
136
|
+
engine._spatialIndex.buildEdgeIndex(engine._edges, engine._nodeMap);
|
|
137
|
+
engine.markDirty();
|
|
138
|
+
self._iterationCount = msg.iteration;
|
|
139
|
+
|
|
140
|
+
// Calculate elapsed time and ETA
|
|
141
|
+
var elapsed = performance.now() - self._startTime;
|
|
142
|
+
var progress = msg.iteration / self._maxIterations;
|
|
143
|
+
var eta = progress > 0.01 ? (elapsed / progress - elapsed) : 0;
|
|
144
|
+
|
|
145
|
+
engine._emit('layoutProgress', {
|
|
146
|
+
iteration: msg.iteration,
|
|
147
|
+
maxIterations: self._maxIterations,
|
|
148
|
+
percent: Math.round(progress * 100),
|
|
149
|
+
energy: msg.energy,
|
|
150
|
+
algorithm: msg.algorithm || 'unknown',
|
|
151
|
+
elapsed: Math.round(elapsed),
|
|
152
|
+
eta: Math.round(eta),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (msg.type === 'done') {
|
|
157
|
+
self._isRunning = false;
|
|
158
|
+
var totalTime = Math.round(performance.now() - self._startTime);
|
|
159
|
+
engine._emit('stabilizationDone', {
|
|
160
|
+
iterations: msg.iteration,
|
|
161
|
+
totalTimeMs: totalTime,
|
|
162
|
+
algorithm: msg.algorithm || 'unknown',
|
|
163
|
+
reason: msg.reason || 'maxIterations',
|
|
164
|
+
});
|
|
165
|
+
if (self._onStabilized) {
|
|
166
|
+
self._onStabilized();
|
|
167
|
+
self._onStabilized = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// ── Phase-8C: Send initial positions as Float32Array (Transferable) ──
|
|
173
|
+
var initPositionBuf = new Float32Array(nodeData.length * 2);
|
|
174
|
+
for (var i = 0; i < nodeData.length; i++) {
|
|
175
|
+
initPositionBuf[i * 2] = nodeData[i].x;
|
|
176
|
+
initPositionBuf[i * 2 + 1] = nodeData[i].y;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this._worker.postMessage({
|
|
180
|
+
type: 'init',
|
|
181
|
+
nodes: nodeData,
|
|
182
|
+
edges: edgeData,
|
|
183
|
+
options: opts,
|
|
184
|
+
positionBuffer: initPositionBuf,
|
|
185
|
+
}, [initPositionBuf.buffer]); // Transferable — zero-copy
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Stop the layout simulation.
|
|
190
|
+
*/
|
|
191
|
+
LayoutEngine.prototype.stop = function() {
|
|
192
|
+
if (this._worker) {
|
|
193
|
+
this._worker.terminate();
|
|
194
|
+
this._worker = null;
|
|
195
|
+
}
|
|
196
|
+
this._isRunning = false;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Destroy the layout engine.
|
|
201
|
+
*/
|
|
202
|
+
LayoutEngine.prototype.destroy = function() {
|
|
203
|
+
this.stop();
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Generate the Web Worker code as a string.
|
|
208
|
+
* Includes Barnes-Hut QuadTree and adaptive algorithm selection.
|
|
209
|
+
*/
|
|
210
|
+
LayoutEngine.prototype._getWorkerCode = function() {
|
|
211
|
+
return \`
|
|
212
|
+
var nodes = [];
|
|
213
|
+
var edges = [];
|
|
214
|
+
var nodeMap = {};
|
|
215
|
+
var options = {};
|
|
216
|
+
var iteration = 0;
|
|
217
|
+
var maxIterations = 300;
|
|
218
|
+
|
|
219
|
+
self.onmessage = function(e) {
|
|
220
|
+
var msg = e.data;
|
|
221
|
+
if (msg.type === 'init') {
|
|
222
|
+
nodes = msg.nodes;
|
|
223
|
+
edges = msg.edges;
|
|
224
|
+
options = msg.options || {};
|
|
225
|
+
maxIterations = options.maxIterations || 300;
|
|
226
|
+
nodeMap = {};
|
|
227
|
+
|
|
228
|
+
// Accept Float32Array positions (Phase-8C zero-copy)
|
|
229
|
+
var posBuf = msg.positionBuffer;
|
|
230
|
+
|
|
231
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
232
|
+
if (posBuf && i * 2 + 1 < posBuf.length) {
|
|
233
|
+
nodes[i].x = posBuf[i * 2];
|
|
234
|
+
nodes[i].y = posBuf[i * 2 + 1];
|
|
235
|
+
}
|
|
236
|
+
nodes[i].vx = 0;
|
|
237
|
+
nodes[i].vy = 0;
|
|
238
|
+
nodes[i].fx = 0;
|
|
239
|
+
nodes[i].fy = 0;
|
|
240
|
+
nodes[i].mass = 1 + (nodes[i].degree || 0) * 0.1;
|
|
241
|
+
nodeMap[nodes[i].id] = nodes[i];
|
|
242
|
+
}
|
|
243
|
+
iteration = 0;
|
|
244
|
+
runSimulation();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// ================================================================
|
|
249
|
+
// QuadTree for Barnes-Hut approximation
|
|
250
|
+
// ================================================================
|
|
251
|
+
|
|
252
|
+
function QuadTree(x, y, w, h) {
|
|
253
|
+
this.x = x; // center X
|
|
254
|
+
this.y = y; // center Y
|
|
255
|
+
this.w = w; // half-width
|
|
256
|
+
this.h = h; // half-height
|
|
257
|
+
this.body = null; // single body (leaf)
|
|
258
|
+
this.mass = 0; // total mass
|
|
259
|
+
this.cx = 0; // center of mass X
|
|
260
|
+
this.cy = 0; // center of mass Y
|
|
261
|
+
this.nw = null; // children
|
|
262
|
+
this.ne = null;
|
|
263
|
+
this.sw = null;
|
|
264
|
+
this.se = null;
|
|
265
|
+
this.isLeaf = true;
|
|
266
|
+
this.isEmpty = true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Insert a body into the quadtree.
|
|
271
|
+
*/
|
|
272
|
+
QuadTree.prototype.insert = function(body) {
|
|
273
|
+
if (this.isEmpty) {
|
|
274
|
+
this.body = body;
|
|
275
|
+
this.mass = body.mass;
|
|
276
|
+
this.cx = body.x;
|
|
277
|
+
this.cy = body.y;
|
|
278
|
+
this.isEmpty = false;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (this.isLeaf) {
|
|
283
|
+
// Subdivide
|
|
284
|
+
this._subdivide();
|
|
285
|
+
// Re-insert existing body
|
|
286
|
+
this._insertIntoChild(this.body);
|
|
287
|
+
this.body = null;
|
|
288
|
+
this.isLeaf = false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Insert new body into appropriate child
|
|
292
|
+
this._insertIntoChild(body);
|
|
293
|
+
|
|
294
|
+
// Update mass and center of mass
|
|
295
|
+
var totalMass = this.mass + body.mass;
|
|
296
|
+
this.cx = (this.cx * this.mass + body.x * body.mass) / totalMass;
|
|
297
|
+
this.cy = (this.cy * this.mass + body.y * body.mass) / totalMass;
|
|
298
|
+
this.mass = totalMass;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
QuadTree.prototype._subdivide = function() {
|
|
302
|
+
var hw = this.w / 2;
|
|
303
|
+
var hh = this.h / 2;
|
|
304
|
+
this.nw = new QuadTree(this.x - hw, this.y - hh, hw, hh);
|
|
305
|
+
this.ne = new QuadTree(this.x + hw, this.y - hh, hw, hh);
|
|
306
|
+
this.sw = new QuadTree(this.x - hw, this.y + hh, hw, hh);
|
|
307
|
+
this.se = new QuadTree(this.x + hw, this.y + hh, hw, hh);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
QuadTree.prototype._insertIntoChild = function(body) {
|
|
311
|
+
if (body.x <= this.x) {
|
|
312
|
+
if (body.y <= this.y) this.nw.insert(body);
|
|
313
|
+
else this.sw.insert(body);
|
|
314
|
+
} else {
|
|
315
|
+
if (body.y <= this.y) this.ne.insert(body);
|
|
316
|
+
else this.se.insert(body);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Calculate repulsive force on a body using Barnes-Hut approximation.
|
|
322
|
+
* theta: opening angle parameter (0.8 is typical)
|
|
323
|
+
*/
|
|
324
|
+
QuadTree.prototype.calculateForce = function(body, repulsion, theta) {
|
|
325
|
+
if (this.isEmpty) return;
|
|
326
|
+
|
|
327
|
+
var dx = this.cx - body.x;
|
|
328
|
+
var dy = this.cy - body.y;
|
|
329
|
+
var distSq = dx * dx + dy * dy;
|
|
330
|
+
if (distSq < 1) distSq = 1;
|
|
331
|
+
|
|
332
|
+
// If this is a leaf with a single body
|
|
333
|
+
if (this.isLeaf) {
|
|
334
|
+
if (this.body === body) return; // skip self
|
|
335
|
+
var dist = Math.sqrt(distSq);
|
|
336
|
+
var force = repulsion * body.mass * this.mass / distSq;
|
|
337
|
+
body.fx -= (dx / dist) * force;
|
|
338
|
+
body.fy -= (dy / dist) * force;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Barnes-Hut criterion: s/d < theta → treat as single body
|
|
343
|
+
var s = this.w * 2; // cell size
|
|
344
|
+
if (s * s / distSq < theta * theta) {
|
|
345
|
+
var dist = Math.sqrt(distSq);
|
|
346
|
+
var force = repulsion * body.mass * this.mass / distSq;
|
|
347
|
+
body.fx -= (dx / dist) * force;
|
|
348
|
+
body.fy -= (dy / dist) * force;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Otherwise, recurse into children
|
|
353
|
+
if (this.nw) this.nw.calculateForce(body, repulsion, theta);
|
|
354
|
+
if (this.ne) this.ne.calculateForce(body, repulsion, theta);
|
|
355
|
+
if (this.sw) this.sw.calculateForce(body, repulsion, theta);
|
|
356
|
+
if (this.se) this.se.calculateForce(body, repulsion, theta);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Build a QuadTree from all nodes.
|
|
361
|
+
*/
|
|
362
|
+
function buildQuadTree(nodes) {
|
|
363
|
+
// Find bounds
|
|
364
|
+
var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
365
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
366
|
+
if (nodes[i].x < minX) minX = nodes[i].x;
|
|
367
|
+
if (nodes[i].y < minY) minY = nodes[i].y;
|
|
368
|
+
if (nodes[i].x > maxX) maxX = nodes[i].x;
|
|
369
|
+
if (nodes[i].y > maxY) maxY = nodes[i].y;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
var cx = (minX + maxX) / 2;
|
|
373
|
+
var cy = (minY + maxY) / 2;
|
|
374
|
+
var hw = Math.max((maxX - minX) / 2, (maxY - minY) / 2) + 1;
|
|
375
|
+
|
|
376
|
+
var qt = new QuadTree(cx, cy, hw, hw);
|
|
377
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
378
|
+
qt.insert(nodes[i]);
|
|
379
|
+
}
|
|
380
|
+
return qt;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ================================================================
|
|
384
|
+
// Simulation Loop
|
|
385
|
+
// ================================================================
|
|
386
|
+
|
|
387
|
+
function runSimulation() {
|
|
388
|
+
var batchSize = options.batchSize || 10;
|
|
389
|
+
var springLength = options.springLength || 150;
|
|
390
|
+
var springK = options.springConstant || 0.05;
|
|
391
|
+
var repulsion = Math.abs(options.repulsion || 80);
|
|
392
|
+
var gravity = options.gravity || 0.015;
|
|
393
|
+
var damping = options.damping || 0.4;
|
|
394
|
+
var theta = options.theta || 0.8;
|
|
395
|
+
var useBarnesHut = options.useBarnesHut || false;
|
|
396
|
+
var earlyTermThreshold = options.earlyTerminationThreshold || 0.5;
|
|
397
|
+
var algorithm = useBarnesHut ? 'barnes-hut' : 'fruchterman-reingold';
|
|
398
|
+
|
|
399
|
+
// Track energy for early termination
|
|
400
|
+
var lastEnergies = [];
|
|
401
|
+
var stableCount = 0;
|
|
402
|
+
|
|
403
|
+
function tick() {
|
|
404
|
+
var batchEnd = Math.min(iteration + batchSize, maxIterations);
|
|
405
|
+
var totalEnergy = 0;
|
|
406
|
+
|
|
407
|
+
while (iteration < batchEnd) {
|
|
408
|
+
var temperature = 1 - iteration / maxIterations; // cooling
|
|
409
|
+
temperature = Math.max(temperature, 0.01);
|
|
410
|
+
|
|
411
|
+
// Reset forces
|
|
412
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
413
|
+
nodes[i].fx = 0;
|
|
414
|
+
nodes[i].fy = 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ── Repulsion ──
|
|
418
|
+
if (useBarnesHut) {
|
|
419
|
+
// Barnes-Hut: O(n log n)
|
|
420
|
+
var qt = buildQuadTree(nodes);
|
|
421
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
422
|
+
qt.calculateForce(nodes[i], repulsion, theta);
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
// Classic: O(n²) — only for small graphs
|
|
426
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
427
|
+
for (var j = i + 1; j < nodes.length; j++) {
|
|
428
|
+
var dx = nodes[j].x - nodes[i].x;
|
|
429
|
+
var dy = nodes[j].y - nodes[i].y;
|
|
430
|
+
var distSq = dx * dx + dy * dy;
|
|
431
|
+
if (distSq < 1) distSq = 1;
|
|
432
|
+
var dist = Math.sqrt(distSq);
|
|
433
|
+
var force = repulsion / distSq;
|
|
434
|
+
var fx = (dx / dist) * force;
|
|
435
|
+
var fy = (dy / dist) * force;
|
|
436
|
+
nodes[i].fx -= fx;
|
|
437
|
+
nodes[i].fy -= fy;
|
|
438
|
+
nodes[j].fx += fx;
|
|
439
|
+
nodes[j].fy += fy;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ── Attraction (edges) ──
|
|
445
|
+
for (var i = 0; i < edges.length; i++) {
|
|
446
|
+
var from = nodeMap[edges[i].from];
|
|
447
|
+
var to = nodeMap[edges[i].to];
|
|
448
|
+
if (!from || !to) continue;
|
|
449
|
+
var dx = to.x - from.x;
|
|
450
|
+
var dy = to.y - from.y;
|
|
451
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
452
|
+
if (dist < 1) dist = 1;
|
|
453
|
+
var force = (dist - springLength) * springK;
|
|
454
|
+
var fx = (dx / dist) * force;
|
|
455
|
+
var fy = (dy / dist) * force;
|
|
456
|
+
from.fx += fx;
|
|
457
|
+
from.fy += fy;
|
|
458
|
+
to.fx -= fx;
|
|
459
|
+
to.fy -= fy;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ── Gravity (pull toward center) ──
|
|
463
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
464
|
+
nodes[i].fx -= nodes[i].x * gravity;
|
|
465
|
+
nodes[i].fy -= nodes[i].y * gravity;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ── Apply forces with damping and temperature ──
|
|
469
|
+
totalEnergy = 0;
|
|
470
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
471
|
+
nodes[i].vx = (nodes[i].vx + nodes[i].fx) * damping * temperature;
|
|
472
|
+
nodes[i].vy = (nodes[i].vy + nodes[i].fy) * damping * temperature;
|
|
473
|
+
|
|
474
|
+
// Clamp velocity
|
|
475
|
+
var speed = Math.sqrt(nodes[i].vx * nodes[i].vx + nodes[i].vy * nodes[i].vy);
|
|
476
|
+
var maxSpeed = 50 * temperature;
|
|
477
|
+
if (speed > maxSpeed) {
|
|
478
|
+
nodes[i].vx *= maxSpeed / speed;
|
|
479
|
+
nodes[i].vy *= maxSpeed / speed;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
nodes[i].x += nodes[i].vx;
|
|
483
|
+
nodes[i].y += nodes[i].vy;
|
|
484
|
+
totalEnergy += speed;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
iteration++;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ── Early termination check ──
|
|
491
|
+
lastEnergies.push(totalEnergy);
|
|
492
|
+
if (lastEnergies.length > 5) lastEnergies.shift();
|
|
493
|
+
|
|
494
|
+
// Check if energy has stabilized
|
|
495
|
+
if (lastEnergies.length >= 5) {
|
|
496
|
+
var avgEnergy = 0;
|
|
497
|
+
for (var i = 0; i < lastEnergies.length; i++) avgEnergy += lastEnergies[i];
|
|
498
|
+
avgEnergy /= lastEnergies.length;
|
|
499
|
+
if (avgEnergy < earlyTermThreshold * nodes.length) {
|
|
500
|
+
stableCount++;
|
|
501
|
+
} else {
|
|
502
|
+
stableCount = 0;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Send positions back to main thread via Float32Array (zero-copy)
|
|
507
|
+
var posBuf = new Float32Array(nodes.length * 2);
|
|
508
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
509
|
+
posBuf[i * 2] = nodes[i].x;
|
|
510
|
+
posBuf[i * 2 + 1] = nodes[i].y;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
self.postMessage({
|
|
514
|
+
type: 'positions',
|
|
515
|
+
positionBuffer: posBuf,
|
|
516
|
+
iteration: iteration,
|
|
517
|
+
energy: totalEnergy,
|
|
518
|
+
algorithm: algorithm,
|
|
519
|
+
}, [posBuf.buffer]); // Transferable — zero-copy to main thread
|
|
520
|
+
|
|
521
|
+
// Check termination conditions
|
|
522
|
+
var earlyDone = stableCount >= 3;
|
|
523
|
+
if (iteration >= maxIterations || earlyDone) {
|
|
524
|
+
self.postMessage({
|
|
525
|
+
type: 'done',
|
|
526
|
+
iteration: iteration,
|
|
527
|
+
algorithm: algorithm,
|
|
528
|
+
reason: earlyDone ? 'converged' : 'maxIterations',
|
|
529
|
+
});
|
|
530
|
+
} else {
|
|
531
|
+
setTimeout(tick, 0); // yield to message queue
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
tick();
|
|
536
|
+
}
|
|
537
|
+
\`;
|
|
538
|
+
};
|
|
539
|
+
`;
|
|
540
|
+
}
|
|
541
|
+
//# sourceMappingURL=layout-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-worker.js","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/layout-worker.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAEH,sDAygBC;AAzgBD,SAAgB,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAugBR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LODManager — 多级细节渲染
|
|
3
|
+
*
|
|
4
|
+
* 3 级 LOD:
|
|
5
|
+
* - Level 0 (全景, zoom < thresholds[0]): 彩色圆点,无标签,直线边
|
|
6
|
+
* - Level 1 (标准, thresholds[0] ~ thresholds[1]): 形状 + 标签 + 虚线边
|
|
7
|
+
* - Level 2 (特写, > thresholds[1]): 阴影 + 详情 + 曲线边
|
|
8
|
+
*/
|
|
9
|
+
export declare function getLODScript(): string;
|
|
10
|
+
//# sourceMappingURL=lod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lod.d.ts","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/lod.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,YAAY,IAAI,MAAM,CAkGrC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LODManager — 多级细节渲染
|
|
4
|
+
*
|
|
5
|
+
* 3 级 LOD:
|
|
6
|
+
* - Level 0 (全景, zoom < thresholds[0]): 彩色圆点,无标签,直线边
|
|
7
|
+
* - Level 1 (标准, thresholds[0] ~ thresholds[1]): 形状 + 标签 + 虚线边
|
|
8
|
+
* - Level 2 (特写, > thresholds[1]): 阴影 + 详情 + 曲线边
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getLODScript = getLODScript;
|
|
12
|
+
function getLODScript() {
|
|
13
|
+
return `
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// LODManager — Level of Detail
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
function LODManager(engine) {
|
|
19
|
+
this._engine = engine;
|
|
20
|
+
|
|
21
|
+
// LOD thresholds (scale values)
|
|
22
|
+
this._thresholds = [0.15, 0.5];
|
|
23
|
+
// [0, 0.15) → level 0 (minimal)
|
|
24
|
+
// [0.15, 0.5) → level 1 (standard)
|
|
25
|
+
// [0.5, ∞) → level 2 (detailed)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the LOD level for the current zoom scale.
|
|
30
|
+
* @param {number} scale - Current viewport scale
|
|
31
|
+
* @returns {number} 0, 1, or 2
|
|
32
|
+
*/
|
|
33
|
+
LODManager.prototype.getLevel = function(scale) {
|
|
34
|
+
if (scale < this._thresholds[0]) return 0;
|
|
35
|
+
if (scale < this._thresholds[1]) return 1;
|
|
36
|
+
return 2;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get opacity factor for smooth LOD transitions.
|
|
41
|
+
* Returns a value between 0 and 1 for smooth crossfade.
|
|
42
|
+
* @param {number} scale
|
|
43
|
+
* @param {number} fromLevel
|
|
44
|
+
* @returns {number} opacity 0~1
|
|
45
|
+
*/
|
|
46
|
+
LODManager.prototype.getTransitionAlpha = function(scale, fromLevel) {
|
|
47
|
+
var t0 = this._thresholds[0];
|
|
48
|
+
var t1 = this._thresholds[1];
|
|
49
|
+
var blend = 0.3; // 30% of threshold range for smooth transition
|
|
50
|
+
|
|
51
|
+
if (fromLevel === 0) {
|
|
52
|
+
// Transitioning from level 0 → 1
|
|
53
|
+
var low = t0 * (1 - blend);
|
|
54
|
+
var high = t0 * (1 + blend);
|
|
55
|
+
if (scale <= low) return 0;
|
|
56
|
+
if (scale >= high) return 1;
|
|
57
|
+
return (scale - low) / (high - low);
|
|
58
|
+
}
|
|
59
|
+
if (fromLevel === 1) {
|
|
60
|
+
// Transitioning from level 1 → 2
|
|
61
|
+
var low = t1 * (1 - blend);
|
|
62
|
+
var high = t1 * (1 + blend);
|
|
63
|
+
if (scale <= low) return 0;
|
|
64
|
+
if (scale >= high) return 1;
|
|
65
|
+
return (scale - low) / (high - low);
|
|
66
|
+
}
|
|
67
|
+
return 1;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set custom LOD thresholds.
|
|
72
|
+
* @param {Array<number>} thresholds - [level0_max, level1_max]
|
|
73
|
+
*/
|
|
74
|
+
LODManager.prototype.setThresholds = function(thresholds) {
|
|
75
|
+
if (thresholds && thresholds.length >= 2) {
|
|
76
|
+
this._thresholds = thresholds.slice(0, 2);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Should labels be rendered at this scale?
|
|
82
|
+
*/
|
|
83
|
+
LODManager.prototype.shouldRenderLabels = function(scale) {
|
|
84
|
+
return scale >= this._thresholds[0];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Should arrows be rendered at this scale?
|
|
89
|
+
*/
|
|
90
|
+
LODManager.prototype.shouldRenderArrows = function(scale) {
|
|
91
|
+
return scale >= this._thresholds[0] * 0.8;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Should shadows be rendered at this scale?
|
|
96
|
+
*/
|
|
97
|
+
LODManager.prototype.shouldRenderShadows = function(scale) {
|
|
98
|
+
return scale >= this._thresholds[1];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the maximum label characters for the current LOD level.
|
|
103
|
+
*/
|
|
104
|
+
LODManager.prototype.getMaxLabelChars = function(lodLevel) {
|
|
105
|
+
if (lodLevel === 0) return 0;
|
|
106
|
+
if (lodLevel === 1) return 15;
|
|
107
|
+
return 40;
|
|
108
|
+
};
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=lod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lod.js","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/lod.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAEH,oCAkGC;AAlGD,SAAgB,YAAY;IAC1B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgGR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RenderPipeline — 视口裁剪 + 脏区域局部渲染
|
|
3
|
+
*
|
|
4
|
+
* 借鉴 LeaferJS Renderer 架构:
|
|
5
|
+
* - 全量渲染 (fullRender): 清空并重绘所有可见内容
|
|
6
|
+
* - 局部渲染 (partRender): 仅重绘脏区域内的节点/边
|
|
7
|
+
* - 视口裁剪: 只遍历 SpatialIndex.search(viewport) 返回的节点
|
|
8
|
+
*
|
|
9
|
+
* 渲染顺序: 背景 → 边 → 节点 → 标签 → 覆盖层 (panel/debug)
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRendererScript(): string;
|
|
12
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/visualize/graph-canvas/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,wBAAgB,iBAAiB,IAAI,MAAM,CA2pB1C"}
|