Kaynağa Gözat

feat: 完善三区域联动交互

- 滚动条样式优化为6px细条
- 人设匹配区域标签改为"人设匹配"
- 点击人设匹配区域节点/边联动人设树和关系图
- 点击人设树/关系图联动人设匹配区域高亮或变灰
- 点击时人设树自动滚动到对应节点位置
- 移除关系图自动缩放(保留手动缩放)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
yangxiaohui 5 gün önce
ebeveyn
işleme
d821b5bac8
1 değiştirilmiş dosya ile 109 ekleme ve 6 silme
  1. 109 6
      script/data_processing/visualize_match_graph.py

+ 109 - 6
script/data_processing/visualize_match_graph.py

@@ -38,6 +38,21 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
             color: #eee;
             overflow: hidden;
         }}
+        /* 自定义滚动条 */
+        ::-webkit-scrollbar {{
+            width: 6px;
+            height: 6px;
+        }}
+        ::-webkit-scrollbar-track {{
+            background: transparent;
+        }}
+        ::-webkit-scrollbar-thumb {{
+            background: rgba(255,255,255,0.2);
+            border-radius: 3px;
+        }}
+        ::-webkit-scrollbar-thumb:hover {{
+            background: rgba(255,255,255,0.3);
+        }}
         #container {{
             display: flex;
             height: 100vh;
@@ -1330,7 +1345,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
             const layerConfig = [
                 {{ name: "帖子点", layer: 0, color: "rgba(243, 156, 18, 0.08)", stroke: "rgba(243, 156, 18, 0.3)" }},
                 {{ name: "帖子标签", layer: 1, color: "rgba(52, 152, 219, 0.08)", stroke: "rgba(52, 152, 219, 0.3)" }},
-                {{ name: "人设", layer: 2, color: "rgba(155, 89, 182, 0.08)", stroke: "rgba(155, 89, 182, 0.3)" }}
+                {{ name: "人设匹配", layer: 2, color: "rgba(155, 89, 182, 0.08)", stroke: "rgba(155, 89, 182, 0.3)" }}
             ];
 
             // 绘制三层圆形背景
@@ -1659,6 +1674,14 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
                 const linkIndex = links.indexOf(d);
                 highlightEdge(d, linkIndex);
                 showEdgeInfo(d);
+                // 边联动:如果边涉及人设节点,联动人设树和关系图
+                const sourceNode = typeof d.source === "object" ? d.source : nodes.find(n => n.id === d.source);
+                const targetNode = typeof d.target === "object" ? d.target : nodes.find(n => n.id === d.target);
+                if (sourceNode && sourceNode.source === "人设") {{
+                    handleNodeClick(sourceNode.节点ID, sourceNode.节点名称);
+                }} else if (targetNode && targetNode.source === "人设") {{
+                    handleNodeClick(targetNode.节点ID, targetNode.节点名称);
+                }}
             }})
             .on("mouseover", function(event, d) {{
                 d3.select(this.parentNode).select(".link")
@@ -1765,6 +1788,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
                 event.stopPropagation();
                 highlightNode(d);
                 showNodeInfo(d);
+                // 人设节点联动:高亮人设树 + 显示关系图
+                if (d.source === "人设") {{
+                    handleNodeClick(d.节点ID, d.节点名称);
+                }}
             }});
 
             // 更新位置
@@ -2298,16 +2325,64 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
                 showTreeNodeDetail(nodeData, inEdges, outEdges);
             }}
 
-            // 显示关系子图
+            // 显示关系子图(先显示,这样容器高度才正确)
             renderEgoGraph(clickedNodeId, clickedNodeName);
 
-            // 高亮右侧图中对应节点
+            // 滚动人设树到对应节点位置(放在关系图显示后,高度才准确)
+            if (clickedD3Node) {{
+                // 用 setTimeout 确保 DOM 更新完成
+                setTimeout(() => {{
+                    const treeContainer = document.getElementById("tree-container");
+                    const containerHeight = treeContainer.clientHeight;
+                    // D3树布局中 x 是垂直方向(因为是水平树)
+                    const nodeY = clickedD3Node.x + 25;  // 加上treeGroup的transform偏移
+                    const targetScroll = nodeY - containerHeight / 2;
+                    treeContainer.scrollTo({{
+                        top: Math.max(0, targetScroll),
+                        behavior: "smooth"
+                    }});
+                }}, 50);
+            }}
+
+            // 高亮右侧图中对应节点及其相关节点和边
             if (g) {{
-                const graphNode = g.selectAll(".node").filter(n => n.节点ID === clickedNodeId);
-                if (!graphNode.empty()) {{
+                // 收集相关节点ID(点击的节点及其直接连接的节点)
+                const relatedNodeIds = new Set([clickedNodeId]);
+                // 找所有与该节点相连的边
+                g.selectAll(".link-group").each(function(d) {{
+                    const srcId = typeof d.source === "object" ? d.source.id : d.source;
+                    const tgtId = typeof d.target === "object" ? d.target.id : d.target;
+                    if (srcId === clickedNodeId || tgtId === clickedNodeId) {{
+                        relatedNodeIds.add(srcId);
+                        relatedNodeIds.add(tgtId);
+                    }}
+                }});
+
+                // 检查是否有相关节点在图中
+                const hasRelatedNodes = g.selectAll(".node").filter(n => relatedNodeIds.has(n.id)).size() > 0;
+
+                if (hasRelatedNodes) {{
+                    // 高亮相关节点,变灰其他
+                    g.selectAll(".node")
+                        .classed("dimmed", n => !relatedNodeIds.has(n.id))
+                        .classed("highlighted", n => relatedNodeIds.has(n.id));
+                    // 高亮相关边
+                    g.selectAll(".link-group").each(function(d) {{
+                        const srcId = typeof d.source === "object" ? d.source.id : d.source;
+                        const tgtId = typeof d.target === "object" ? d.target.id : d.target;
+                        const isRelated = srcId === clickedNodeId || tgtId === clickedNodeId;
+                        d3.select(this)
+                            .classed("dimmed", !isRelated)
+                            .classed("highlighted", isRelated);
+                        // 显示相关的跨层边
+                        if (isRelated) {{
+                            d3.select(this).style("display", "block");
+                        }}
+                    }});
+                }} else {{
+                    // 点击的节点不在图中,全部变灰
                     g.selectAll(".node").classed("dimmed", true).classed("highlighted", false);
                     g.selectAll(".link-group").classed("dimmed", true).classed("highlighted", false);
-                    graphNode.classed("dimmed", false).classed("highlighted", true);
                 }}
             }}
         }}
@@ -2386,6 +2461,34 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
             `;
             document.getElementById("detailContent").innerHTML = html;
 
+            // 高亮右侧人设匹配区域
+            if (g) {{
+                const relatedNodeIds = new Set([srcNodeId, tgtNodeId]);
+                const hasRelatedNodes = g.selectAll(".node").filter(n => relatedNodeIds.has(n.id)).size() > 0;
+
+                if (hasRelatedNodes) {{
+                    // 高亮相关节点
+                    g.selectAll(".node")
+                        .classed("dimmed", n => !relatedNodeIds.has(n.id))
+                        .classed("highlighted", n => relatedNodeIds.has(n.id));
+                    // 高亮相关边
+                    g.selectAll(".link-group").each(function(d) {{
+                        const sid = typeof d.source === "object" ? d.source.id : d.source;
+                        const tid = typeof d.target === "object" ? d.target.id : d.target;
+                        const isRelated = (sid === srcNodeId && tid === tgtNodeId) ||
+                                          (sid === tgtNodeId && tid === srcNodeId) ||
+                                          relatedNodeIds.has(sid) || relatedNodeIds.has(tid);
+                        d3.select(this)
+                            .classed("dimmed", !isRelated)
+                            .classed("highlighted", isRelated);
+                    }});
+                }} else {{
+                    // 全部变灰
+                    g.selectAll(".node").classed("dimmed", true).classed("highlighted", false);
+                    g.selectAll(".link-group").classed("dimmed", true).classed("highlighted", false);
+                }}
+            }}
+
             // 在关系图中展示这条边和两个节点
             const edgeData = {{
                 边类型: edgeType,