diff --git a/plugins/value_rank/main.py b/plugins/value_rank/main.py
index f0ab034..8e8ed80 100644
--- a/plugins/value_rank/main.py
+++ b/plugins/value_rank/main.py
@@ -1295,6 +1295,73 @@ class ValueRankPlugin(MessagePluginInterface):
if not selected_edges:
return None
+ # 关系图改为“全员 + 主要边”策略:
+ # 1. 用户希望不要限制图上的人数,因此节点仍尽量保留全量;
+ # 2. 真正导致看不清的不是人数,而是边过多、边标签过多;
+ # 3. 这里对展示边做一层稀疏化,只保留每个人的主要关系和全图最强的若干边。
+ full_selected_edges = selected_edges
+ display_edge_cap = max(18, int(len(selected_nodes) * 1.18))
+ display_edge_cap = min(len(full_selected_edges), display_edge_cap)
+ if len(full_selected_edges) > display_edge_cap:
+ core_node_count = max(3, min(6, len(selected_nodes) // 8 + 2))
+ core_nodes = set(selected_nodes[:core_node_count])
+ node_degree_cap: Dict[str, int] = {
+ uid: (6 if uid in core_nodes else 3)
+ for uid in selected_nodes
+ }
+ edge_degree_map: Dict[str, int] = {uid: 0 for uid in selected_nodes}
+ strongest_edge_by_node: Dict[str, Tuple[str, str, float, float, int, int]] = {}
+ sorted_edges = sorted(
+ full_selected_edges,
+ key=lambda item: (
+ -float(item[3]),
+ -float(item[2]),
+ -min(len(partner_map.get(item[0], set())), len(partner_map.get(item[1], set()))),
+ item[0],
+ item[1],
+ ),
+ )
+
+ for edge in sorted_edges:
+ a, b = edge[0], edge[1]
+ if a not in strongest_edge_by_node:
+ strongest_edge_by_node[a] = edge
+ if b not in strongest_edge_by_node:
+ strongest_edge_by_node[b] = edge
+
+ kept_edge_keys = set()
+ pruned_edges: List[Tuple[str, str, float, float, int, int]] = []
+
+ def try_append_edge(edge_item: Tuple[str, str, float, float, int, int], ignore_degree_cap: bool = False) -> bool:
+ edge_a, edge_b = edge_item[0], edge_item[1]
+ edge_key = (edge_a, edge_b)
+ if edge_key in kept_edge_keys:
+ return False
+ if not ignore_degree_cap:
+ if edge_degree_map.get(edge_a, 0) >= node_degree_cap.get(edge_a, 3):
+ return False
+ if edge_degree_map.get(edge_b, 0) >= node_degree_cap.get(edge_b, 3):
+ return False
+ kept_edge_keys.add(edge_key)
+ pruned_edges.append(edge_item)
+ edge_degree_map[edge_a] = edge_degree_map.get(edge_a, 0) + 1
+ edge_degree_map[edge_b] = edge_degree_map.get(edge_b, 0) + 1
+ return True
+
+ # 先保证每个人至少能挂上一条自己最强的关系线,避免外围节点变成“孤点”。
+ for uid in selected_nodes:
+ edge = strongest_edge_by_node.get(uid)
+ if edge:
+ try_append_edge(edge, ignore_degree_cap=True)
+
+ # 再按全图强度补满,兼顾整体关系结构,但限制每个节点的展示边数。
+ for edge in sorted_edges:
+ if len(pruned_edges) >= display_edge_cap:
+ break
+ try_append_edge(edge, ignore_degree_cap=False)
+
+ selected_edges = pruned_edges
+
html_content = self._build_social_graph_html(group_id, selected_nodes, selected_edges, partner_map, node_score_map)
if not html_content:
return None
@@ -1325,8 +1392,8 @@ class ValueRankPlugin(MessagePluginInterface):
import html
- width = 1460
- height = 1040
+ width = 1540
+ height = 1120
cx, cy = width // 2, height // 2 + 24
node_count = len(selected_nodes)
@@ -1338,9 +1405,9 @@ class ValueRankPlugin(MessagePluginInterface):
# 1. 核心节点(连接人数更多)放在内圈,外围节点逐层向外扩散;
# 2. 每圈容量递增,避免所有人都挤在一个圆上;
# 3. 外圈增加轻微抖动和椭圆拉伸,让大图下更分散,不会像等分钟表一样拥挤。
- inner_margin = 150.0
- outer_margin_x = 120.0
- outer_margin_y = 110.0
+ inner_margin = 188.0
+ outer_margin_x = 108.0
+ outer_margin_y = 102.0
max_rx = width * 0.5 - outer_margin_x
max_ry = height * 0.5 - outer_margin_y
ring_capacities: List[int] = []
@@ -1348,9 +1415,9 @@ class ValueRankPlugin(MessagePluginInterface):
ring_index = 0
while placed < node_count:
if ring_index == 0:
- capacity = min(4, node_count)
+ capacity = min(3, node_count)
elif ring_index == 1:
- capacity = 8
+ capacity = 7
else:
capacity = 12 + ring_index * 6
ring_capacities.append(capacity)
@@ -1393,20 +1460,42 @@ class ValueRankPlugin(MessagePluginInterface):
cm = ContactManager.get_instance()
edge_svg_parts: List[str] = []
- # 双向@标签层:
- # 1. 每条边只保留双向次数数字,避免标签信息量过大把图面压得太满;
- # 2. 仍保留白底半透明标签,确保在密集连线里也能看清数值。
+ # 边数字标签只给“最重要的少数边”:
+ # 1. 全边标数字时,图面会被 0/1、1/0 这类噪声淹没;
+ # 2. 这里保留最强的一小部分边标签,其余只看粗细即可;
+ # 3. 这样既能保留方向信息,也不至于牺牲整体结构可读性。
edge_label_parts: List[str] = []
+ max_labeled_edges = max(6, min(12, len(selected_nodes) // 4 + 3))
+ labeled_edge_keys = {
+ (edge[0], edge[1])
+ for edge in sorted(
+ selected_edges,
+ key=lambda item: (-float(item[3]), -float(item[2]), item[0], item[1]),
+ )[:max_labeled_edges]
+ }
+
+ def _shorten_graph_nick(raw_name: str, max_len: int = 8) -> str:
+ text = str(raw_name or "").strip()
+ return text if len(text) <= max_len else f"{text[:max_len]}…"
+
+ def _estimate_label_units(raw_text: str) -> float:
+ units = 0.0
+ for ch in str(raw_text or ""):
+ units += 1.0 if ord(ch) < 128 else 1.75
+ return units
+
for edge_idx, (a, b, mention_count, score, a_to_b_count, b_to_a_count) in enumerate(selected_edges, start=1):
ax, ay = pos_map[a]
bx, by = pos_map[b]
normalized = max(0.12, min(score / max_edge_score, 1.0))
- stroke_width = 1.0 + 7.0 * normalized
- opacity = 0.20 + 0.55 * normalized
+ stroke_width = 1.2 + 6.2 * normalized
+ opacity = 0.14 + 0.42 * normalized
edge_svg_parts.append(
f'