aifastdb-devplan 1.3.9 → 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,844 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GraphCanvas Core — Canvas2D 渲染器骨架
|
|
4
|
+
*
|
|
5
|
+
* 职责:
|
|
6
|
+
* - 创建/管理 Canvas 元素
|
|
7
|
+
* - 维护节点/边数据
|
|
8
|
+
* - requestAnimationFrame 渲染循环
|
|
9
|
+
* - 协调各子模块 (Viewport, SpatialIndex, Renderer, etc.)
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getCoreScript = getCoreScript;
|
|
13
|
+
function getCoreScript() {
|
|
14
|
+
return `
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// GraphCanvas Core
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GraphCanvas — 高性能 Canvas2D 图谱渲染引擎
|
|
21
|
+
*
|
|
22
|
+
* @param {HTMLElement} container - 容器 DOM 元素
|
|
23
|
+
* @param {Object} options - 配置选项
|
|
24
|
+
*/
|
|
25
|
+
function GraphCanvas(container, options) {
|
|
26
|
+
if (!container) throw new Error('GraphCanvas: container is required');
|
|
27
|
+
|
|
28
|
+
this._container = container;
|
|
29
|
+
this._options = Object.assign({
|
|
30
|
+
pixelRatio: Math.min(window.devicePixelRatio || 1, 2),
|
|
31
|
+
backgroundColor: '#111827',
|
|
32
|
+
maxFPS: 60,
|
|
33
|
+
debugMode: false,
|
|
34
|
+
}, options || {});
|
|
35
|
+
|
|
36
|
+
// ── Canvas Setup ──────────────────────────────────────────────────────
|
|
37
|
+
this._canvas = document.createElement('canvas');
|
|
38
|
+
this._canvas.style.cssText = 'width:100%;height:100%;display:block;outline:none;';
|
|
39
|
+
this._canvas.tabIndex = 0; // focusable for keyboard events
|
|
40
|
+
container.appendChild(this._canvas);
|
|
41
|
+
this._ctx = this._canvas.getContext('2d');
|
|
42
|
+
|
|
43
|
+
// ── Data ──────────────────────────────────────────────────────────────
|
|
44
|
+
this._nodes = []; // GraphNode[] — all nodes
|
|
45
|
+
this._edges = []; // GraphEdge[] — all edges
|
|
46
|
+
this._nodeMap = {}; // id → node (O(1) lookup)
|
|
47
|
+
this._edgeMap = {}; // id → edge
|
|
48
|
+
this._nodeEdges = {}; // nodeId → [edge, ...] (adjacency list)
|
|
49
|
+
this._nodeCount = 0;
|
|
50
|
+
this._edgeCount = 0;
|
|
51
|
+
|
|
52
|
+
// ── Sub-modules (created lazily or during init) ───────────────────────
|
|
53
|
+
this._viewport = new ViewportManager(this);
|
|
54
|
+
this._spatialIndex = new SpatialIndex();
|
|
55
|
+
this._renderer = new RenderPipeline(this);
|
|
56
|
+
this._lod = new LODManager(this);
|
|
57
|
+
this._interaction = new InteractionManager(this);
|
|
58
|
+
this._layoutEngine = null; // Created on demand
|
|
59
|
+
this._clusterer = null; // Created on demand
|
|
60
|
+
this._styles = new StyleManager();
|
|
61
|
+
|
|
62
|
+
// ── Render Loop State ─────────────────────────────────────────────────
|
|
63
|
+
this._rafId = null;
|
|
64
|
+
this._dirty = true; // needs full redraw
|
|
65
|
+
this._dirtyRects = []; // partial redraw regions [{x,y,w,h}, ...]
|
|
66
|
+
this._lastFrameTime = 0;
|
|
67
|
+
this._frameInterval = 1000 / this._options.maxFPS;
|
|
68
|
+
this._running = false;
|
|
69
|
+
this._sleeping = false; // true = render loop paused (no rAF)
|
|
70
|
+
this._frameCount = 0;
|
|
71
|
+
this._fpsTimer = 0;
|
|
72
|
+
this._currentFPS = 0;
|
|
73
|
+
this._idleFrames = 0; // consecutive frames with nothing to draw
|
|
74
|
+
this._idleThreshold = 30; // sleep after N idle frames (~0.5s at 60fps)
|
|
75
|
+
|
|
76
|
+
// ── Event Callbacks ───────────────────────────────────────────────────
|
|
77
|
+
this._eventHandlers = {}; // eventName → [callback, ...]
|
|
78
|
+
|
|
79
|
+
// ── Performance Metrics ───────────────────────────────────────────────
|
|
80
|
+
this._metrics = {
|
|
81
|
+
lastRenderMs: 0,
|
|
82
|
+
visibleNodes: 0,
|
|
83
|
+
visibleEdges: 0,
|
|
84
|
+
fps: 0,
|
|
85
|
+
totalNodes: 0,
|
|
86
|
+
totalEdges: 0,
|
|
87
|
+
visibleClusters: 0,
|
|
88
|
+
layoutProgress: 0,
|
|
89
|
+
layoutAlgorithm: '',
|
|
90
|
+
layoutEta: 0,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Listen for layout progress events to store in metrics
|
|
94
|
+
var self = this;
|
|
95
|
+
this.on('layoutProgress', function(data) {
|
|
96
|
+
self._metrics.layoutProgress = data.percent || 0;
|
|
97
|
+
self._metrics.layoutAlgorithm = data.algorithm || '';
|
|
98
|
+
self._metrics.layoutEta = data.eta || 0;
|
|
99
|
+
self.markDirty(); // Ensure progress bar redraws
|
|
100
|
+
});
|
|
101
|
+
this.on('stabilizationDone', function() {
|
|
102
|
+
self._metrics.layoutProgress = 0;
|
|
103
|
+
self._metrics.layoutAlgorithm = '';
|
|
104
|
+
self._metrics.layoutEta = 0;
|
|
105
|
+
self.markDirty();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ── Initialize ────────────────────────────────────────────────────────
|
|
109
|
+
this._resizeObserver = null;
|
|
110
|
+
this._handleResize();
|
|
111
|
+
this._setupResizeObserver();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Canvas Sizing ─────────────────────────────────────────────────────────
|
|
115
|
+
GraphCanvas.prototype._handleResize = function() {
|
|
116
|
+
var rect = this._container.getBoundingClientRect();
|
|
117
|
+
var pr = this._options.pixelRatio;
|
|
118
|
+
var w = Math.max(rect.width, 1);
|
|
119
|
+
var h = Math.max(rect.height, 1);
|
|
120
|
+
|
|
121
|
+
this._canvas.width = w * pr;
|
|
122
|
+
this._canvas.height = h * pr;
|
|
123
|
+
this._width = w;
|
|
124
|
+
this._height = h;
|
|
125
|
+
|
|
126
|
+
// Scale context for HiDPI
|
|
127
|
+
this._ctx.setTransform(pr, 0, 0, pr, 0, 0);
|
|
128
|
+
|
|
129
|
+
this.markDirty();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
GraphCanvas.prototype._setupResizeObserver = function() {
|
|
133
|
+
var self = this;
|
|
134
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
135
|
+
this._resizeObserver = new ResizeObserver(function() {
|
|
136
|
+
self._handleResize();
|
|
137
|
+
});
|
|
138
|
+
this._resizeObserver.observe(this._container);
|
|
139
|
+
} else {
|
|
140
|
+
// Fallback for older browsers
|
|
141
|
+
window.addEventListener('resize', function() { self._handleResize(); });
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// ── Data Management ───────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Set graph data (bulk load).
|
|
149
|
+
* @param {{ nodes: Array, edges: Array }} data
|
|
150
|
+
*/
|
|
151
|
+
GraphCanvas.prototype.setData = function(data) {
|
|
152
|
+
var nodes = data.nodes || [];
|
|
153
|
+
var edges = data.edges || [];
|
|
154
|
+
|
|
155
|
+
// Clear previous data
|
|
156
|
+
this._nodes = [];
|
|
157
|
+
this._edges = [];
|
|
158
|
+
this._nodeMap = {};
|
|
159
|
+
this._edgeMap = {};
|
|
160
|
+
this._nodeEdges = {};
|
|
161
|
+
this._nodeIndexMap = {}; // id → array index (for TypedArray)
|
|
162
|
+
|
|
163
|
+
// ── TypedArray backing store (Phase-8C) ──
|
|
164
|
+
// Float32Array for positions: [x0, y0, x1, y1, ...] (stride=2)
|
|
165
|
+
// Float32Array for properties: [radius0, degree0, radius1, degree1, ...] (stride=2)
|
|
166
|
+
var nodeCount = nodes.length;
|
|
167
|
+
this._positionArray = new Float32Array(nodeCount * 2);
|
|
168
|
+
this._propertyArray = new Float32Array(nodeCount * 2);
|
|
169
|
+
|
|
170
|
+
// Process nodes
|
|
171
|
+
for (var i = 0; i < nodeCount; i++) {
|
|
172
|
+
var n = nodes[i];
|
|
173
|
+
var px = n.x != null ? n.x : 0;
|
|
174
|
+
var py = n.y != null ? n.y : 0;
|
|
175
|
+
this._positionArray[i * 2] = px;
|
|
176
|
+
this._positionArray[i * 2 + 1] = py;
|
|
177
|
+
this._propertyArray[i * 2] = 10; // default radius
|
|
178
|
+
this._propertyArray[i * 2 + 1] = n.degree || 0;
|
|
179
|
+
|
|
180
|
+
var node = {
|
|
181
|
+
id: n.id,
|
|
182
|
+
label: n.label || n.id,
|
|
183
|
+
type: n.type || 'default',
|
|
184
|
+
x: px,
|
|
185
|
+
y: py,
|
|
186
|
+
_idx: i, // TypedArray index
|
|
187
|
+
// Computed fields
|
|
188
|
+
_screenX: 0,
|
|
189
|
+
_screenY: 0,
|
|
190
|
+
_radius: 10,
|
|
191
|
+
_visible: true,
|
|
192
|
+
_lodLevel: 2, // 0=minimal, 1=standard, 2=detailed
|
|
193
|
+
_hovered: false,
|
|
194
|
+
_selected: false,
|
|
195
|
+
_dragging: false,
|
|
196
|
+
_clustered: false,
|
|
197
|
+
_style: null, // cached style
|
|
198
|
+
_aabb: null, // { minX, minY, maxX, maxY } for R-tree
|
|
199
|
+
// Original data
|
|
200
|
+
properties: n.properties || {},
|
|
201
|
+
degree: n.degree || 0,
|
|
202
|
+
_origData: n,
|
|
203
|
+
};
|
|
204
|
+
this._nodes.push(node);
|
|
205
|
+
this._nodeMap[node.id] = node;
|
|
206
|
+
this._nodeIndexMap[node.id] = i;
|
|
207
|
+
this._nodeEdges[node.id] = [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Process edges
|
|
211
|
+
for (var i = 0; i < edges.length; i++) {
|
|
212
|
+
var e = edges[i];
|
|
213
|
+
var eid = e.id || ('e_' + i);
|
|
214
|
+
var edge = {
|
|
215
|
+
id: eid,
|
|
216
|
+
from: e.from,
|
|
217
|
+
to: e.to,
|
|
218
|
+
label: e.label || '',
|
|
219
|
+
_visible: true,
|
|
220
|
+
_highlighted: false,
|
|
221
|
+
_style: null,
|
|
222
|
+
_origData: e,
|
|
223
|
+
};
|
|
224
|
+
this._edges.push(edge);
|
|
225
|
+
this._edgeMap[eid] = edge;
|
|
226
|
+
if (this._nodeEdges[e.from]) this._nodeEdges[e.from].push(edge);
|
|
227
|
+
if (this._nodeEdges[e.to]) this._nodeEdges[e.to].push(edge);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this._nodeCount = this._nodes.length;
|
|
231
|
+
this._edgeCount = this._edges.length;
|
|
232
|
+
this._metrics.totalNodes = this._nodeCount;
|
|
233
|
+
this._metrics.totalEdges = this._edgeCount;
|
|
234
|
+
|
|
235
|
+
// Apply styles to all nodes/edges
|
|
236
|
+
this._styles.applyAllStyles(this._nodes, this._edges);
|
|
237
|
+
|
|
238
|
+
// Build spatial index (nodes + edges)
|
|
239
|
+
this._spatialIndex.buildFromNodes(this._nodes);
|
|
240
|
+
this._spatialIndex.buildEdgeIndex(this._edges, this._nodeMap);
|
|
241
|
+
|
|
242
|
+
// Auto-enable clusterer for larger datasets (>500 nodes)
|
|
243
|
+
if (this._nodeCount > 500) {
|
|
244
|
+
if (!this._clusterer) {
|
|
245
|
+
this._clusterer = new Clusterer(this);
|
|
246
|
+
}
|
|
247
|
+
this._clusterer.setEnabled(true);
|
|
248
|
+
} else if (this._clusterer) {
|
|
249
|
+
this._clusterer.setEnabled(false);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Assign initial positions if not set (random layout as starting point)
|
|
253
|
+
var hasPositions = false;
|
|
254
|
+
for (var i = 0; i < this._nodes.length; i++) {
|
|
255
|
+
if (this._nodes[i].x !== 0 || this._nodes[i].y !== 0) {
|
|
256
|
+
hasPositions = true;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!hasPositions && this._nodes.length > 0) {
|
|
261
|
+
this._assignRandomPositions();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.markDirty();
|
|
265
|
+
this._emit('dataLoaded', { nodeCount: this._nodeCount, edgeCount: this._edgeCount });
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Assign random initial positions for layout bootstrapping.
|
|
270
|
+
*/
|
|
271
|
+
GraphCanvas.prototype._assignRandomPositions = function() {
|
|
272
|
+
var spread = Math.sqrt(this._nodeCount) * 80;
|
|
273
|
+
for (var i = 0; i < this._nodes.length; i++) {
|
|
274
|
+
this._nodes[i].x = (Math.random() - 0.5) * spread;
|
|
275
|
+
this._nodes[i].y = (Math.random() - 0.5) * spread;
|
|
276
|
+
}
|
|
277
|
+
this._spatialIndex.buildFromNodes(this._nodes);
|
|
278
|
+
this._spatialIndex.buildEdgeIndex(this._edges, this._nodeMap);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// ── TypedArray Accessors (Phase-8C) ────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get node position from TypedArray (fast path for Worker sync).
|
|
285
|
+
* @param {number} index — node array index
|
|
286
|
+
* @returns {{ x: number, y: number }}
|
|
287
|
+
*/
|
|
288
|
+
GraphCanvas.prototype.getNodePos = function(index) {
|
|
289
|
+
return {
|
|
290
|
+
x: this._positionArray[index * 2],
|
|
291
|
+
y: this._positionArray[index * 2 + 1],
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Set node position in both TypedArray and node object.
|
|
297
|
+
* @param {number} index — node array index
|
|
298
|
+
* @param {number} x
|
|
299
|
+
* @param {number} y
|
|
300
|
+
*/
|
|
301
|
+
GraphCanvas.prototype.setNodePos = function(index, x, y) {
|
|
302
|
+
this._positionArray[index * 2] = x;
|
|
303
|
+
this._positionArray[index * 2 + 1] = y;
|
|
304
|
+
if (this._nodes[index]) {
|
|
305
|
+
this._nodes[index].x = x;
|
|
306
|
+
this._nodes[index].y = y;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Bulk sync: copy positions from TypedArray → node objects.
|
|
312
|
+
* Called after Worker returns Transferable ArrayBuffer.
|
|
313
|
+
*/
|
|
314
|
+
GraphCanvas.prototype._syncFromPositionArray = function() {
|
|
315
|
+
var arr = this._positionArray;
|
|
316
|
+
var nodes = this._nodes;
|
|
317
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
318
|
+
nodes[i].x = arr[i * 2];
|
|
319
|
+
nodes[i].y = arr[i * 2 + 1];
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Bulk sync: copy positions from node objects → TypedArray.
|
|
325
|
+
* Called before sending to Worker.
|
|
326
|
+
*/
|
|
327
|
+
GraphCanvas.prototype._syncToPositionArray = function() {
|
|
328
|
+
var arr = this._positionArray;
|
|
329
|
+
var nodes = this._nodes;
|
|
330
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
331
|
+
arr[i * 2] = nodes[i].x;
|
|
332
|
+
arr[i * 2 + 1] = nodes[i].y;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get a transferable copy of the position array for Worker.
|
|
338
|
+
* @returns {Float32Array} — new copy (original stays usable)
|
|
339
|
+
*/
|
|
340
|
+
GraphCanvas.prototype.getPositionBuffer = function() {
|
|
341
|
+
return new Float32Array(this._positionArray);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// ── Incremental Data API (Phase-8C T8C.5) ─────────────────────────────────
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Incrementally add nodes (batch).
|
|
348
|
+
* Appends to existing data without full rebuild.
|
|
349
|
+
* @param {Array} newNodes — [{id, label, type, x, y, ...}, ...]
|
|
350
|
+
*/
|
|
351
|
+
GraphCanvas.prototype.addNodes = function(newNodes) {
|
|
352
|
+
if (!newNodes || newNodes.length === 0) return;
|
|
353
|
+
|
|
354
|
+
var oldCount = this._nodeCount;
|
|
355
|
+
var totalCount = oldCount + newNodes.length;
|
|
356
|
+
|
|
357
|
+
// Grow TypedArrays
|
|
358
|
+
var newPosArray = new Float32Array(totalCount * 2);
|
|
359
|
+
var newPropArray = new Float32Array(totalCount * 2);
|
|
360
|
+
newPosArray.set(this._positionArray);
|
|
361
|
+
newPropArray.set(this._propertyArray);
|
|
362
|
+
|
|
363
|
+
for (var i = 0; i < newNodes.length; i++) {
|
|
364
|
+
var n = newNodes[i];
|
|
365
|
+
var idx = oldCount + i;
|
|
366
|
+
var px = n.x != null ? n.x : (Math.random() - 0.5) * Math.sqrt(totalCount) * 80;
|
|
367
|
+
var py = n.y != null ? n.y : (Math.random() - 0.5) * Math.sqrt(totalCount) * 80;
|
|
368
|
+
newPosArray[idx * 2] = px;
|
|
369
|
+
newPosArray[idx * 2 + 1] = py;
|
|
370
|
+
newPropArray[idx * 2] = 10;
|
|
371
|
+
newPropArray[idx * 2 + 1] = n.degree || 0;
|
|
372
|
+
|
|
373
|
+
var node = {
|
|
374
|
+
id: n.id,
|
|
375
|
+
label: n.label || n.id,
|
|
376
|
+
type: n.type || 'default',
|
|
377
|
+
x: px, y: py,
|
|
378
|
+
_idx: idx,
|
|
379
|
+
_screenX: 0, _screenY: 0,
|
|
380
|
+
_radius: 10, _visible: true, _lodLevel: 2,
|
|
381
|
+
_hovered: false, _selected: false, _dragging: false, _clustered: false,
|
|
382
|
+
_style: null, _aabb: null,
|
|
383
|
+
properties: n.properties || {},
|
|
384
|
+
degree: n.degree || 0,
|
|
385
|
+
_origData: n,
|
|
386
|
+
};
|
|
387
|
+
this._nodes.push(node);
|
|
388
|
+
this._nodeMap[node.id] = node;
|
|
389
|
+
this._nodeIndexMap[node.id] = idx;
|
|
390
|
+
this._nodeEdges[node.id] = [];
|
|
391
|
+
|
|
392
|
+
// Incremental R-tree insert
|
|
393
|
+
this._spatialIndex.insertNode(node);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this._positionArray = newPosArray;
|
|
397
|
+
this._propertyArray = newPropArray;
|
|
398
|
+
this._nodeCount = totalCount;
|
|
399
|
+
this._metrics.totalNodes = totalCount;
|
|
400
|
+
|
|
401
|
+
// Apply styles to new nodes
|
|
402
|
+
this._styles.applyAllStyles(this._nodes, this._edges);
|
|
403
|
+
|
|
404
|
+
this.markDirty();
|
|
405
|
+
this._emit('nodesAdded', { count: newNodes.length, total: totalCount });
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Incrementally add edges (batch).
|
|
410
|
+
* @param {Array} newEdges — [{id, from, to, label, ...}, ...]
|
|
411
|
+
*/
|
|
412
|
+
GraphCanvas.prototype.addEdges = function(newEdges) {
|
|
413
|
+
if (!newEdges || newEdges.length === 0) return;
|
|
414
|
+
|
|
415
|
+
for (var i = 0; i < newEdges.length; i++) {
|
|
416
|
+
var e = newEdges[i];
|
|
417
|
+
var eid = e.id || ('e_' + (this._edgeCount + i));
|
|
418
|
+
var edge = {
|
|
419
|
+
id: eid,
|
|
420
|
+
from: e.from,
|
|
421
|
+
to: e.to,
|
|
422
|
+
label: e.label || '',
|
|
423
|
+
_visible: true,
|
|
424
|
+
_highlighted: false,
|
|
425
|
+
_style: null,
|
|
426
|
+
_origData: e,
|
|
427
|
+
};
|
|
428
|
+
this._edges.push(edge);
|
|
429
|
+
this._edgeMap[eid] = edge;
|
|
430
|
+
if (this._nodeEdges[e.from]) this._nodeEdges[e.from].push(edge);
|
|
431
|
+
if (this._nodeEdges[e.to]) this._nodeEdges[e.to].push(edge);
|
|
432
|
+
|
|
433
|
+
// Incremental edge R-tree insert
|
|
434
|
+
var fromNode = this._nodeMap[e.from];
|
|
435
|
+
var toNode = this._nodeMap[e.to];
|
|
436
|
+
if (fromNode && toNode) {
|
|
437
|
+
this._spatialIndex.insertEdge(edge, fromNode, toNode);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this._edgeCount = this._edges.length;
|
|
442
|
+
this._metrics.totalEdges = this._edgeCount;
|
|
443
|
+
this._styles.applyAllStyles(this._nodes, this._edges);
|
|
444
|
+
this.markDirty();
|
|
445
|
+
this._emit('edgesAdded', { count: newEdges.length, total: this._edgeCount });
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// ── Dirty Marking ─────────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Mark the entire canvas as needing redraw.
|
|
452
|
+
*/
|
|
453
|
+
GraphCanvas.prototype.markDirty = function() {
|
|
454
|
+
this._dirty = true;
|
|
455
|
+
this._idleFrames = 0;
|
|
456
|
+
this._wakeRenderLoop();
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Mark a specific rectangular region as dirty (world coordinates).
|
|
461
|
+
* @param {number} x - World X
|
|
462
|
+
* @param {number} y - World Y
|
|
463
|
+
* @param {number} w - Width
|
|
464
|
+
* @param {number} h - Height
|
|
465
|
+
*/
|
|
466
|
+
GraphCanvas.prototype.markDirtyRect = function(x, y, w, h) {
|
|
467
|
+
this._dirtyRects.push({ x: x, y: y, w: w, h: h });
|
|
468
|
+
this._idleFrames = 0;
|
|
469
|
+
this._wakeRenderLoop();
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Mark a node's region as dirty — automatically computes AABB with padding.
|
|
474
|
+
* Also marks connected edges' regions as dirty.
|
|
475
|
+
* @param {Object} node — graph node with .x, .y, ._radius
|
|
476
|
+
* @param {Object} [prevPos] — { x, y } previous position (for drag, to dirty old + new area)
|
|
477
|
+
*/
|
|
478
|
+
GraphCanvas.prototype.markDirtyNode = function(node, prevPos) {
|
|
479
|
+
if (!node) return;
|
|
480
|
+
var r = (node._radius || 10);
|
|
481
|
+
// Padding accounts for glow/shadow, labels, and edge connection changes
|
|
482
|
+
var pad = r * 0.8 + 20 / Math.max(this._viewport.getScale(), 0.01);
|
|
483
|
+
var totalR = r + pad;
|
|
484
|
+
|
|
485
|
+
// Dirty the current node area
|
|
486
|
+
this._dirtyRects.push({
|
|
487
|
+
x: node.x - totalR,
|
|
488
|
+
y: node.y - totalR,
|
|
489
|
+
w: totalR * 2,
|
|
490
|
+
h: totalR * 2,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// If node moved, also dirty the old position
|
|
494
|
+
if (prevPos) {
|
|
495
|
+
this._dirtyRects.push({
|
|
496
|
+
x: prevPos.x - totalR,
|
|
497
|
+
y: prevPos.y - totalR,
|
|
498
|
+
w: totalR * 2,
|
|
499
|
+
h: totalR * 2,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Dirty connected edges — mark each connected node's area too
|
|
504
|
+
var connEdges = this._nodeEdges[node.id] || [];
|
|
505
|
+
for (var i = 0; i < connEdges.length; i++) {
|
|
506
|
+
var e = connEdges[i];
|
|
507
|
+
var otherId = e.from === node.id ? e.to : e.from;
|
|
508
|
+
var otherNode = this._nodeMap[otherId];
|
|
509
|
+
if (otherNode) {
|
|
510
|
+
var oR = (otherNode._radius || 10) + pad;
|
|
511
|
+
this._dirtyRects.push({
|
|
512
|
+
x: otherNode.x - oR,
|
|
513
|
+
y: otherNode.y - oR,
|
|
514
|
+
w: oR * 2,
|
|
515
|
+
h: oR * 2,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
this._idleFrames = 0;
|
|
521
|
+
this._wakeRenderLoop();
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Merge overlapping/adjacent dirty rects to reduce clip operations.
|
|
526
|
+
* Uses a greedy merge: merge any two rects that overlap or are close together.
|
|
527
|
+
* @returns {Array} merged rects [{x,y,w,h}, ...]
|
|
528
|
+
*/
|
|
529
|
+
GraphCanvas.prototype._mergeDirtyRects = function() {
|
|
530
|
+
var rects = this._dirtyRects;
|
|
531
|
+
if (rects.length <= 1) return rects;
|
|
532
|
+
|
|
533
|
+
// If too many dirty rects, just do a full redraw — cheaper than many clips
|
|
534
|
+
if (rects.length > 16) {
|
|
535
|
+
this._dirty = true;
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Convert to minX/minY/maxX/maxY for easier merging
|
|
540
|
+
var merged = [];
|
|
541
|
+
for (var i = 0; i < rects.length; i++) {
|
|
542
|
+
var r = rects[i];
|
|
543
|
+
merged.push({ minX: r.x, minY: r.y, maxX: r.x + r.w, maxY: r.y + r.h });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Greedy merge pass
|
|
547
|
+
var changed = true;
|
|
548
|
+
while (changed) {
|
|
549
|
+
changed = false;
|
|
550
|
+
for (var i = 0; i < merged.length; i++) {
|
|
551
|
+
for (var j = i + 1; j < merged.length; j++) {
|
|
552
|
+
var a = merged[i], b = merged[j];
|
|
553
|
+
// Check if rects overlap or are very close (within merge gap)
|
|
554
|
+
var gap = 20 / Math.max(this._viewport.getScale(), 0.01);
|
|
555
|
+
if (a.minX - gap <= b.maxX && a.maxX + gap >= b.minX &&
|
|
556
|
+
a.minY - gap <= b.maxY && a.maxY + gap >= b.minY) {
|
|
557
|
+
// Merge b into a
|
|
558
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
559
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
560
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
561
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
562
|
+
merged.splice(j, 1);
|
|
563
|
+
changed = true;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (changed) break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Convert back to {x,y,w,h}
|
|
572
|
+
var result = [];
|
|
573
|
+
for (var i = 0; i < merged.length; i++) {
|
|
574
|
+
var m = merged[i];
|
|
575
|
+
result.push({ x: m.minX, y: m.minY, w: m.maxX - m.minX, h: m.maxY - m.minY });
|
|
576
|
+
}
|
|
577
|
+
return result;
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// ── Render Loop ───────────────────────────────────────────────────────────
|
|
581
|
+
|
|
582
|
+
GraphCanvas.prototype._startRenderLoop = function() {
|
|
583
|
+
if (this._running) return;
|
|
584
|
+
this._running = true;
|
|
585
|
+
this._sleeping = false;
|
|
586
|
+
this._idleFrames = 0;
|
|
587
|
+
this._lastFrameTime = performance.now();
|
|
588
|
+
this._fpsTimer = this._lastFrameTime;
|
|
589
|
+
this._frameCount = 0;
|
|
590
|
+
var self = this;
|
|
591
|
+
function loop(now) {
|
|
592
|
+
self._rafId = requestAnimationFrame(loop);
|
|
593
|
+
// FPS throttle
|
|
594
|
+
var elapsed = now - self._lastFrameTime;
|
|
595
|
+
if (elapsed < self._frameInterval) return;
|
|
596
|
+
self._lastFrameTime = now - (elapsed % self._frameInterval);
|
|
597
|
+
|
|
598
|
+
// FPS counter
|
|
599
|
+
self._frameCount++;
|
|
600
|
+
if (now - self._fpsTimer >= 1000) {
|
|
601
|
+
self._currentFPS = self._frameCount;
|
|
602
|
+
self._metrics.fps = self._currentFPS;
|
|
603
|
+
self._frameCount = 0;
|
|
604
|
+
self._fpsTimer = now;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Render frame if needed
|
|
608
|
+
if (self._dirty || self._dirtyRects.length > 0) {
|
|
609
|
+
var t0 = performance.now();
|
|
610
|
+
self._renderFrame();
|
|
611
|
+
self._metrics.lastRenderMs = Math.round((performance.now() - t0) * 100) / 100;
|
|
612
|
+
self._idleFrames = 0;
|
|
613
|
+
} else {
|
|
614
|
+
// No work this frame — increment idle counter
|
|
615
|
+
self._idleFrames++;
|
|
616
|
+
if (self._idleFrames >= self._idleThreshold) {
|
|
617
|
+
// Go to sleep: stop rAF loop to save CPU/battery
|
|
618
|
+
self._sleep();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
this._rafId = requestAnimationFrame(loop);
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Put the render loop to sleep (stop rAF). Wakes on markDirty/markDirtyNode.
|
|
627
|
+
*/
|
|
628
|
+
GraphCanvas.prototype._sleep = function() {
|
|
629
|
+
if (this._rafId) {
|
|
630
|
+
cancelAnimationFrame(this._rafId);
|
|
631
|
+
this._rafId = null;
|
|
632
|
+
}
|
|
633
|
+
this._sleeping = true;
|
|
634
|
+
this._running = false;
|
|
635
|
+
this._metrics.fps = 0;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Wake the render loop from sleep (triggered by markDirty/markDirtyNode).
|
|
640
|
+
*/
|
|
641
|
+
GraphCanvas.prototype._wakeRenderLoop = function() {
|
|
642
|
+
if (!this._running) {
|
|
643
|
+
this._startRenderLoop();
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
GraphCanvas.prototype._stopRenderLoop = function() {
|
|
648
|
+
if (this._rafId) {
|
|
649
|
+
cancelAnimationFrame(this._rafId);
|
|
650
|
+
this._rafId = null;
|
|
651
|
+
}
|
|
652
|
+
this._running = false;
|
|
653
|
+
this._sleeping = false;
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Render one frame.
|
|
658
|
+
* Delegates to RenderPipeline for viewport culling + draw.
|
|
659
|
+
* Uses merged dirty rects for partial rendering when possible.
|
|
660
|
+
*/
|
|
661
|
+
GraphCanvas.prototype._renderFrame = function() {
|
|
662
|
+
var mergedRects = [];
|
|
663
|
+
if (!this._dirty && this._dirtyRects.length > 0) {
|
|
664
|
+
mergedRects = this._mergeDirtyRects();
|
|
665
|
+
// If merge decided it's too many → _dirty was set to true
|
|
666
|
+
}
|
|
667
|
+
this._renderer.render(this._ctx, this._dirty, mergedRects);
|
|
668
|
+
this._dirty = false;
|
|
669
|
+
this._dirtyRects = [];
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// ── Event System ──────────────────────────────────────────────────────────
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Register event handler.
|
|
676
|
+
* Supported events: click, doubleClick, hover, dragStart, dragging, dragEnd,
|
|
677
|
+
* select, deselect, viewportChanged, stabilizationDone, dataLoaded
|
|
678
|
+
*/
|
|
679
|
+
GraphCanvas.prototype.on = function(event, callback) {
|
|
680
|
+
if (!this._eventHandlers[event]) this._eventHandlers[event] = [];
|
|
681
|
+
this._eventHandlers[event].push(callback);
|
|
682
|
+
return this;
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
GraphCanvas.prototype.off = function(event, callback) {
|
|
686
|
+
var handlers = this._eventHandlers[event];
|
|
687
|
+
if (!handlers) return this;
|
|
688
|
+
if (callback) {
|
|
689
|
+
for (var i = handlers.length - 1; i >= 0; i--) {
|
|
690
|
+
if (handlers[i] === callback) handlers.splice(i, 1);
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
this._eventHandlers[event] = [];
|
|
694
|
+
}
|
|
695
|
+
return this;
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
GraphCanvas.prototype._emit = function(event, data) {
|
|
699
|
+
var handlers = this._eventHandlers[event];
|
|
700
|
+
if (!handlers) return;
|
|
701
|
+
for (var i = 0; i < handlers.length; i++) {
|
|
702
|
+
try {
|
|
703
|
+
handlers[i](data);
|
|
704
|
+
} catch (e) {
|
|
705
|
+
console.error('GraphCanvas event handler error (' + event + '):', e);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Fit view to show all nodes with padding.
|
|
714
|
+
*/
|
|
715
|
+
GraphCanvas.prototype.fit = function(options) {
|
|
716
|
+
this._viewport.fitToNodes(this._nodes, options);
|
|
717
|
+
this.markDirty();
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Center view on a specific node.
|
|
722
|
+
*/
|
|
723
|
+
GraphCanvas.prototype.focusNode = function(nodeId, options) {
|
|
724
|
+
var node = this._nodeMap[nodeId];
|
|
725
|
+
if (!node) return;
|
|
726
|
+
this._viewport.centerOn(node.x, node.y, options);
|
|
727
|
+
this.markDirty();
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Get current viewport bounds in world coordinates.
|
|
732
|
+
*/
|
|
733
|
+
GraphCanvas.prototype.getViewportBounds = function() {
|
|
734
|
+
return this._viewport.getWorldBounds();
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Get node positions.
|
|
739
|
+
*/
|
|
740
|
+
GraphCanvas.prototype.getPositions = function(nodeIds) {
|
|
741
|
+
var result = {};
|
|
742
|
+
if (nodeIds) {
|
|
743
|
+
for (var i = 0; i < nodeIds.length; i++) {
|
|
744
|
+
var n = this._nodeMap[nodeIds[i]];
|
|
745
|
+
if (n) result[nodeIds[i]] = { x: n.x, y: n.y };
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
748
|
+
for (var i = 0; i < this._nodes.length; i++) {
|
|
749
|
+
var n = this._nodes[i];
|
|
750
|
+
result[n.id] = { x: n.x, y: n.y };
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return result;
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Move a node to new coordinates.
|
|
758
|
+
*/
|
|
759
|
+
GraphCanvas.prototype.moveNode = function(nodeId, x, y) {
|
|
760
|
+
var node = this._nodeMap[nodeId];
|
|
761
|
+
if (!node) return;
|
|
762
|
+
var prevPos = { x: node.x, y: node.y };
|
|
763
|
+
node.x = x;
|
|
764
|
+
node.y = y;
|
|
765
|
+
// Update spatial index (node + connected edges)
|
|
766
|
+
this._spatialIndex.updateNode(node);
|
|
767
|
+
this._spatialIndex.updateEdgesForNode(node.id, this._nodeEdges, this._nodeMap);
|
|
768
|
+
this.markDirtyNode(node, prevPos);
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Get connected edges for a node.
|
|
773
|
+
*/
|
|
774
|
+
GraphCanvas.prototype.getConnectedEdges = function(nodeId) {
|
|
775
|
+
var edges = this._nodeEdges[nodeId] || [];
|
|
776
|
+
var ids = [];
|
|
777
|
+
for (var i = 0; i < edges.length; i++) ids.push(edges[i].id);
|
|
778
|
+
return ids;
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Get connected nodes for a node.
|
|
783
|
+
*/
|
|
784
|
+
GraphCanvas.prototype.getConnectedNodes = function(nodeId) {
|
|
785
|
+
var edges = this._nodeEdges[nodeId] || [];
|
|
786
|
+
var result = [];
|
|
787
|
+
var seen = {};
|
|
788
|
+
for (var i = 0; i < edges.length; i++) {
|
|
789
|
+
var otherId = edges[i].from === nodeId ? edges[i].to : edges[i].from;
|
|
790
|
+
if (!seen[otherId]) {
|
|
791
|
+
result.push(otherId);
|
|
792
|
+
seen[otherId] = true;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return result;
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Get performance metrics.
|
|
800
|
+
*/
|
|
801
|
+
GraphCanvas.prototype.getMetrics = function() {
|
|
802
|
+
return Object.assign({}, this._metrics);
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Force a full redraw.
|
|
807
|
+
*/
|
|
808
|
+
GraphCanvas.prototype.redraw = function() {
|
|
809
|
+
this.markDirty();
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Destroy the engine, clean up all resources.
|
|
814
|
+
*/
|
|
815
|
+
GraphCanvas.prototype.destroy = function() {
|
|
816
|
+
this._stopRenderLoop();
|
|
817
|
+
this._interaction.destroy();
|
|
818
|
+
if (this._resizeObserver) this._resizeObserver.disconnect();
|
|
819
|
+
if (this._layoutEngine) this._layoutEngine.destroy();
|
|
820
|
+
if (this._canvas.parentNode) this._canvas.parentNode.removeChild(this._canvas);
|
|
821
|
+
this._nodes = [];
|
|
822
|
+
this._edges = [];
|
|
823
|
+
this._nodeMap = {};
|
|
824
|
+
this._edgeMap = {};
|
|
825
|
+
this._nodeEdges = {};
|
|
826
|
+
this._eventHandlers = {};
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Convert screen coordinates to world coordinates.
|
|
831
|
+
*/
|
|
832
|
+
GraphCanvas.prototype.screenToWorld = function(sx, sy) {
|
|
833
|
+
return this._viewport.screenToWorld(sx, sy);
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Convert world coordinates to screen coordinates.
|
|
838
|
+
*/
|
|
839
|
+
GraphCanvas.prototype.worldToScreen = function(wx, wy) {
|
|
840
|
+
return this._viewport.worldToScreen(wx, wy);
|
|
841
|
+
};
|
|
842
|
+
`;
|
|
843
|
+
}
|
|
844
|
+
//# sourceMappingURL=core.js.map
|