|
|
@@ -77,6 +77,29 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
flex: 1;
|
|
|
overflow: hidden;
|
|
|
}}
|
|
|
+ #left-panel {{
|
|
|
+ width: 420px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background: #1a1a2e;
|
|
|
+ border-right: 1px solid #0f3460;
|
|
|
+ }}
|
|
|
+ #tree-container {{
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ }}
|
|
|
+ #tree-container svg {{
|
|
|
+ display: block;
|
|
|
+ }}
|
|
|
+ #ego-container {{
|
|
|
+ height: 420px;
|
|
|
+ border-top: 1px solid #0f3460;
|
|
|
+ display: none;
|
|
|
+ }}
|
|
|
+ #ego-container svg {{
|
|
|
+ display: block;
|
|
|
+ }}
|
|
|
#graph {{
|
|
|
flex: 1;
|
|
|
position: relative;
|
|
|
@@ -490,6 +513,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
{tabs_html}
|
|
|
</div>
|
|
|
<div class="main-content">
|
|
|
+ <div id="left-panel">
|
|
|
+ <div id="tree-container"></div>
|
|
|
+ <div id="ego-container"></div>
|
|
|
+ </div>
|
|
|
<div id="graph">
|
|
|
<div class="tooltip" id="tooltip"></div>
|
|
|
</div>
|
|
|
@@ -577,6 +604,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
let simulation = null;
|
|
|
let svg = null;
|
|
|
let g = null;
|
|
|
+ let treeSvg = null; // 人设树独立SVG
|
|
|
+ let treeG = null; // 人设树group
|
|
|
+ let egoSvg = null; // 关系图独立SVG
|
|
|
+ let egoG = null; // 关系图group
|
|
|
let zoom = null;
|
|
|
let showLabels = true;
|
|
|
let showCrossLayerEdges = false; // 跨层边默认隐藏
|
|
|
@@ -606,9 +637,11 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
// 初始化
|
|
|
function init() {{
|
|
|
const container = document.getElementById("graph");
|
|
|
+ const treeContainer = document.getElementById("tree-container");
|
|
|
const width = container.clientWidth;
|
|
|
const height = container.clientHeight;
|
|
|
|
|
|
+ // 创建圆形层SVG
|
|
|
svg = d3.select("#graph")
|
|
|
.append("svg")
|
|
|
.attr("width", width)
|
|
|
@@ -624,6 +657,30 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
|
|
|
svg.call(zoom);
|
|
|
|
|
|
+ // 创建人设树SVG(独立可滚动)
|
|
|
+ treeSvg = d3.select("#tree-container")
|
|
|
+ .append("svg")
|
|
|
+ .attr("width", 400);
|
|
|
+
|
|
|
+ treeG = treeSvg.append("g");
|
|
|
+
|
|
|
+ // 创建关系图SVG(在独立的ego-container中)
|
|
|
+ egoSvg = d3.select("#ego-container")
|
|
|
+ .append("svg")
|
|
|
+ .attr("width", 400)
|
|
|
+ .attr("height", 400);
|
|
|
+
|
|
|
+ egoG = egoSvg.append("g");
|
|
|
+
|
|
|
+ // 关系图缩放
|
|
|
+ const egoZoom = d3.zoom()
|
|
|
+ .scaleExtent([0.3, 3])
|
|
|
+ .on("zoom", (event) => {{
|
|
|
+ egoG.attr("transform", event.transform);
|
|
|
+ }});
|
|
|
+
|
|
|
+ egoSvg.call(egoZoom);
|
|
|
+
|
|
|
// 绑定Tab点击事件
|
|
|
document.querySelectorAll(".tab").forEach((tab, index) => {{
|
|
|
tab.addEventListener("click", () => switchTab(index));
|
|
|
@@ -736,6 +793,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
function renderGraph(data) {{
|
|
|
// 清空现有图谱
|
|
|
g.selectAll("*").remove();
|
|
|
+ treeG.selectAll("*").remove();
|
|
|
if (simulation) {{
|
|
|
simulation.stop();
|
|
|
}}
|
|
|
@@ -743,12 +801,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
const container = document.getElementById("graph");
|
|
|
const width = container.clientWidth;
|
|
|
|
|
|
- // 布局配置:左侧人设树,右侧三层圆形
|
|
|
- // 树宽度自适应:占总宽度的35%,最小400,最大550
|
|
|
- const treeAreaWidth = Math.max(400, Math.min(550, width * 0.35));
|
|
|
- const circleAreaLeft = treeAreaWidth + 40; // 圆形区域起始X
|
|
|
- const circleAreaWidth = width - circleAreaLeft - 50;
|
|
|
- const circleAreaCenterX = circleAreaLeft + circleAreaWidth / 2;
|
|
|
+ // 布局配置:人设树在左侧独立容器,右侧是圆形层
|
|
|
+ const treeAreaWidth = 400; // 固定树宽度
|
|
|
+ const circleAreaWidth = width - 40;
|
|
|
+ const circleAreaCenterX = circleAreaWidth / 2;
|
|
|
|
|
|
// 准备数据
|
|
|
const nodes = data.nodes.map(n => ({{
|
|
|
@@ -1003,18 +1059,16 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
const layerRadius = {{
|
|
|
0: calcRadius(layerCounts[0]),
|
|
|
1: calcRadius(layerCounts[1]),
|
|
|
- 2: calcRadius(layerCounts[2]),
|
|
|
- 3: 250 // 关系图层默认半径(更大,容纳更多节点)
|
|
|
+ 2: calcRadius(layerCounts[2])
|
|
|
}};
|
|
|
console.log("每层半径:", layerRadius);
|
|
|
|
|
|
- // 计算四层的高度和圆心Y坐标
|
|
|
+ // 计算三层的高度和圆心Y坐标(关系图移到左侧)
|
|
|
const layerPadding = 50;
|
|
|
const layerHeights = {{
|
|
|
0: layerRadius[0] * 2 + layerPadding,
|
|
|
1: layerRadius[1] * 2 + layerPadding,
|
|
|
- 2: layerRadius[2] * 2 + layerPadding,
|
|
|
- 3: layerRadius[3] * 2 + layerPadding
|
|
|
+ 2: layerRadius[2] * 2 + layerPadding
|
|
|
}};
|
|
|
|
|
|
// 获取人设树数据(单棵树)
|
|
|
@@ -1022,8 +1076,12 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
// 树高度自适应:根据节点数量,每个节点约12px,最小800
|
|
|
const treeHeight = Math.max(800, personaTree.nodeCount * 12 + 150);
|
|
|
|
|
|
- // 计算总高度(取圆形和树的最大值)
|
|
|
- const circleHeight = layerHeights[0] + layerHeights[1] + layerHeights[2] + layerHeights[3];
|
|
|
+ // 关系图配置(放在树下方)
|
|
|
+ const egoRadius = 180;
|
|
|
+ const egoAreaHeight = egoRadius * 2 + 80;
|
|
|
+
|
|
|
+ // 计算总高度(取圆形和树+关系图的最大值)
|
|
|
+ const circleHeight = layerHeights[0] + layerHeights[1] + layerHeights[2];
|
|
|
const height = Math.max(circleHeight + 100, treeHeight + 80, container.clientHeight);
|
|
|
|
|
|
// 计算每层圆心坐标(在右侧区域)
|
|
|
@@ -1039,9 +1097,6 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
currentY += layerRadius[1] + layerPadding + layerRadius[2];
|
|
|
layerCenterY[2] = currentY;
|
|
|
layerCenterX[2] = circleAreaCenterX;
|
|
|
- currentY += layerRadius[2] + layerPadding + layerRadius[3];
|
|
|
- layerCenterY[3] = currentY;
|
|
|
- layerCenterX[3] = circleAreaCenterX;
|
|
|
|
|
|
console.log("每层圆心X:", layerCenterX);
|
|
|
console.log("每层圆心Y:", layerCenterY);
|
|
|
@@ -1271,15 +1326,14 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
// 绘制层背景(圆形,使用动态大小)
|
|
|
const layerBg = g.append("g").attr("class", "layer-backgrounds");
|
|
|
|
|
|
- // 层配置:名称、颜色(四层圆)
|
|
|
+ // 层配置:名称、颜色(三层圆,关系图在左侧)
|
|
|
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: 3, color: "rgba(233, 69, 96, 0.08)", stroke: "rgba(233, 69, 96, 0.3)" }}
|
|
|
+ {{ name: "人设", layer: 2, color: "rgba(155, 89, 182, 0.08)", stroke: "rgba(155, 89, 182, 0.3)" }}
|
|
|
];
|
|
|
|
|
|
- // 绘制四层圆形背景
|
|
|
+ // 绘制三层圆形背景
|
|
|
layerConfig.forEach(cfg => {{
|
|
|
const cx = layerCenterX[cfg.layer];
|
|
|
const cy = layerCenterY[cfg.layer];
|
|
|
@@ -1303,33 +1357,48 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
.attr("font-weight", "bold")
|
|
|
.attr("text-anchor", "end")
|
|
|
.text(cfg.name);
|
|
|
-
|
|
|
- // 关系图层添加占位提示
|
|
|
- if (cfg.layer === 3) {{
|
|
|
- layerBg.append("text")
|
|
|
- .attr("class", "ego-placeholder")
|
|
|
- .attr("x", cx)
|
|
|
- .attr("y", cy)
|
|
|
- .attr("dy", "0.35em")
|
|
|
- .attr("fill", "rgba(255,255,255,0.3)")
|
|
|
- .attr("font-size", "12px")
|
|
|
- .attr("text-anchor", "middle")
|
|
|
- .text("点击左侧人设树节点查看关系");
|
|
|
- }}
|
|
|
}});
|
|
|
|
|
|
- // 创建关系图层内容组(动态填充)
|
|
|
- const egoGraphGroup = g.append("g")
|
|
|
- .attr("class", "ego-graph-content")
|
|
|
- .attr("transform", `translate(${{layerCenterX[3]}}, ${{layerCenterY[3]}})`);
|
|
|
-
|
|
|
- // 绘制左侧人设树(单棵树,根节点为"人设")
|
|
|
+ // 绘制左侧人设树(在独立的treeSvg中)
|
|
|
const dimColors = {{ "灵感点": "#f39c12", "目的点": "#3498db", "关键点": "#9b59b6" }};
|
|
|
|
|
|
- const treeGroup = g.append("g")
|
|
|
+ // 更新树SVG高度
|
|
|
+ treeSvg.attr("height", treeHeight + 50);
|
|
|
+
|
|
|
+ const treeGroup = treeG.append("g")
|
|
|
.attr("class", "persona-tree")
|
|
|
.attr("transform", `translate(15, 25)`);
|
|
|
|
|
|
+ // 设置关系图SVG
|
|
|
+ egoG.selectAll("*").remove();
|
|
|
+
|
|
|
+ const egoDisplayRadius = 180;
|
|
|
+ const egoCenterGroup = egoG.append("g")
|
|
|
+ .attr("class", "ego-center")
|
|
|
+ .attr("transform", `translate(200, 210)`);
|
|
|
+
|
|
|
+ // 关系图背景圆
|
|
|
+ egoCenterGroup.append("circle")
|
|
|
+ .attr("class", "ego-background")
|
|
|
+ .attr("r", egoDisplayRadius)
|
|
|
+ .attr("fill", "rgba(233, 69, 96, 0.08)")
|
|
|
+ .attr("stroke", "rgba(233, 69, 96, 0.3)")
|
|
|
+ .attr("stroke-width", 2);
|
|
|
+
|
|
|
+ // 关系图标题
|
|
|
+ egoCenterGroup.append("text")
|
|
|
+ .attr("class", "ego-title")
|
|
|
+ .attr("y", -egoDisplayRadius - 10)
|
|
|
+ .attr("text-anchor", "middle")
|
|
|
+ .attr("fill", "rgba(255,255,255,0.5)")
|
|
|
+ .attr("font-size", "13px")
|
|
|
+ .attr("font-weight", "bold")
|
|
|
+ .text("关系图");
|
|
|
+
|
|
|
+ // 关系图内容组
|
|
|
+ egoCenterGroup.append("g")
|
|
|
+ .attr("class", "ego-graph-content");
|
|
|
+
|
|
|
// 绘制背景矩形
|
|
|
treeGroup.append("rect")
|
|
|
.attr("x", -5)
|
|
|
@@ -2330,6 +2399,9 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
let currentEgoSimulation = null; // 保存当前的力模拟,用于停止
|
|
|
|
|
|
function renderEgoGraph(centerNodeId, centerNodeName) {{
|
|
|
+ // 显示关系图容器
|
|
|
+ document.getElementById("ego-container").style.display = "block";
|
|
|
+
|
|
|
// 获取关系图层组
|
|
|
const egoGroup = d3.select(".ego-graph-content");
|
|
|
if (egoGroup.empty()) return;
|
|
|
@@ -2343,11 +2415,11 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
// 清除旧内容
|
|
|
egoGroup.selectAll("*").remove();
|
|
|
|
|
|
- // 隐藏占位提示
|
|
|
- d3.select(".ego-placeholder").style("display", "none");
|
|
|
+ // 更新标题
|
|
|
+ d3.select(".ego-container .ego-title").text(`关系图: ${{centerNodeName}}`);
|
|
|
|
|
|
- // 获取层半径(需要从全局获取或重新计算)
|
|
|
- const radius = 120; // 固定半径
|
|
|
+ // 获取层半径
|
|
|
+ const radius = 160; // 固定半径
|
|
|
|
|
|
// 找到所有相关的边
|
|
|
const relatedEdges = personaTreeData.edges.filter(e =>
|
|
|
@@ -2568,8 +2640,8 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
if (!egoGroup.empty()) {{
|
|
|
egoGroup.selectAll("*").remove();
|
|
|
}}
|
|
|
- // 显示占位提示
|
|
|
- d3.select(".ego-placeholder").style("display", null);
|
|
|
+ // 隐藏关系图容器
|
|
|
+ document.getElementById("ego-container").style.display = "none";
|
|
|
// 停止模拟
|
|
|
if (currentEgoSimulation) {{
|
|
|
currentEgoSimulation.stop();
|
|
|
@@ -2579,6 +2651,9 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
|
|
|
// 渲染单条边和两个节点(点击树边时调用)
|
|
|
function renderEgoGraphEdge(edgeData, sourceNode, targetNode) {{
|
|
|
+ // 显示关系图容器
|
|
|
+ document.getElementById("ego-container").style.display = "block";
|
|
|
+
|
|
|
const egoGroup = d3.select(".ego-graph-content");
|
|
|
if (egoGroup.empty()) return;
|
|
|
|
|
|
@@ -2590,9 +2665,11 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
|
|
|
// 清除旧内容
|
|
|
egoGroup.selectAll("*").remove();
|
|
|
- d3.select(".ego-placeholder").style("display", "none");
|
|
|
|
|
|
- const radius = 250;
|
|
|
+ // 更新标题
|
|
|
+ d3.select(".ego-container .ego-title").text(`关系图: ${{edgeData.边类型}}`);
|
|
|
+
|
|
|
+ const radius = 160;
|
|
|
|
|
|
// 边类型颜色
|
|
|
const edgeColors = {{
|