|
|
|
|
|
<!DOCTYPE html>
|
|
|
<html lang="zh">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<title>三国人物关系图谱</title>
|
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
|
<style>
|
|
|
body {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
background: linear-gradient(135deg, #0a1a3a, #1a3a6a);
|
|
|
color: white;
|
|
|
font-family: "Microsoft YaHei", sans-serif;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
#header {
|
|
|
text-align: center;
|
|
|
padding: 20px 0;
|
|
|
background: rgba(0,0,0,0.3);
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
#title {
|
|
|
font-size: 36px;
|
|
|
font-weight: bold;
|
|
|
background: linear-gradient(to right, #ff8a00, #e52e71);
|
|
|
-webkit-background-clip: text;
|
|
|
-webkit-text-fill-color: transparent;
|
|
|
text-shadow: 0 0 10px rgba(255,138,0,0.3);
|
|
|
}
|
|
|
#container {
|
|
|
display: flex;
|
|
|
height: calc(100vh - 120px);
|
|
|
}
|
|
|
#graph {
|
|
|
flex: 3;
|
|
|
border-right: 1px solid rgba(255,255,255,0.1);
|
|
|
}
|
|
|
#panel {
|
|
|
flex: 1;
|
|
|
padding: 20px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
.node {
|
|
|
stroke: #fff;
|
|
|
stroke-width: 1.5px;
|
|
|
filter: drop-shadow(0 0 5px rgba(255,255,255,0.5));
|
|
|
}
|
|
|
.node:hover {
|
|
|
filter: drop-shadow(0 0 10px gold);
|
|
|
}
|
|
|
.link {
|
|
|
stroke-opacity: 0.6;
|
|
|
}
|
|
|
.link.military {
|
|
|
stroke: #ff4d4d;
|
|
|
}
|
|
|
.link.political {
|
|
|
stroke: #4da6ff;
|
|
|
}
|
|
|
.link.family {
|
|
|
stroke: #66ff66;
|
|
|
}
|
|
|
.legend {
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
.legend-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
.legend-color {
|
|
|
width: 20px;
|
|
|
height: 20px;
|
|
|
margin-right: 10px;
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
#tooltip {
|
|
|
position: absolute;
|
|
|
padding: 10px;
|
|
|
background: rgba(0,0,0,0.8);
|
|
|
border-radius: 5px;
|
|
|
pointer-events: none;
|
|
|
max-width: 200px;
|
|
|
font-size: 14px;
|
|
|
z-index: 10;
|
|
|
}
|
|
|
.layout-btn {
|
|
|
padding: 8px 15px;
|
|
|
margin-right: 10px;
|
|
|
background: rgba(255,255,255,0.1);
|
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
|
color: white;
|
|
|
cursor: pointer;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
.layout-btn:hover {
|
|
|
background: rgba(255,255,255,0.2);
|
|
|
}
|
|
|
#controls {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
.panel-title {
|
|
|
font-size: 20px;
|
|
|
margin-bottom: 15px;
|
|
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
|
|
padding-bottom: 10px;
|
|
|
}
|
|
|
.panel-content {
|
|
|
font-size: 14px;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
.relation-item {
|
|
|
margin-bottom: 10px;
|
|
|
padding: 8px;
|
|
|
background: rgba(255,255,255,0.1);
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div id="header">
|
|
|
<div id="title">三国人物关系图谱</div>
|
|
|
</div>
|
|
|
<div id="controls">
|
|
|
<button class="layout-btn" onclick="changeLayout('force')">力导向布局</button>
|
|
|
<button class="layout-btn" onclick="changeLayout('radial')">辐射状布局</button>
|
|
|
<button class="layout-btn" onclick="changeLayout('circular')">环形布局</button>
|
|
|
<button class="layout-btn" onclick="changeLayout('grid')">网格布局</button>
|
|
|
</div>
|
|
|
<div id="container">
|
|
|
<div id="graph"></div>
|
|
|
<div id="panel">
|
|
|
<div class="panel-title">人物信息</div>
|
|
|
<div id="person-info" class="panel-content">
|
|
|
点击节点查看详细信息
|
|
|
</div>
|
|
|
<div class="panel-title" style="margin-top: 20px;">关系</div>
|
|
|
<div id="relations-list"></div>
|
|
|
<div class="panel-title legend">图例</div>
|
|
|
<div class="legend-item">
|
|
|
<div class="legend-color" style="background: #ff4d4d;"></div>
|
|
|
<div>军事关系</div>
|
|
|
</div>
|
|
|
<div class="legend-item">
|
|
|
<div class="legend-color" style="background: #4da6ff;"></div>
|
|
|
<div>政治关系</div>
|
|
|
</div>
|
|
|
<div class="legend-item">
|
|
|
<div class="legend-color" style="background: #66ff66;"></div>
|
|
|
<div>家族关系</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div id="tooltip"></div>
|
|
|
|
|
|
<script>
|
|
|
// 数据定义
|
|
|
const data = {
|
|
|
"nodes": [
|
|
|
{
|
|
|
"id": "曹操",
|
|
|
"group": 1,
|
|
|
"type": "君主",
|
|
|
"desc": "字孟德,魏国奠基人,政治家、军事家、文学家",
|
|
|
"rank": 10
|
|
|
},
|
|
|
{
|
|
|
"id": "刘备",
|
|
|
"group": 2,
|
|
|
"type": "君主",
|
|
|
"desc": "字玄德,蜀汉开国皇帝,以仁德著称",
|
|
|
"rank": 10
|
|
|
},
|
|
|
{
|
|
|
"id": "孙权",
|
|
|
"group": 3,
|
|
|
"type": "君主",
|
|
|
"desc": "字仲谋,吴国建立者,统治江东50余年",
|
|
|
"rank": 10
|
|
|
},
|
|
|
{
|
|
|
"id": "诸葛亮",
|
|
|
"group": 2,
|
|
|
"type": "谋士",
|
|
|
"desc": "字孔明,蜀汉丞相,杰出的政治家、军事家",
|
|
|
"rank": 9
|
|
|
},
|
|
|
{
|
|
|
"id": "司马懿",
|
|
|
"group": 1,
|
|
|
"type": "谋士",
|
|
|
"desc": "字仲达,魏国重臣,晋朝奠基人",
|
|
|
"rank": 9
|
|
|
},
|
|
|
{
|
|
|
"id": "周瑜",
|
|
|
"group": 3,
|
|
|
"type": "武将",
|
|
|
"desc": "字公瑾,东吴名将,赤壁之战主帅",
|
|
|
"rank": 8
|
|
|
},
|
|
|
{
|
|
|
"id": "关羽",
|
|
|
"group": 2,
|
|
|
"type": "武将",
|
|
|
"desc": "字云长,蜀汉五虎上将之首,忠义化身",
|
|
|
"rank": 8
|
|
|
},
|
|
|
{
|
|
|
"id": "张飞",
|
|
|
"group": 2,
|
|
|
"type": "武将",
|
|
|
"desc": "字益德,蜀汉五虎上将,勇猛善战",
|
|
|
"rank": 7
|
|
|
},
|
|
|
{
|
|
|
"id": "赵云",
|
|
|
"group": 2,
|
|
|
"type": "武将",
|
|
|
"desc": "字子龙,蜀汉五虎上将,常胜将军",
|
|
|
"rank": 7
|
|
|
},
|
|
|
{
|
|
|
"id": "吕布",
|
|
|
"group": 4,
|
|
|
"type": "武将",
|
|
|
"desc": "字奉先,猛将,有\"人中吕布,马中赤兔\"之称",
|
|
|
"rank": 7
|
|
|
},
|
|
|
{
|
|
|
"id": "荀彧",
|
|
|
"group": 1,
|
|
|
"type": "谋士",
|
|
|
"desc": "字文若,曹操重要谋士,战略家",
|
|
|
"rank": 7
|
|
|
},
|
|
|
{
|
|
|
"id": "郭嘉",
|
|
|
"group": 1,
|
|
|
"type": "谋士",
|
|
|
"desc": "字奉孝,曹操重要谋士,英年早逝",
|
|
|
"rank": 7
|
|
|
},
|
|
|
{
|
|
|
"id": "鲁肃",
|
|
|
"group": 3,
|
|
|
"type": "谋士",
|
|
|
"desc": "字子敬,东吴战略家,促成孙刘联盟",
|
|
|
"rank": 7
|
|
|
},
|
|
|
{
|
|
|
"id": "陆逊",
|
|
|
"group": 3,
|
|
|
"type": "武将",
|
|
|
"desc": "字伯言,东吴名将,夷陵之战击败刘备",
|
|
|
"rank": 7
|
|
|
},
|
|
|
{
|
|
|
"id": "黄盖",
|
|
|
"group": 3,
|
|
|
"type": "武将",
|
|
|
"desc": "字公覆,东吴老将,赤壁之战献苦肉计",
|
|
|
"rank": 6
|
|
|
},
|
|
|
{
|
|
|
"id": "马超",
|
|
|
"group": 2,
|
|
|
"type": "武将",
|
|
|
"desc": "字孟起,蜀汉五虎上将,西凉名将",
|
|
|
"rank": 6
|
|
|
},
|
|
|
{
|
|
|
"id": "黄忠",
|
|
|
"group": 2,
|
|
|
"type": "武将",
|
|
|
"desc": "字汉升,蜀汉五虎上将,老当益壮",
|
|
|
"rank": 6
|
|
|
},
|
|
|
{
|
|
|
"id": "夏侯惇",
|
|
|
"group": 1,
|
|
|
"type": "武将",
|
|
|
"desc": "字元让,曹操宗族大将,独眼将军",
|
|
|
"rank": 6
|
|
|
},
|
|
|
{
|
|
|
"id": "张辽",
|
|
|
"group": 1,
|
|
|
"type": "武将",
|
|
|
"desc": "字文远,魏国名将,逍遥津之战威震江东",
|
|
|
"rank": 6
|
|
|
},
|
|
|
{
|
|
|
"id": "许褚",
|
|
|
"group": 1,
|
|
|
"type": "武将",
|
|
|
"desc": "字仲康,曹操虎卫,号称\"虎痴\"",
|
|
|
"rank": 5
|
|
|
}
|
|
|
],
|
|
|
"links": [
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "刘备",
|
|
|
"value": 5,
|
|
|
"type": "political",
|
|
|
"desc": "早期合作对抗董卓,后期成为主要对手"
|
|
|
},
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "孙权",
|
|
|
"value": 5,
|
|
|
"type": "political",
|
|
|
"desc": "赤壁之战对手,长期对峙"
|
|
|
},
|
|
|
{
|
|
|
"source": "刘备",
|
|
|
"target": "孙权",
|
|
|
"value": 5,
|
|
|
"type": "political",
|
|
|
"desc": "曾结盟抗曹,后因荆州问题反目"
|
|
|
},
|
|
|
{
|
|
|
"source": "刘备",
|
|
|
"target": "诸葛亮",
|
|
|
"value": 8,
|
|
|
"type": "political",
|
|
|
"desc": "三顾茅庐请出,君臣相得"
|
|
|
},
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "司马懿",
|
|
|
"value": 6,
|
|
|
"type": "political",
|
|
|
"desc": "重要谋士,后期掌握大权"
|
|
|
},
|
|
|
{
|
|
|
"source": "孙权",
|
|
|
"target": "周瑜",
|
|
|
"value": 7,
|
|
|
"type": "political",
|
|
|
"desc": "君臣关系,周瑜为东吴重要将领"
|
|
|
},
|
|
|
{
|
|
|
"source": "刘备",
|
|
|
"target": "关羽",
|
|
|
"value": 8,
|
|
|
"type": "military",
|
|
|
"desc": "结义兄弟,重要将领"
|
|
|
},
|
|
|
{
|
|
|
"source": "刘备",
|
|
|
"target": "张飞",
|
|
|
"value": 8,
|
|
|
"type": "military",
|
|
|
"desc": "结义兄弟,重要将领"
|
|
|
},
|
|
|
{
|
|
|
"source": "刘备",
|
|
|
"target": "赵云",
|
|
|
"value": 7,
|
|
|
"type": "military",
|
|
|
"desc": "重要将领,曾救阿斗"
|
|
|
},
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "荀彧",
|
|
|
"value": 7,
|
|
|
"type": "political",
|
|
|
"desc": "重要谋士,战略规划者"
|
|
|
},
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "郭嘉",
|
|
|
"value": 7,
|
|
|
"type": "political",
|
|
|
"desc": "重要谋士,英年早逝"
|
|
|
},
|
|
|
{
|
|
|
"source": "孙权",
|
|
|
"target": "鲁肃",
|
|
|
"value": 6,
|
|
|
"type": "political",
|
|
|
"desc": "重要谋士,主张联刘抗曹"
|
|
|
},
|
|
|
{
|
|
|
"source": "孙权",
|
|
|
"target": "陆逊",
|
|
|
"value": 6,
|
|
|
"type": "military",
|
|
|
"desc": "重要将领,夷陵之战主帅"
|
|
|
},
|
|
|
{
|
|
|
"source": "周瑜",
|
|
|
"target": "诸葛亮",
|
|
|
"value": 5,
|
|
|
"type": "political",
|
|
|
"desc": "赤壁之战合作,后因荆州问题对立"
|
|
|
},
|
|
|
{
|
|
|
"source": "关羽",
|
|
|
"target": "曹操",
|
|
|
"value": 5,
|
|
|
"type": "political",
|
|
|
"desc": "曾短暂投靠曹操,后回归刘备"
|
|
|
},
|
|
|
{
|
|
|
"source": "吕布",
|
|
|
"target": "曹操",
|
|
|
"value": 5,
|
|
|
"type": "military",
|
|
|
"desc": "敌对关系,被曹操擒杀"
|
|
|
},
|
|
|
{
|
|
|
"source": "吕布",
|
|
|
"target": "刘备",
|
|
|
"value": 5,
|
|
|
"type": "political",
|
|
|
"desc": "曾合作又反目,夺取徐州"
|
|
|
},
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "夏侯惇",
|
|
|
"value": 6,
|
|
|
"type": "family",
|
|
|
"desc": "宗族关系,重要将领"
|
|
|
},
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "张辽",
|
|
|
"value": 6,
|
|
|
"type": "military",
|
|
|
"desc": "重要降将,五子良将之首"
|
|
|
},
|
|
|
{
|
|
|
"source": "曹操",
|
|
|
"target": "许褚",
|
|
|
"value": 5,
|
|
|
"type": "military",
|
|
|
"desc": "贴身护卫,忠勇将领"
|
|
|
},
|
|
|
{
|
|
|
"source": "刘备",
|
|
|
"target": "马超",
|
|
|
"value": 5,
|
|
|
"type": "military",
|
|
|
"desc": "重要降将,五虎上将"
|
|
|
},
|
|
|
{
|
|
|
"source": "刘备",
|
|
|
"target": "黄忠",
|
|
|
"value": 5,
|
|
|
"type": "military",
|
|
|
"desc": "重要将领,五虎上将"
|
|
|
},
|
|
|
{
|
|
|
"source": "孙权",
|
|
|
"target": "黄盖",
|
|
|
"value": 5,
|
|
|
"type": "military",
|
|
|
"desc": "重要将领,献苦肉计"
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
|
|
|
// 可视化实现
|
|
|
const width = document.getElementById('graph').clientWidth;
|
|
|
const height = document.getElementById('graph').clientHeight;
|
|
|
|
|
|
const svg = d3.select("#graph")
|
|
|
.append("svg")
|
|
|
.attr("width", width)
|
|
|
.attr("height", height)
|
|
|
.attr("viewBox", [0, 0, width, height])
|
|
|
.attr("style", "max-width: 100%; height: auto;");
|
|
|
|
|
|
const simulation = d3.forceSimulation(data.nodes)
|
|
|
.force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
|
|
|
.force("charge", d3.forceManyBody().strength(-300))
|
|
|
.force("x", d3.forceX(width / 2).strength(0.05))
|
|
|
.force("y", d3.forceY(height / 2).strength(0.05))
|
|
|
.force("collision", d3.forceCollide().radius(d => Math.sqrt(d.rank) * 10));
|
|
|
|
|
|
// 定义渐变
|
|
|
const defs = svg.append("defs");
|
|
|
|
|
|
const gradient1 = defs.append("radialGradient")
|
|
|
.attr("id", "nodeGradient1")
|
|
|
.attr("cx", "50%")
|
|
|
.attr("cy", "50%")
|
|
|
.attr("r", "70%")
|
|
|
.attr("fx", "40%")
|
|
|
.attr("fy", "40%");
|
|
|
|
|
|
gradient1.append("stop").attr("offset", "0%").attr("stop-color", "#4da6ff");
|
|
|
gradient1.append("stop").attr("offset", "100%").attr("stop-color", "#1a3a6a");
|
|
|
|
|
|
const gradient2 = defs.append("radialGradient")
|
|
|
.attr("id", "nodeGradient2")
|
|
|
.attr("cx", "50%")
|
|
|
.attr("cy", "50%")
|
|
|
.attr("r", "70%")
|
|
|
.attr("fx", "40%")
|
|
|
.attr("fy", "40%");
|
|
|
|
|
|
gradient2.append("stop").attr("offset", "0%").attr("stop-color", "#ff6666");
|
|
|
gradient2.append("stop").attr("offset", "100%").attr("stop-color", "#8b0000");
|
|
|
|
|
|
const gradient3 = defs.append("radialGradient")
|
|
|
.attr("id", "nodeGradient3")
|
|
|
.attr("cx", "50%")
|
|
|
.attr("cy", "50%")
|
|
|
.attr("r", "70%")
|
|
|
.attr("fx", "40%")
|
|
|
.attr("fy", "40%");
|
|
|
|
|
|
gradient3.append("stop").attr("offset", "0%").attr("stop-color", "#66ff66");
|
|
|
gradient3.append("stop").attr("offset", "100%").attr("stop-color", "#006400");
|
|
|
|
|
|
const gradient4 = defs.append("radialGradient")
|
|
|
.attr("id", "nodeGradient4")
|
|
|
.attr("cx", "50%")
|
|
|
.attr("cy", "50%")
|
|
|
.attr("r", "70%")
|
|
|
.attr("fx", "40%")
|
|
|
.attr("fy", "40%");
|
|
|
|
|
|
gradient4.append("stop").attr("offset", "0%").attr("stop-color", "#ffcc00");
|
|
|
gradient4.append("stop").attr("offset", "100%").attr("stop-color", "#996600");
|
|
|
|
|
|
// 绘制连线
|
|
|
const link = svg.append("g")
|
|
|
.selectAll("line")
|
|
|
.data(data.links)
|
|
|
.join("line")
|
|
|
.attr("class", d => `link ${d.type}`)
|
|
|
.attr("stroke-width", d => Math.sqrt(d.value));
|
|
|
|
|
|
// 绘制节点
|
|
|
const node = svg.append("g")
|
|
|
.selectAll("circle")
|
|
|
.data(data.nodes)
|
|
|
.join("circle")
|
|
|
.attr("class", "node")
|
|
|
.attr("r", d => Math.sqrt(d.rank) * 5)
|
|
|
.attr("fill", d => {
|
|
|
switch(d.group) {
|
|
|
case 1: return "url(#nodeGradient1)";
|
|
|
case 2: return "url(#nodeGradient2)";
|
|
|
case 3: return "url(#nodeGradient3)";
|
|
|
default: return "url(#nodeGradient4)";
|
|
|
}
|
|
|
})
|
|
|
.call(drag(simulation))
|
|
|
.on("mouseover", showTooltip)
|
|
|
.on("mouseout", hideTooltip)
|
|
|
.on("click", updatePanel);
|
|
|
|
|
|
// 添加节点文字
|
|
|
const text = svg.append("g")
|
|
|
.selectAll("text")
|
|
|
.data(data.nodes)
|
|
|
.join("text")
|
|
|
.attr("dy", 4)
|
|
|
.attr("dominant-baseline", "central")
|
|
|
.attr("text-anchor", "middle")
|
|
|
.text(d => d.id)
|
|
|
.style("font-size", d => Math.max(10, Math.sqrt(d.rank) * 3))
|
|
|
.style("fill", "white")
|
|
|
.style("pointer-events", "none");
|
|
|
|
|
|
// 更新力导向布局
|
|
|
simulation.on("tick", () => {
|
|
|
link
|
|
|
.attr("x1", d => d.source.x)
|
|
|
.attr("y1", d => d.source.y)
|
|
|
.attr("x2", d => d.target.x)
|
|
|
.attr("y2", d => d.target.y);
|
|
|
|
|
|
node
|
|
|
.attr("cx", d => d.x)
|
|
|
.attr("cy", d => d.y);
|
|
|
|
|
|
text
|
|
|
.attr("x", d => d.x)
|
|
|
.attr("y", d => d.y);
|
|
|
});
|
|
|
|
|
|
// 拖拽功能
|
|
|
function drag(simulation) {
|
|
|
function dragstarted(event, d) {
|
|
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
|
d.fx = d.x;
|
|
|
d.fy = d.y;
|
|
|
}
|
|
|
|
|
|
function dragged(event, d) {
|
|
|
d.fx = event.x;
|
|
|
d.fy = event.y;
|
|
|
}
|
|
|
|
|
|
function dragended(event, d) {
|
|
|
if (!event.active) simulation.alphaTarget(0);
|
|
|
d.fx = null;
|
|
|
d.fy = null;
|
|
|
}
|
|
|
|
|
|
return d3.drag()
|
|
|
.on("start", dragstarted)
|
|
|
.on("drag", dragged)
|
|
|
.on("end", dragended);
|
|
|
}
|
|
|
|
|
|
// 工具提示
|
|
|
const tooltip = d3.select("#tooltip");
|
|
|
|
|
|
function showTooltip(event, d) {
|
|
|
tooltip
|
|
|
.style("opacity", 1)
|
|
|
.html(`<strong>${d.id}</strong><br>${d.type}<br>${d.desc}`)
|
|
|
.style("left", (event.pageX + 10) + "px")
|
|
|
.style("top", (event.pageY - 10) + "px");
|
|
|
}
|
|
|
|
|
|
function hideTooltip() {
|
|
|
tooltip.style("opacity", 0);
|
|
|
}
|
|
|
|
|
|
// 更新右侧面板
|
|
|
function updatePanel(event, d) {
|
|
|
// 更新人物信息
|
|
|
document.getElementById("person-info").innerHTML = `
|
|
|
<h3>${d.id}</h3>
|
|
|
<p><strong>类型:</strong> ${d.type}</p>
|
|
|
<p><strong>描述:</strong> ${d.desc}</p>
|
|
|
<p><strong>重要性:</strong> ${d.rank}/10</p>
|
|
|
`;
|
|
|
|
|
|
// 更新关系列表
|
|
|
const relations = data.links.filter(
|
|
|
link => link.source.id === d.id || link.target.id === d.id
|
|
|
);
|
|
|
|
|
|
let relationsHtml = "";
|
|
|
relations.forEach(rel => {
|
|
|
const otherNode = rel.source.id === d.id ? rel.target : rel.source;
|
|
|
const relationType =
|
|
|
rel.type === "military" ? "军事关系" :
|
|
|
rel.type === "political" ? "政治关系" : "家族关系";
|
|
|
|
|
|
relationsHtml += `
|
|
|
<div class="relation-item">
|
|
|
<strong>${otherNode}</strong>
|
|
|
<p>关系类型: ${relationType}</p>
|
|
|
<p>${rel.desc}</p>
|
|
|
</div>
|
|
|
`;
|
|
|
});
|
|
|
|
|
|
document.getElementById("relations-list").innerHTML = relationsHtml || "<p>无记录的关系</p>";
|
|
|
}
|
|
|
|
|
|
// 布局切换
|
|
|
function changeLayout(type) {
|
|
|
simulation.stop();
|
|
|
|
|
|
switch(type) {
|
|
|
case "force":
|
|
|
simulation
|
|
|
.force("x", d3.forceX(width / 2).strength(0.05))
|
|
|
.force("y", d3.forceY(height / 2).strength(0.05))
|
|
|
.force("charge", d3.forceManyBody().strength(-300))
|
|
|
.alpha(1).restart();
|
|
|
break;
|
|
|
|
|
|
case "radial":
|
|
|
simulation
|
|
|
.force("x", null)
|
|
|
.force("y", null)
|
|
|
.force("radial", d3.forceRadial(height / 3, width / 2, height / 2).strength(0.1))
|
|
|
.force("charge", d3.forceManyBody().strength(-500))
|
|
|
.alpha(1).restart();
|
|
|
break;
|
|
|
|
|
|
case "circular":
|
|
|
simulation
|
|
|
.force("x", null)
|
|
|
.force("y", null)
|
|
|
.force("charge", null)
|
|
|
.force("circle", forceCircle())
|
|
|
.alpha(1).restart();
|
|
|
break;
|
|
|
|
|
|
case "grid":
|
|
|
simulation
|
|
|
.force("x", d3.forceX(d => (data.nodes.indexOf(d) % 5) * 150 + 100).strength(1))
|
|
|
.force("y", d3.forceY(d => Math.floor(data.nodes.indexOf(d) / 5) * 150 + 100).strength(1))
|
|
|
.force("charge", d3.forceManyBody().strength(-1000))
|
|
|
.alpha(1).restart();
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 圆形布局力
|
|
|
function forceCircle() {
|
|
|
const radius = Math.min(width, height) / 3;
|
|
|
let nodes;
|
|
|
|
|
|
function force(alpha) {
|
|
|
const centerX = width / 2;
|
|
|
const centerY = height / 2;
|
|
|
|
|
|
nodes.forEach((d, i) => {
|
|
|
const angle = (i * 2 * Math.PI) / nodes.length;
|
|
|
d.x = centerX + radius * Math.cos(angle);
|
|
|
d.y = centerY + radius * Math.sin(angle);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
force.initialize = _ => nodes = _;
|
|
|
return force;
|
|
|
}
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|