| … | … | @@ -38,7 +38,7 @@ const CLUSTER_COLORS: Array[Color] = [ |
| 38 | 38 | Color("#e67e22"), Color("#5865f2"), Color("#0dbd8b"), Color("#e74c3c"), |
| 39 | 39 | Color("#f1c40f"), Color("#9b59b6"), Color("#1abc9c"), Color("#3498db") |
| 40 | 40 | ] |
| 41 | | const COLOR_UNCLUSTERED := Color(0.4, 0.4, 0.4) |
| 41 | const COLOR_UNCLUSTERED := Color(0.6, 0.65, 0.7) |
| 42 | 42 | const COLOR_EDGE := Color(1.0, 1.0, 1.0) |
| 43 | 43 | const COLOR_BG := Color(0.08, 0.08, 0.1) |
| 44 | 44 | |
| … | … | @@ -70,14 +70,48 @@ func set_graph_data(nodes: Array, edges: Array, clusters: Array, cluster_labels: |
| 70 | 70 | var nid: String = str(node.get("id", "")) |
| 71 | 71 | _title_map[nid] = str(node.get("title", nid.left(8))) |
| 72 | 72 | |
| 73 | | # Compute layout synchronously — no animation |
| 74 | | _init_cluster_positions(clusters) |
| 75 | | for i: int in LAYOUT_ITERATIONS: |
| 76 | | _layout_step() |
| 73 | # Deterministic seed from room IDs so same data = same layout |
| 74 | var ids: Array = [] |
| 75 | for node: Variant in _nodes: |
| 76 | if node is Dictionary: |
| 77 | ids.append(str(node.get("id", ""))) |
| 78 | ids.sort() |
| 79 | seed("".join(ids).hash()) |
| 80 | |
| 81 | if _edges.is_empty(): |
| 82 | # No edges → grid layout (force-directed is pointless without attraction) |
| 83 | _grid_layout() |
| 84 | else: |
| 85 | # Force-directed layout |
| 86 | _init_cluster_positions(clusters) |
| 87 | for i: int in LAYOUT_ITERATIONS: |
| 88 | _layout_step() |
| 77 | 89 | _rescale_to_fit() |
| 78 | 90 | queue_redraw() |
| 79 | 91 | |
| 80 | 92 | |
| 93 | func _grid_layout() -> void: |
| 94 | ## Deterministic grid layout — used when no edges exist. |
| 95 | if _nodes.is_empty(): |
| 96 | return |
| 97 | _positions.clear() |
| 98 | var rect: Vector2 = size if size.x > 0 else Vector2(600, 400) |
| 99 | var n: int = _nodes.size() |
| 100 | var cols: int = ceili(sqrt(float(n))) |
| 101 | var rows: int = ceili(float(n) / float(cols)) |
| 102 | var cell_w: float = rect.x / float(cols + 1) |
| 103 | var cell_h: float = rect.y / float(rows + 1) |
| 104 | |
| 105 | for i: int in n: |
| 106 | var node: Variant = _nodes[i] |
| 107 | if not node is Dictionary: |
| 108 | continue |
| 109 | var nid: String = str(node.get("id", "")) |
| 110 | var col: int = i % cols |
| 111 | var row: int = i / cols |
| 112 | _positions[nid] = Vector2((col + 1) * cell_w, (row + 1) * cell_h) |
| 113 | |
| 114 | |
| 81 | 115 | func _init_cluster_positions(clusters: Array) -> void: |
| 82 | 116 | ## Place nodes from the same cluster near each other. |
| 83 | 117 | ## Unclustered nodes start near center with random offset. |