react-html-graph 1.0.0 → 1.2.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.
Files changed (88) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1 -1
  3. package/dist/calculations/index.d.ts +15 -1
  4. package/dist/calculations/index.d.ts.map +1 -1
  5. package/dist/calculations/index.js +696 -0
  6. package/dist/calculations/index.js.map +1 -1
  7. package/dist/calculations/types.d.ts +70 -0
  8. package/dist/calculations/types.d.ts.map +1 -1
  9. package/dist/example/App.css +38 -38
  10. package/dist/example/index.css +47 -47
  11. package/dist/graph/index.d.ts.map +1 -1
  12. package/dist/graph/index.js +218 -16
  13. package/dist/graph/index.js.map +1 -1
  14. package/dist/hooks/connection.d.ts.map +1 -1
  15. package/dist/hooks/connection.js +5 -2
  16. package/dist/hooks/connection.js.map +1 -1
  17. package/dist/hooks/get-viewbox.d.ts +9 -0
  18. package/dist/hooks/get-viewbox.d.ts.map +1 -0
  19. package/dist/hooks/get-viewbox.js +20 -0
  20. package/dist/hooks/get-viewbox.js.map +1 -0
  21. package/dist/layouts/centralizar.d.ts +1 -0
  22. package/dist/layouts/centralizar.d.ts.map +1 -0
  23. package/dist/layouts/centralizar.js +2 -0
  24. package/dist/layouts/centralizar.js.map +1 -0
  25. package/dist/layouts/force-direction-layout.d.ts +9 -0
  26. package/dist/layouts/force-direction-layout.d.ts.map +1 -0
  27. package/dist/layouts/force-direction-layout.js +11 -0
  28. package/dist/layouts/force-direction-layout.js.map +1 -0
  29. package/dist/layouts/index.d.ts +13 -0
  30. package/dist/layouts/index.d.ts.map +1 -0
  31. package/dist/layouts/index.js +17 -0
  32. package/dist/layouts/index.js.map +1 -0
  33. package/dist/layouts/organic-layout.d.ts +9 -0
  34. package/dist/layouts/organic-layout.d.ts.map +1 -0
  35. package/dist/layouts/organic-layout.js +11 -0
  36. package/dist/layouts/organic-layout.js.map +1 -0
  37. package/dist/layouts/radial-layout.d.ts +9 -0
  38. package/dist/layouts/radial-layout.d.ts.map +1 -0
  39. package/dist/layouts/radial-layout.js +11 -0
  40. package/dist/layouts/radial-layout.js.map +1 -0
  41. package/dist/layouts/sequential-layout.d.ts +9 -0
  42. package/dist/layouts/sequential-layout.d.ts.map +1 -0
  43. package/dist/layouts/sequential-layout.js +11 -0
  44. package/dist/layouts/sequential-layout.js.map +1 -0
  45. package/dist/layouts/shared.d.ts +10 -0
  46. package/dist/layouts/shared.d.ts.map +1 -0
  47. package/dist/layouts/shared.js +39 -0
  48. package/dist/layouts/shared.js.map +1 -0
  49. package/dist/layouts/structural-layout.d.ts +9 -0
  50. package/dist/layouts/structural-layout.d.ts.map +1 -0
  51. package/dist/layouts/structural-layout.js +11 -0
  52. package/dist/layouts/structural-layout.js.map +1 -0
  53. package/dist/layouts/tree-layout.d.ts +9 -0
  54. package/dist/layouts/tree-layout.d.ts.map +1 -0
  55. package/dist/layouts/tree-layout.js +11 -0
  56. package/dist/layouts/tree-layout.js.map +1 -0
  57. package/dist/link/base.d.ts +3 -2
  58. package/dist/link/base.d.ts.map +1 -1
  59. package/dist/link/base.js +113 -20
  60. package/dist/link/base.js.map +1 -1
  61. package/dist/link/temp-link.d.ts.map +1 -1
  62. package/dist/link/temp-link.js +6 -4
  63. package/dist/link/temp-link.js.map +1 -1
  64. package/dist/module.d.ts +2 -1
  65. package/dist/module.d.ts.map +1 -1
  66. package/dist/module.js +2 -1
  67. package/dist/module.js.map +1 -1
  68. package/dist/nodes/base.d.ts +1 -1
  69. package/dist/nodes/base.d.ts.map +1 -1
  70. package/dist/nodes/base.js +59 -22
  71. package/dist/nodes/base.js.map +1 -1
  72. package/dist/ports/base.d.ts.map +1 -1
  73. package/dist/style.css +77 -77
  74. package/dist/types.d.ts +153 -0
  75. package/dist/types.d.ts.map +1 -1
  76. package/package.json +77 -73
  77. package/dist/example/App.d.ts +0 -3
  78. package/dist/example/App.d.ts.map +0 -1
  79. package/dist/example/App.js +0 -7
  80. package/dist/example/App.js.map +0 -1
  81. package/dist/example/graph-teste.d.ts +0 -2
  82. package/dist/example/graph-teste.d.ts.map +0 -1
  83. package/dist/example/graph-teste.js +0 -205
  84. package/dist/example/graph-teste.js.map +0 -1
  85. package/dist/example/reportWebVitals.d.ts +0 -4
  86. package/dist/example/reportWebVitals.d.ts.map +0 -1
  87. package/dist/example/reportWebVitals.js +0 -13
  88. package/dist/example/reportWebVitals.js.map +0 -1
@@ -93,6 +93,676 @@ function workerSource() {
93
93
  default: return { x: x + dist, y };
94
94
  }
95
95
  }
96
+ function sortNodeIds(nodes) {
97
+ return nodes.map(node => node.id);
98
+ }
99
+ function buildGraphIndex(nodes, links) {
100
+ const outgoing = new Map();
101
+ const incoming = new Map();
102
+ const undirected = new Map();
103
+ const indegree = new Map();
104
+ const nodeIds = new Set(nodes.map(node => node.id));
105
+ for (const node of nodes) {
106
+ outgoing.set(node.id, []);
107
+ incoming.set(node.id, []);
108
+ undirected.set(node.id, []);
109
+ indegree.set(node.id, 0);
110
+ }
111
+ for (const link of links) {
112
+ if (!nodeIds.has(link.from) || !nodeIds.has(link.to))
113
+ continue;
114
+ outgoing.get(link.from)?.push(link.to);
115
+ incoming.get(link.to)?.push(link.from);
116
+ undirected.get(link.from)?.push(link.to);
117
+ undirected.get(link.to)?.push(link.from);
118
+ indegree.set(link.to, (indegree.get(link.to) ?? 0) + 1);
119
+ }
120
+ return { outgoing, incoming, undirected, indegree };
121
+ }
122
+ function getLayoutBounds(nodes, positions) {
123
+ if (nodes.length === 0 || positions.length === 0) {
124
+ return { left: 0, top: 0, width: 0, height: 0 };
125
+ }
126
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
127
+ let left = Infinity;
128
+ let top = Infinity;
129
+ let right = -Infinity;
130
+ let bottom = -Infinity;
131
+ for (const position of positions) {
132
+ const node = nodeMap.get(position.id);
133
+ if (!node)
134
+ continue;
135
+ left = Math.min(left, position.x);
136
+ top = Math.min(top, position.y);
137
+ right = Math.max(right, position.x + node.width);
138
+ bottom = Math.max(bottom, position.y + node.height);
139
+ }
140
+ if (!Number.isFinite(left) || !Number.isFinite(top) || !Number.isFinite(right) || !Number.isFinite(bottom)) {
141
+ return { left: 0, top: 0, width: 0, height: 0 };
142
+ }
143
+ return {
144
+ left,
145
+ top,
146
+ width: Math.max(0, right - left),
147
+ height: Math.max(0, bottom - top),
148
+ };
149
+ }
150
+ function getConnectedComponents(nodes, links) {
151
+ const { undirected } = buildGraphIndex(nodes, links);
152
+ const visited = new Set();
153
+ const components = [];
154
+ for (const node of nodes) {
155
+ if (visited.has(node.id))
156
+ continue;
157
+ const queue = [node.id];
158
+ const component = [];
159
+ while (queue.length > 0) {
160
+ const current = queue.shift();
161
+ if (visited.has(current))
162
+ continue;
163
+ visited.add(current);
164
+ component.push(current);
165
+ for (const next of undirected.get(current) ?? []) {
166
+ if (!visited.has(next)) {
167
+ queue.push(next);
168
+ }
169
+ }
170
+ }
171
+ components.push(component);
172
+ }
173
+ return components;
174
+ }
175
+ function packDisconnectedComponents(nodes, links, positions, options = {}) {
176
+ const components = getConnectedComponents(nodes, links);
177
+ if (components.length <= 1)
178
+ return positions;
179
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
180
+ const positionMap = new Map(positions.map(position => [position.id, position]));
181
+ const gapX = Math.max(options.gapX ?? 140, 60);
182
+ const gapY = Math.max(options.gapY ?? 100, 60);
183
+ const componentEntries = components.map(ids => {
184
+ const componentPositions = ids
185
+ .map(id => positionMap.get(id))
186
+ .filter((position) => Boolean(position));
187
+ const componentNodes = ids
188
+ .map(id => nodeMap.get(id))
189
+ .filter((node) => Boolean(node));
190
+ const bounds = getLayoutBounds(componentNodes, componentPositions);
191
+ return {
192
+ ids,
193
+ bounds,
194
+ area: Math.max(1, bounds.width * bounds.height),
195
+ };
196
+ }).sort((a, b) => b.area - a.area);
197
+ const totalArea = componentEntries.reduce((sum, entry) => sum + entry.area, 0);
198
+ const maxComponentWidth = componentEntries.reduce((max, entry) => Math.max(max, entry.bounds.width), 0);
199
+ const targetRowWidth = Math.max(maxComponentWidth, Math.sqrt(totalArea) * 1.25);
200
+ let cursorX = 0;
201
+ let cursorY = 0;
202
+ let rowHeight = 0;
203
+ const componentOffsetMap = new Map();
204
+ for (const entry of componentEntries) {
205
+ if (cursorX > 0 && cursorX + entry.bounds.width > targetRowWidth) {
206
+ cursorX = 0;
207
+ cursorY += rowHeight + gapY;
208
+ rowHeight = 0;
209
+ }
210
+ const offset = {
211
+ x: cursorX - entry.bounds.left,
212
+ y: cursorY - entry.bounds.top,
213
+ };
214
+ for (const id of entry.ids) {
215
+ componentOffsetMap.set(id, offset);
216
+ }
217
+ cursorX += entry.bounds.width + gapX;
218
+ rowHeight = Math.max(rowHeight, entry.bounds.height);
219
+ }
220
+ return positions.map(position => {
221
+ const offset = componentOffsetMap.get(position.id);
222
+ if (!offset)
223
+ return position;
224
+ return {
225
+ ...position,
226
+ x: position.x + offset.x,
227
+ y: position.y + offset.y,
228
+ };
229
+ });
230
+ }
231
+ function normalizeLayout(nodes, positions, options = {}) {
232
+ const padding = options.padding ?? 48;
233
+ const bounds = getLayoutBounds(nodes, positions);
234
+ const offsetX = options.center
235
+ ? options.center.x - (bounds.left + bounds.width / 2)
236
+ : padding - bounds.left;
237
+ const offsetY = options.center
238
+ ? options.center.y - (bounds.top + bounds.height / 2)
239
+ : padding - bounds.top;
240
+ const normalized = positions.map(position => ({
241
+ ...position,
242
+ x: position.x + offsetX,
243
+ y: position.y + offsetY,
244
+ }));
245
+ return {
246
+ positions: normalized,
247
+ bounds: getLayoutBounds(nodes, normalized),
248
+ };
249
+ }
250
+ function flipLayout(nodes, positions, axis) {
251
+ const bounds = getLayoutBounds(nodes, positions);
252
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
253
+ return positions.map(position => {
254
+ const node = nodeMap.get(position.id);
255
+ if (!node)
256
+ return position;
257
+ if (axis === "x") {
258
+ return {
259
+ ...position,
260
+ x: bounds.left + bounds.width - (position.x - bounds.left) - node.width,
261
+ };
262
+ }
263
+ return {
264
+ ...position,
265
+ y: bounds.top + bounds.height - (position.y - bounds.top) - node.height,
266
+ };
267
+ });
268
+ }
269
+ function getTopologicalOrder(nodes, links) {
270
+ const { outgoing, indegree } = buildGraphIndex(nodes, links);
271
+ const fallbackOrder = sortNodeIds(nodes);
272
+ const pending = new Map(indegree);
273
+ const queue = fallbackOrder.filter(id => (pending.get(id) ?? 0) === 0);
274
+ const order = [];
275
+ while (queue.length > 0) {
276
+ const id = queue.shift();
277
+ order.push(id);
278
+ for (const next of outgoing.get(id) ?? []) {
279
+ const newDegree = (pending.get(next) ?? 0) - 1;
280
+ pending.set(next, newDegree);
281
+ if (newDegree === 0) {
282
+ queue.push(next);
283
+ queue.sort((a, b) => fallbackOrder.indexOf(a) - fallbackOrder.indexOf(b));
284
+ }
285
+ }
286
+ }
287
+ for (const id of fallbackOrder) {
288
+ if (!order.includes(id))
289
+ order.push(id);
290
+ }
291
+ return order;
292
+ }
293
+ function getHierarchicalDepths(nodes, links, treeMode) {
294
+ const { outgoing, indegree } = buildGraphIndex(nodes, links);
295
+ const order = getTopologicalOrder(nodes, links);
296
+ const depths = new Map();
297
+ const fallbackOrder = sortNodeIds(nodes);
298
+ for (const id of fallbackOrder) {
299
+ depths.set(id, 0);
300
+ }
301
+ if (treeMode) {
302
+ const roots = fallbackOrder.filter(id => (indegree.get(id) ?? 0) === 0);
303
+ const queue = [...roots, ...fallbackOrder.filter(id => !roots.includes(id))];
304
+ const visited = new Set();
305
+ while (queue.length > 0) {
306
+ const current = queue.shift();
307
+ if (visited.has(current))
308
+ continue;
309
+ visited.add(current);
310
+ const baseDepth = depths.get(current) ?? 0;
311
+ for (const next of outgoing.get(current) ?? []) {
312
+ if ((depths.get(next) ?? 0) < baseDepth + 1) {
313
+ depths.set(next, baseDepth + 1);
314
+ }
315
+ queue.push(next);
316
+ }
317
+ }
318
+ return depths;
319
+ }
320
+ for (const id of order) {
321
+ const depth = depths.get(id) ?? 0;
322
+ for (const next of outgoing.get(id) ?? []) {
323
+ if ((depths.get(next) ?? 0) < depth + 1) {
324
+ depths.set(next, depth + 1);
325
+ }
326
+ }
327
+ }
328
+ return depths;
329
+ }
330
+ function getViewportScales(options = {}) {
331
+ const viewportWidth = options.viewport?.width ?? 0;
332
+ const viewportHeight = options.viewport?.height ?? 0;
333
+ if (viewportWidth <= 0 || viewportHeight <= 0) {
334
+ return { scaleX: 1, scaleY: 1 };
335
+ }
336
+ if (viewportWidth >= viewportHeight) {
337
+ return {
338
+ scaleX: Math.min(viewportWidth / viewportHeight, 1.8),
339
+ scaleY: 1,
340
+ };
341
+ }
342
+ return {
343
+ scaleX: 1,
344
+ scaleY: Math.min(viewportHeight / viewportWidth, 1.8),
345
+ };
346
+ }
347
+ function getPreferredDirection(options = {}, fallback) {
348
+ if (options.direction)
349
+ return options.direction;
350
+ const viewportWidth = options.viewport?.width ?? 0;
351
+ const viewportHeight = options.viewport?.height ?? 0;
352
+ if (viewportWidth <= 0 || viewportHeight <= 0)
353
+ return fallback;
354
+ return viewportWidth >= viewportHeight ? "LR" : "TB";
355
+ }
356
+ function layoutSequential(nodes, links, options = {}) {
357
+ const gapX = options.gapX ?? 80;
358
+ const gapY = options.gapY ?? 80;
359
+ const order = getTopologicalOrder(nodes, links);
360
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
361
+ const viewportWidth = options.viewport?.width ?? 0;
362
+ const viewportHeight = options.viewport?.height ?? 0;
363
+ const aspect = viewportWidth > 0 && viewportHeight > 0 ? viewportWidth / viewportHeight : 1;
364
+ const defaultColumns = Math.ceil(Math.sqrt(Math.max(1, nodes.length)) * Math.sqrt(Math.max(aspect, 0.5)));
365
+ const columns = Math.max(1, Math.floor(options.columns ?? defaultColumns));
366
+ let x = 0;
367
+ let y = 0;
368
+ let rowHeight = 0;
369
+ let column = 0;
370
+ const positions = [];
371
+ for (const id of order) {
372
+ const node = nodeMap.get(id);
373
+ if (!node)
374
+ continue;
375
+ positions.push({ id, x, y, z: node.z });
376
+ rowHeight = Math.max(rowHeight, node.height);
377
+ column += 1;
378
+ if (column >= columns) {
379
+ column = 0;
380
+ x = 0;
381
+ y += rowHeight + gapY;
382
+ rowHeight = 0;
383
+ }
384
+ else {
385
+ x += node.width + gapX;
386
+ }
387
+ }
388
+ return normalizeLayout(nodes, packDisconnectedComponents(nodes, links, positions, options), options);
389
+ }
390
+ function layoutHierarchical(nodes, links, options = {}, treeMode = false) {
391
+ const gapX = options.gapX ?? 100;
392
+ const gapY = options.gapY ?? 80;
393
+ const direction = getPreferredDirection(options, treeMode ? "TB" : "LR");
394
+ const depths = getHierarchicalDepths(nodes, links, treeMode);
395
+ const order = sortNodeIds(nodes);
396
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
397
+ const layers = new Map();
398
+ for (const id of order) {
399
+ const depth = depths.get(id) ?? 0;
400
+ const group = layers.get(depth) ?? [];
401
+ group.push(id);
402
+ layers.set(depth, group);
403
+ }
404
+ const layerKeys = [...layers.keys()].sort((a, b) => a - b);
405
+ const positions = [];
406
+ let primaryCursor = 0;
407
+ for (const layerKey of layerKeys) {
408
+ const ids = layers.get(layerKey) ?? [];
409
+ let secondaryCursor = 0;
410
+ let primarySize = 0;
411
+ for (const id of ids) {
412
+ const node = nodeMap.get(id);
413
+ if (!node)
414
+ continue;
415
+ if (direction === "LR" || direction === "RL") {
416
+ positions.push({ id, x: primaryCursor, y: secondaryCursor, z: node.z });
417
+ secondaryCursor += node.height + gapY;
418
+ primarySize = Math.max(primarySize, node.width);
419
+ }
420
+ else {
421
+ positions.push({ id, x: secondaryCursor, y: primaryCursor, z: node.z });
422
+ secondaryCursor += node.width + gapX;
423
+ primarySize = Math.max(primarySize, node.height);
424
+ }
425
+ }
426
+ primaryCursor += primarySize + (direction === "LR" || direction === "RL" ? gapX : gapY);
427
+ }
428
+ let adjusted = positions;
429
+ if (direction === "RL")
430
+ adjusted = flipLayout(nodes, adjusted, "x");
431
+ if (direction === "BT")
432
+ adjusted = flipLayout(nodes, adjusted, "y");
433
+ return normalizeLayout(nodes, packDisconnectedComponents(nodes, links, adjusted, options), options);
434
+ }
435
+ function layoutRadial(nodes, links, options = {}) {
436
+ const { outgoing, incoming, indegree, undirected } = buildGraphIndex(nodes, links);
437
+ const order = sortNodeIds(nodes);
438
+ const orderIndex = new Map(order.map((id, index) => [id, index]));
439
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
440
+ const { scaleX, scaleY } = getViewportScales(options);
441
+ const radiusStep = options.radiusStep ?? Math.max((options.gapX ?? 96) * 0.8, (options.gapY ?? 80) * 0.8, 72);
442
+ const angularGap = Math.max((options.gapX ?? 96) * 0.2, 16);
443
+ const positions = [];
444
+ const adjacency = new Map();
445
+ for (const node of nodes) {
446
+ const combined = [
447
+ ...(outgoing.get(node.id) ?? []),
448
+ ...(incoming.get(node.id) ?? []),
449
+ ...(undirected.get(node.id) ?? []),
450
+ ];
451
+ adjacency.set(node.id, [...new Set(combined)].sort((a, b) => (orderIndex.get(a) ?? 0) - (orderIndex.get(b) ?? 0)));
452
+ }
453
+ const components = getConnectedComponents(nodes, links);
454
+ for (const component of components) {
455
+ const componentSet = new Set(component);
456
+ const root = component.find(id => (indegree.get(id) ?? 0) === 0) ?? component[0];
457
+ const children = new Map();
458
+ const depth = new Map();
459
+ const visited = new Set();
460
+ const buildTree = (nodeId, parentId, currentDepth = 0) => {
461
+ visited.add(nodeId);
462
+ depth.set(nodeId, currentDepth);
463
+ const childIds = (adjacency.get(nodeId) ?? [])
464
+ .filter(next => componentSet.has(next) && next !== parentId && !visited.has(next));
465
+ children.set(nodeId, childIds);
466
+ for (const childId of childIds) {
467
+ buildTree(childId, nodeId, currentDepth + 1);
468
+ }
469
+ };
470
+ buildTree(root);
471
+ const subtreeWeight = new Map();
472
+ const calculateWeight = (nodeId) => {
473
+ const childIds = children.get(nodeId) ?? [];
474
+ if (childIds.length === 0) {
475
+ subtreeWeight.set(nodeId, 1);
476
+ return 1;
477
+ }
478
+ const weight = childIds.reduce((sum, childId) => sum + calculateWeight(childId), 0);
479
+ subtreeWeight.set(nodeId, Math.max(1, weight));
480
+ return Math.max(1, weight);
481
+ };
482
+ calculateWeight(root);
483
+ const angle = new Map();
484
+ const assignAngles = (nodeId, startAngle, endAngle) => {
485
+ angle.set(nodeId, (startAngle + endAngle) / 2);
486
+ const childIds = children.get(nodeId) ?? [];
487
+ if (childIds.length === 0)
488
+ return;
489
+ const totalWeight = childIds.reduce((sum, childId) => sum + (subtreeWeight.get(childId) ?? 1), 0);
490
+ let cursor = startAngle;
491
+ for (const childId of childIds) {
492
+ const span = ((endAngle - startAngle) * (subtreeWeight.get(childId) ?? 1)) / Math.max(totalWeight, 1);
493
+ assignAngles(childId, cursor, cursor + span);
494
+ cursor += span;
495
+ }
496
+ };
497
+ assignAngles(root, -Math.PI / 2, (Math.PI * 3) / 2);
498
+ const layers = new Map();
499
+ for (const nodeId of component) {
500
+ const layer = depth.get(nodeId) ?? 0;
501
+ const group = layers.get(layer) ?? [];
502
+ group.push(nodeId);
503
+ layers.set(layer, group);
504
+ }
505
+ const layerKeys = [...layers.keys()].sort((a, b) => a - b);
506
+ const radiusByLayer = new Map();
507
+ let outerRadius = 0;
508
+ for (const layer of layerKeys) {
509
+ const layerIds = layers.get(layer) ?? [];
510
+ if (layer === 0) {
511
+ radiusByLayer.set(layer, 0);
512
+ const rootNode = nodeMap.get(root);
513
+ outerRadius = rootNode
514
+ ? Math.sqrt(rootNode.width * rootNode.width + rootNode.height * rootNode.height) / 2
515
+ : 0;
516
+ continue;
517
+ }
518
+ const sortedLayerIds = [...layerIds].sort((a, b) => (angle.get(a) ?? 0) - (angle.get(b) ?? 0));
519
+ let layerRadius = outerRadius + radiusStep;
520
+ if (sortedLayerIds.length > 1) {
521
+ for (let index = 0; index < sortedLayerIds.length; index++) {
522
+ const currentId = sortedLayerIds[index];
523
+ const nextId = sortedLayerIds[(index + 1) % sortedLayerIds.length];
524
+ const currentNode = nodeMap.get(currentId);
525
+ const nextNode = nodeMap.get(nextId);
526
+ if (!currentNode || !nextNode)
527
+ continue;
528
+ const currentAngle = angle.get(currentId) ?? 0;
529
+ const nextAngle = angle.get(nextId) ?? 0;
530
+ const angleDeltaRaw = nextAngle > currentAngle
531
+ ? nextAngle - currentAngle
532
+ : nextAngle + Math.PI * 2 - currentAngle;
533
+ const angleDelta = Math.max(angleDeltaRaw, 0.2);
534
+ const requiredArc = (Math.sqrt(currentNode.width * currentNode.width + currentNode.height * currentNode.height) / 2 +
535
+ Math.sqrt(nextNode.width * nextNode.width + nextNode.height * nextNode.height) / 2 +
536
+ angularGap);
537
+ layerRadius = Math.max(layerRadius, requiredArc / angleDelta);
538
+ }
539
+ }
540
+ radiusByLayer.set(layer, layerRadius);
541
+ const maxHalfDiagonal = layerIds.reduce((max, nodeId) => {
542
+ const node = nodeMap.get(nodeId);
543
+ if (!node)
544
+ return max;
545
+ return Math.max(max, Math.sqrt(node.width * node.width + node.height * node.height) / 2);
546
+ }, 0);
547
+ outerRadius = layerRadius + maxHalfDiagonal;
548
+ }
549
+ for (const nodeId of component) {
550
+ const node = nodeMap.get(nodeId);
551
+ if (!node)
552
+ continue;
553
+ const currentDepth = depth.get(nodeId) ?? 0;
554
+ const radius = radiusByLayer.get(currentDepth) ?? 0;
555
+ const theta = angle.get(nodeId) ?? 0;
556
+ positions.push({
557
+ id: nodeId,
558
+ x: Math.cos(theta) * radius * scaleX - node.width / 2,
559
+ y: Math.sin(theta) * radius * scaleY - node.height / 2,
560
+ z: node.z,
561
+ });
562
+ }
563
+ }
564
+ return normalizeLayout(nodes, packDisconnectedComponents(nodes, links, positions, options), options);
565
+ }
566
+ function separateOverlappingNodes(nodes, centers, gapX, gapY) {
567
+ for (let iteration = 0; iteration < 12; iteration++) {
568
+ let moved = false;
569
+ for (let i = 0; i < nodes.length; i++) {
570
+ for (let j = i + 1; j < nodes.length; j++) {
571
+ const minDx = (nodes[i].width + nodes[j].width) / 2 + gapX * 0.35;
572
+ const minDy = (nodes[i].height + nodes[j].height) / 2 + gapY * 0.35;
573
+ const dx = centers[j].x - centers[i].x;
574
+ const dy = centers[j].y - centers[i].y;
575
+ const overlapX = minDx - Math.abs(dx);
576
+ const overlapY = minDy - Math.abs(dy);
577
+ if (overlapX <= 0 || overlapY <= 0)
578
+ continue;
579
+ moved = true;
580
+ if (overlapX < overlapY) {
581
+ const shift = overlapX / 2 + 1;
582
+ const sign = dx >= 0 ? 1 : -1;
583
+ centers[i].x -= sign * shift;
584
+ centers[j].x += sign * shift;
585
+ }
586
+ else {
587
+ const shift = overlapY / 2 + 1;
588
+ const sign = dy >= 0 ? 1 : -1;
589
+ centers[i].y -= sign * shift;
590
+ centers[j].y += sign * shift;
591
+ }
592
+ }
593
+ }
594
+ if (!moved) {
595
+ return;
596
+ }
597
+ }
598
+ }
599
+ function runForceLayout(nodes, links, options = {}, directedBias = 0, gravity = 0.015, seedPositions) {
600
+ const gapX = options.gapX ?? 140;
601
+ const gapY = options.gapY ?? 100;
602
+ const iterations = Math.max(1, Math.floor(options.iterations ?? 220));
603
+ const nodeMap = new Map(nodes.map((node, index) => [node.id, { node, index }]));
604
+ const nodeRadii = nodes.map(node => Math.sqrt(node.width * node.width + node.height * node.height) / 2);
605
+ const seedPositionMap = new Map((seedPositions ?? []).map(position => [position.id, position]));
606
+ const rawPositions = nodes.map(node => {
607
+ const seed = seedPositionMap.get(node.id);
608
+ return {
609
+ id: node.id,
610
+ x: seed?.x ?? node.x,
611
+ y: seed?.y ?? node.y,
612
+ z: seed?.z ?? node.z,
613
+ };
614
+ });
615
+ const rawBounds = getLayoutBounds(nodes, rawPositions);
616
+ const rawCenterX = rawBounds.left + rawBounds.width / 2;
617
+ const rawCenterY = rawBounds.top + rawBounds.height / 2;
618
+ const { scaleX, scaleY } = getViewportScales(options);
619
+ const desiredSpanX = Math.max(gapX, gapY) * Math.max(2, Math.sqrt(Math.max(1, nodes.length))) * scaleX;
620
+ const desiredSpanY = Math.max(gapX, gapY) * Math.max(2, Math.sqrt(Math.max(1, nodes.length))) * scaleY;
621
+ const currentSpanX = Math.max(rawBounds.width, 1);
622
+ const currentSpanY = Math.max(rawBounds.height, 1);
623
+ const compactScaleX = Math.min(1, desiredSpanX / currentSpanX);
624
+ const compactScaleY = Math.min(1, desiredSpanY / currentSpanY);
625
+ const useCircularSeed = rawBounds.width < 4 && rawBounds.height < 4;
626
+ const centers = nodes.map((node, index) => {
627
+ if (useCircularSeed) {
628
+ const seedRadius = Math.max(gapX, gapY) * 0.8;
629
+ const angle = (Math.PI * 2 * index) / Math.max(1, nodes.length);
630
+ return {
631
+ x: Math.cos(angle) * seedRadius * scaleX,
632
+ y: Math.sin(angle) * seedRadius * scaleY,
633
+ };
634
+ }
635
+ return {
636
+ x: (node.x + node.width / 2 - rawCenterX) * compactScaleX,
637
+ y: (node.y + node.height / 2 - rawCenterY) * compactScaleY,
638
+ };
639
+ });
640
+ const totalNodeArea = nodes.reduce((sum, node) => sum + node.width * node.height, 0);
641
+ const area = Math.max(totalNodeArea + gapX * gapY * Math.max(1, nodes.length) * 0.35, 1);
642
+ const k = Math.sqrt(area / Math.max(1, nodes.length));
643
+ let temperature = Math.max(gapX, gapY, 60);
644
+ for (let iteration = 0; iteration < iterations; iteration++) {
645
+ const displacement = nodes.map(() => ({ x: 0, y: 0 }));
646
+ for (let i = 0; i < nodes.length; i++) {
647
+ for (let j = i + 1; j < nodes.length; j++) {
648
+ let dx = centers[i].x - centers[j].x;
649
+ let dy = centers[i].y - centers[j].y;
650
+ let distance = Math.sqrt(dx * dx + dy * dy);
651
+ if (distance < 0.001) {
652
+ dx = (Math.random() - 0.5) * 0.01;
653
+ dy = (Math.random() - 0.5) * 0.01;
654
+ distance = Math.sqrt(dx * dx + dy * dy);
655
+ }
656
+ const minimumDistance = nodeRadii[i] + nodeRadii[j] + Math.max(gapX, gapY) * 0.18;
657
+ const safeDistance = Math.max(distance - nodeRadii[i] - nodeRadii[j], 1);
658
+ const force = (k * k) / (safeDistance * 1.5);
659
+ const fx = (dx / distance) * force;
660
+ const fy = (dy / distance) * force;
661
+ displacement[i].x += fx;
662
+ displacement[i].y += fy;
663
+ displacement[j].x -= fx;
664
+ displacement[j].y -= fy;
665
+ if (distance < minimumDistance) {
666
+ const collisionForce = (minimumDistance - distance) * 1.1;
667
+ displacement[i].x += (dx / distance) * collisionForce;
668
+ displacement[i].y += (dy / distance) * collisionForce;
669
+ displacement[j].x -= (dx / distance) * collisionForce;
670
+ displacement[j].y -= (dy / distance) * collisionForce;
671
+ }
672
+ }
673
+ }
674
+ for (const link of links) {
675
+ const fromEntry = nodeMap.get(link.from);
676
+ const toEntry = nodeMap.get(link.to);
677
+ if (!fromEntry || !toEntry)
678
+ continue;
679
+ const i = fromEntry.index;
680
+ const j = toEntry.index;
681
+ let dx = centers[i].x - centers[j].x;
682
+ let dy = centers[i].y - centers[j].y;
683
+ let distance = Math.sqrt(dx * dx + dy * dy) || 1;
684
+ const springLength = nodeRadii[i] + nodeRadii[j] + Math.max(gapX, gapY) * 0.28;
685
+ const force = (distance - springLength) * 0.2;
686
+ const fx = (dx / distance) * force;
687
+ const fy = (dy / distance) * force;
688
+ displacement[i].x -= fx;
689
+ displacement[i].y -= fy;
690
+ displacement[j].x += fx;
691
+ displacement[j].y += fy;
692
+ if (directedBias !== 0) {
693
+ displacement[i].x -= directedBias;
694
+ displacement[j].x += directedBias;
695
+ }
696
+ }
697
+ for (let i = 0; i < nodes.length; i++) {
698
+ displacement[i].x -= centers[i].x * gravity;
699
+ displacement[i].y -= centers[i].y * gravity;
700
+ const length = Math.sqrt(displacement[i].x * displacement[i].x + displacement[i].y * displacement[i].y) || 1;
701
+ const limited = Math.min(length, temperature);
702
+ centers[i].x += (displacement[i].x / length) * limited;
703
+ centers[i].y += (displacement[i].y / length) * limited;
704
+ }
705
+ temperature *= 0.96;
706
+ }
707
+ separateOverlappingNodes(nodes, centers, gapX, gapY);
708
+ const positions = nodes.map((node, index) => ({
709
+ id: node.id,
710
+ x: centers[index].x - node.width / 2,
711
+ y: centers[index].y - node.height / 2,
712
+ z: node.z,
713
+ }));
714
+ return normalizeLayout(nodes, packDisconnectedComponents(nodes, links, positions, options), options);
715
+ }
716
+ function calculateLayoutResult(data) {
717
+ const nodes = (data.nodes ?? []);
718
+ const links = (data.links ?? []);
719
+ const options = data.options ?? {};
720
+ switch (data.algorithm) {
721
+ case "sequential":
722
+ return layoutSequential(nodes, links, options);
723
+ case "radial":
724
+ return layoutRadial(nodes, links, options);
725
+ case "tree":
726
+ return layoutHierarchical(nodes, links, options, true);
727
+ case "structural":
728
+ return layoutHierarchical(nodes, links, options, false);
729
+ case "organic": {
730
+ const radialSeed = layoutRadial(nodes, links, options);
731
+ return runForceLayout(nodes, links, {
732
+ ...options,
733
+ iterations: Math.min(options.iterations ?? 150, 130),
734
+ }, 0, 0.02, radialSeed.positions);
735
+ }
736
+ case "force-direction": {
737
+ const structuralSeed = layoutHierarchical(nodes, links, options, false);
738
+ return runForceLayout(nodes, links, {
739
+ ...options,
740
+ iterations: Math.min(options.iterations ?? 140, 120),
741
+ }, Math.max((options.gapX ?? 140) * 0.03, 1), 0.018, structuralSeed.positions);
742
+ }
743
+ default:
744
+ return runForceLayout(nodes, links, options, Math.max((options.gapX ?? 140) * 0.03, 1), 0.018);
745
+ }
746
+ }
747
+ function calculateFitViewResult(data) {
748
+ const nodes = (data.nodes ?? []);
749
+ const bounds = getLayoutBounds(nodes, nodes.map(node => ({ id: node.id, x: node.x, y: node.y, z: node.z })));
750
+ if (nodes.length === 0 || data.viewportWidth <= 0 || data.viewportHeight <= 0) {
751
+ return { x: 0, y: 0, zoom: 1, bounds };
752
+ }
753
+ const padding = Math.max(0, data.padding ?? 32);
754
+ const safeWidth = Math.max(bounds.width, 1);
755
+ const safeHeight = Math.max(bounds.height, 1);
756
+ const availableWidth = Math.max(1, data.viewportWidth - padding * 2);
757
+ const availableHeight = Math.max(1, data.viewportHeight - padding * 2);
758
+ const zoomX = availableWidth / safeWidth;
759
+ const zoomY = availableHeight / safeHeight;
760
+ const unclampedZoom = Math.min(zoomX, zoomY);
761
+ const zoom = Math.min(data.maxZoom ?? 2, Math.max(data.minZoom ?? 0.1, unclampedZoom));
762
+ const x = bounds.left + bounds.width / 2 - data.viewportWidth / (2 * zoom);
763
+ const y = bounds.top + bounds.height / 2 - data.viewportHeight / (2 * zoom);
764
+ return { x, y, zoom, bounds };
765
+ }
96
766
  // eslint-disable-next-line no-restricted-globals
97
767
  self.onmessage = function (e) {
98
768
  const { type, id, ...data } = e.data;
@@ -170,6 +840,14 @@ function workerSource() {
170
840
  });
171
841
  post({ type: "calculateLabels", id, positions });
172
842
  }
843
+ else if (type === "calculateLayout") {
844
+ const result = calculateLayoutResult(data);
845
+ post({ type: "calculateLayout", id, ...result });
846
+ }
847
+ else if (type === "calculateFitView") {
848
+ const result = calculateFitViewResult(data);
849
+ post({ type: "calculateFitView", id, ...result });
850
+ }
173
851
  };
174
852
  }
175
853
  let pool = null;
@@ -244,4 +922,22 @@ export function calculatePath(input) {
244
922
  export function calculateLabels(input) {
245
923
  return request({ type: "calculateLabels", ...input });
246
924
  }
925
+ /**
926
+ * Calcula um layout completo para os nós do grafo em um worker do pool.
927
+ *
928
+ * @param input Dados de entrada do algoritmo de layout
929
+ * @returns Promise com as novas posições e bounds resultantes
930
+ */
931
+ export function calculateLayout(input) {
932
+ return request({ type: "calculateLayout", ...input });
933
+ }
934
+ /**
935
+ * Calcula o viewbox ideal para enquadrar todos os nós em uma viewport.
936
+ *
937
+ * @param input Dados de entrada para fit da viewport
938
+ * @returns Promise com x, y e zoom calculados
939
+ */
940
+ export function calculateFitView(input) {
941
+ return request({ type: "calculateFitView", ...input });
942
+ }
247
943
  //# sourceMappingURL=index.js.map