feat(value_rank): 社交边标签改为昵称并增加恶搞关系文案
- 关系图边标签新增双向@次数明细,并在边中点展示 - 标签从 wxid 改为群昵称显示,提升可读性 - 增加恶搞关系标签规则(如双向奔赴、单向上头、单箭头输出等)
This commit is contained in:
@@ -1247,10 +1247,37 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
if len(selected_nodes) < 2:
|
||||
return None
|
||||
|
||||
selected_edges: List[Tuple[str, str, float, float]] = []
|
||||
# 记录每条无向边对应的双向明细次数:
|
||||
# pair_dir_count[(a,b)] = {"a_to_b": x, "b_to_a": y}
|
||||
# 这样在图上可以直接标注“双向@次数”,避免只看总互动分看不出方向关系。
|
||||
pair_dir_count: Dict[Tuple[str, str], Dict[str, int]] = {}
|
||||
for row in edge_rows:
|
||||
uid_a = str(row.get("from_user_id") or "").strip()
|
||||
uid_b = str(row.get("to_user_id") or "").strip()
|
||||
if not uid_a or not uid_b or uid_a == uid_b:
|
||||
continue
|
||||
a, b = (uid_a, uid_b) if uid_a < uid_b else (uid_b, uid_a)
|
||||
mention_count = int(float(row.get("mention_count_window") or 0.0))
|
||||
bucket = pair_dir_count.setdefault((a, b), {"a_to_b": 0, "b_to_a": 0})
|
||||
if uid_a == a and uid_b == b:
|
||||
bucket["a_to_b"] += mention_count
|
||||
else:
|
||||
bucket["b_to_a"] += mention_count
|
||||
|
||||
selected_edges: List[Tuple[str, str, float, float, int, int]] = []
|
||||
for (a, b), data in edge_map.items():
|
||||
if a in selected_set and b in selected_set:
|
||||
selected_edges.append((a, b, float(data.get("mention_count", 0.0)), float(data.get("score", 0.0))))
|
||||
dir_bucket = pair_dir_count.get((a, b), {"a_to_b": 0, "b_to_a": 0})
|
||||
selected_edges.append(
|
||||
(
|
||||
a,
|
||||
b,
|
||||
float(data.get("mention_count", 0.0)),
|
||||
float(data.get("score", 0.0)),
|
||||
int(dir_bucket.get("a_to_b", 0)),
|
||||
int(dir_bucket.get("b_to_a", 0)),
|
||||
)
|
||||
)
|
||||
if not selected_edges:
|
||||
return None
|
||||
|
||||
@@ -1274,7 +1301,7 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
self,
|
||||
group_id: str,
|
||||
selected_nodes: List[str],
|
||||
selected_edges: List[Tuple[str, str, float, float]],
|
||||
selected_edges: List[Tuple[str, str, float, float, int, int]],
|
||||
partner_map: Dict[str, set],
|
||||
node_score_map: Dict[str, float],
|
||||
) -> str:
|
||||
@@ -1303,7 +1330,11 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
|
||||
cm = ContactManager.get_instance()
|
||||
edge_svg_parts: List[str] = []
|
||||
for a, b, mention_count, score in selected_edges:
|
||||
# 双向@标签层:
|
||||
# 1. 每条边中点标注“a->b / b->a”;
|
||||
# 2. 使用白底半透明标签提升可读性,避免与连线重叠难辨识。
|
||||
edge_label_parts: List[str] = []
|
||||
for a, b, mention_count, score, a_to_b_count, b_to_a_count in selected_edges:
|
||||
ax, ay = pos_map[a]
|
||||
bx, by = pos_map[b]
|
||||
normalized = max(0.12, min(score / max_edge_score, 1.0))
|
||||
@@ -1313,6 +1344,33 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
f'<line x1="{ax:.1f}" y1="{ay:.1f}" x2="{bx:.1f}" y2="{by:.1f}" '
|
||||
f'stroke="rgba(33, 150, 243, {opacity:.3f})" stroke-width="{stroke_width:.2f}" />'
|
||||
)
|
||||
# 通过字典序固定方向,确保同一条边每次渲染文案方向一致。
|
||||
# 标签文案改为“昵称 + 双向次数 + 恶搞关系标签”,不再展示 wxid。
|
||||
mx, my = (ax + bx) / 2.0, (ay + by) / 2.0
|
||||
nick_a = cm.get_group_name(group_id, a) or a
|
||||
nick_b = cm.get_group_name(group_id, b) or b
|
||||
|
||||
total_count = int(a_to_b_count) + int(b_to_a_count)
|
||||
diff_count = abs(int(a_to_b_count) - int(b_to_a_count))
|
||||
if total_count >= 12 and diff_count <= 2:
|
||||
relation_tag = "双向奔赴"
|
||||
elif diff_count >= 8 and max(int(a_to_b_count), int(b_to_a_count)) >= 10:
|
||||
relation_tag = "单向上头"
|
||||
elif total_count <= 2:
|
||||
relation_tag = "点头之交"
|
||||
elif int(a_to_b_count) == 0 or int(b_to_a_count) == 0:
|
||||
relation_tag = "单箭头输出"
|
||||
else:
|
||||
relation_tag = "互相捧场"
|
||||
|
||||
label_text = f"{nick_a}→{nick_b} {a_to_b_count}/{b_to_a_count} | {relation_tag}"
|
||||
safe_label = html.escape(label_text)
|
||||
edge_label_parts.append(
|
||||
f'<g transform="translate({mx:.1f},{my:.1f})">'
|
||||
f'<rect x="-170" y="-12" width="340" height="24" rx="8" ry="8" fill="rgba(255,255,255,0.82)"></rect>'
|
||||
f'<text x="0" y="5" text-anchor="middle" font-size="12" fill="#4E5F7D">{safe_label}</text>'
|
||||
f'</g>'
|
||||
)
|
||||
|
||||
# 节点头像层拆分为 defs + body 两段:
|
||||
# 1. defs 内定义每个节点的裁剪路径,避免头像越界;
|
||||
@@ -1399,6 +1457,7 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
"__GROUP_TITLE__": group_title,
|
||||
"__SUMMARY_TEXT__": summary_text,
|
||||
"__EDGE_SVG__": "".join(edge_svg_parts),
|
||||
"__EDGE_LABELS__": "".join(edge_label_parts),
|
||||
"__NODE_DEFS__": "".join(node_defs_parts),
|
||||
"__NODE_SVG__": "".join(node_svg_parts),
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
__NODE_DEFS__
|
||||
</defs>
|
||||
__EDGE_SVG__
|
||||
__EDGE_LABELS__
|
||||
__NODE_SVG__
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user