OverviewFilesCommitsIssuesDocsReleasesPeers

godot-keet commits / 2e2a99d

Fix graph: deterministic layout, grid fallback, group bar signal wiring
- Seed RNG from room ID hash → same data = same layout on refresh - Grid layout when 0 edges (force-directed pointless without attraction) - Brighter unclustered node color (0.6/0.65/0.7 vs 0.4/0.4/0.4) - compute() now emits clusters_loaded so group_bar gets clusters - graph_panel skips redundant render from double signal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2e2a99d7a0db8346495fd4903bef67928cadbc98
by plankatron <fleeky@gmail.com> · 2026-02-23T02:52:27.000Z

Changed files (3) +49 -9

addons/keet_social/keet_graph.gd +4 -0
……@@ -24,6 +24,10 @@ func compute(force: bool = false, algo: Dictionary = {}, callback: Callable = Ca
2424 return
2525 var r: Dictionary = result if result is Dictionary else {}
2626 graph_computed.emit(r)
27 # Also emit clusters_loaded so group_bar gets clusters from compute()
28 var clusters: Array = r.get("clusters", []) if r is Dictionary else []
29 if clusters is Array:
30 clusters_loaded.emit(clusters)
2731 if callback.is_valid():
2832 callback.call(r)
2933 )
scripts/graph_canvas.gd +39 -5
……@@ -38,7 +38,7 @@ const CLUSTER_COLORS: Array[Color] = [
3838 Color("#e67e22"), Color("#5865f2"), Color("#0dbd8b"), Color("#e74c3c"),
3939 Color("#f1c40f"), Color("#9b59b6"), Color("#1abc9c"), Color("#3498db")
4040]
41const COLOR_UNCLUSTERED := Color(0.4, 0.4, 0.4)
41const COLOR_UNCLUSTERED := Color(0.6, 0.65, 0.7)
4242const COLOR_EDGE := Color(1.0, 1.0, 1.0)
4343const COLOR_BG := Color(0.08, 0.08, 0.1)
4444
……@@ -70,14 +70,48 @@ func set_graph_data(nodes: Array, edges: Array, clusters: Array, cluster_labels:
7070 var nid: String = str(node.get("id", ""))
7171 _title_map[nid] = str(node.get("title", nid.left(8)))
7272
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()
7789 _rescale_to_fit()
7890 queue_redraw()
7991
8092
93func _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
81115func _init_cluster_positions(clusters: Array) -> void:
82116 ## Place nodes from the same cluster near each other.
83117 ## Unclustered nodes start near center with random offset.
scripts/graph_panel.gd +6 -4
……@@ -88,10 +88,12 @@ func _on_graph_computed(result: Dictionary) -> void:
8888 _try_render()
8989
9090
91func _on_clusters_loaded(clusters: Array) -> void:
92 _clusters = clusters
93 _data_ready |= 1
94 _try_render()
91func _on_clusters_loaded(_clusters_arr: Array) -> void:
92 # clusters already set by _on_graph_computed — only handle standalone get_clusters() calls
93 if _data_ready & 1 == 0:
94 _clusters = _clusters_arr
95 _data_ready |= 1
96 _try_render()
9597
9698
9799func _on_edges_loaded(edges: Array) -> void: