|
|
@@ -756,6 +756,14 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
return baseY + tilt;
|
|
|
}}
|
|
|
|
|
|
+ // 获取节点的层级编号(用于层间排斥)
|
|
|
+ function getNodeLayer(d) {{
|
|
|
+ if (d.source === "帖子") {{
|
|
|
+ return d.节点类型 === "点" ? 0 : 1; // 点=0, 标签=1
|
|
|
+ }}
|
|
|
+ return d.是否扩展 ? 3 : 2; // 人设标签=2, 分类=3
|
|
|
+ }}
|
|
|
+
|
|
|
// 获取节点的基准Y(四层布局)
|
|
|
function getNodeBaseY(d) {{
|
|
|
if (d.source === "帖子") {{
|
|
|
@@ -767,6 +775,53 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
return personaBaseY;
|
|
|
}}
|
|
|
|
|
|
+ // 自定义层间排斥力
|
|
|
+ function forceLayerSeparation(strength) {{
|
|
|
+ let nodes;
|
|
|
+ const layerGap = height * 0.12; // 最小层间距
|
|
|
+
|
|
|
+ function force(alpha) {{
|
|
|
+ for (let i = 0; i < nodes.length; i++) {{
|
|
|
+ const nodeA = nodes[i];
|
|
|
+ const layerA = getNodeLayer(nodeA);
|
|
|
+ const baseYA = getNodeBaseY(nodeA);
|
|
|
+
|
|
|
+ for (let j = i + 1; j < nodes.length; j++) {{
|
|
|
+ const nodeB = nodes[j];
|
|
|
+ const layerB = getNodeLayer(nodeB);
|
|
|
+
|
|
|
+ // 只对不同层的节点施加排斥力
|
|
|
+ if (layerA === layerB) continue;
|
|
|
+
|
|
|
+ const dy = nodeB.y - nodeA.y;
|
|
|
+ const absDy = Math.abs(dy);
|
|
|
+
|
|
|
+ // 如果Y方向距离小于层间距,施加排斥力
|
|
|
+ if (absDy < layerGap) {{
|
|
|
+ const baseYB = getNodeBaseY(nodeB);
|
|
|
+ // 根据基准Y判断谁应该在上面
|
|
|
+ const shouldABeAbove = baseYA < baseYB;
|
|
|
+ const pushStrength = strength * alpha * (layerGap - absDy) / layerGap;
|
|
|
+
|
|
|
+ if (shouldABeAbove) {{
|
|
|
+ nodeA.vy -= pushStrength;
|
|
|
+ nodeB.vy += pushStrength;
|
|
|
+ }} else {{
|
|
|
+ nodeA.vy += pushStrength;
|
|
|
+ nodeB.vy -= pushStrength;
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+
|
|
|
+ force.initialize = function(_) {{
|
|
|
+ nodes = _;
|
|
|
+ }};
|
|
|
+
|
|
|
+ return force;
|
|
|
+ }}
|
|
|
+
|
|
|
// 力导向模拟
|
|
|
simulation = d3.forceSimulation(nodes)
|
|
|
.force("link", d3.forceLink(links).id(d => d.id)
|
|
|
@@ -786,7 +841,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}}))
|
|
|
// X方向:拉向目标位置,但允许被推开
|
|
|
.force("x", d3.forceX(d => nodeTargetX[d.id] || width / 2).strength(0.15))
|
|
|
- // Y方向力:三层布局+倾斜
|
|
|
+ // Y方向力:四层布局+倾斜
|
|
|
.force("y", d3.forceY(d => {{
|
|
|
const baseY = getNodeBaseY(d);
|
|
|
return getTiltedY(baseY, d.x || width / 2);
|
|
|
@@ -796,7 +851,9 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
// 帖子节点碰撞半径更大
|
|
|
if (d.source === "帖子") return 60;
|
|
|
return 40;
|
|
|
- }}));
|
|
|
+ }}))
|
|
|
+ // 层间排斥力:不同层节点相互远离
|
|
|
+ .force("layerSeparation", forceLayerSeparation(8));
|
|
|
|
|
|
// 边类型到CSS类的映射
|
|
|
const edgeTypeClass = {{
|