You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

403 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>三国人物关系图谱</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: linear-gradient(135deg, #0a1a3a, #1a3a7a);
font-family: 'Arial', sans-serif;
color: white;
overflow: hidden;
}
#header {
text-align: center;
padding: 20px 0;
background: linear-gradient(90deg, rgba(0,0,0,0), rgba(10,80,150,0.8), rgba(0,0,0,0));
margin-bottom: 20px;
}
#title {
font-size: 36px;
font-weight: bold;
background: linear-gradient(to right, #ff8a00, #e52e71);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 10px rgba(255,255,255,0.3);
}
#container {
display: flex;
height: calc(100vh - 100px);
}
#graph-container {
flex: 3;
position: relative;
}
#info-panel {
flex: 1;
background: rgba(0,0,0,0.5);
padding: 15px;
border-left: 1px solid rgba(255,255,255,0.1);
overflow-y: auto;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
filter: drop-shadow(0 0 5px rgba(255,255,255,0.5));
}
.link {
stroke-opacity: 0.6;
}
.node text {
dominant-baseline: central;
text-anchor: middle;
font-size: 10px;
fill: white;
pointer-events: none;
}
.legend {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 5px;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.legend-color {
width: 15px;
height: 15px;
margin-right: 8px;
border-radius: 3px;
}
#tooltip {
position: absolute;
padding: 10px;
background: rgba(0,0,0,0.8);
border: 1px solid #444;
border-radius: 5px;
pointer-events: none;
display: none;
max-width: 200px;
}
.layout-btn {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.2);
color: white;
padding: 5px 10px;
cursor: pointer;
border-radius: 3px;
}
.layout-btn:hover {
background: rgba(0,0,0,0.7);
}
</style>
</head>
<body>
<div id="header">
<h1 id="title">三国人物关系图谱</h1>
</div>
<div id="container">
<div id="graph-container">
<div class="layout-btn" id="force-layout">力导向布局</div>
<div class="layout-btn" id="radial-layout" style="top: 40px;">辐射状布局</div>
<div class="layout-btn" id="circular-layout" style="top: 70px;">环形布局</div>
<div class="layout-btn" id="grid-layout" style="top: 100px;">网格布局</div>
<svg id="graph"></svg>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #ff7f0e;"></div>
<span>君臣关系</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #1f77b4;"></div>
<span>联盟关系</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #2ca02c;"></div>
<span>敌对关系</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #d62728;"></div>
<span>家族关系</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #9467bd;"></div>
<span>师徒关系</span>
</div>
</div>
<div id="tooltip"></div>
</div>
<div id="info-panel">
<h2 id="selected-name">选择节点查看详情</h2>
<div id="selected-desc"></div>
<h3>关系列表</h3>
<div id="relations-list"></div>
</div>
</div>
<script>
// Define data
const nodes = [
{ id: "曹操", name: "曹操", type: "君主", desc: "魏武帝,三国时期魏国奠基人", rank: 100 },
{ id: "刘备", name: "刘备", type: "君主", desc: "汉昭烈帝,蜀汉开国皇帝", rank: 90 },
{ id: "孙权", name: "孙权", type: "君主", desc: "吴大帝,东吴开国皇帝", rank: 85 },
{ id: "诸葛亮", name: "诸葛亮", type: "谋士", desc: "蜀汉丞相,杰出的政治家、军事家", rank: 95 },
{ id: "周瑜", name: "周瑜", type: "将领", desc: "东吴名将,赤壁之战主要指挥者", rank: 80 },
{ id: "关羽", name: "关羽", type: "将领", desc: "蜀汉名将,被后世尊为武圣", rank: 85 },
{ id: "张飞", name: "张飞", type: "将领", desc: "蜀汉名将,与关羽并称万人敌", rank: 75 },
{ id: "赵云", name: "赵云", type: "将领", desc: "蜀汉名将,被誉为常胜将军", rank: 70 },
{ id: "司马懿", name: "司马懿", type: "谋士", desc: "魏国重臣,西晋王朝奠基人", rank: 85 },
{ id: "吕布", name: "吕布", type: "将领", desc: "猛将,有"人中吕布马中赤兔"之称", rank: 65 },
{ id: "貂蝉", name: "貂蝉", type: "人物", desc: "东汉末年美女,传说参与了连环计", rank: 50 },
{ id: "袁绍", name: "袁绍", type: "君主", desc: "东汉末年军阀,曾为北方最强大势力", rank: 70 },
{ id: "袁术", name: "袁术", type: "君主", desc: "袁绍之弟,自称仲家皇帝", rank: 60 },
{ id: "孙策", name: "孙策", type: "君主", desc: "孙权之兄,东吴奠基人", rank: 75 },
{ id: "荀彧", name: "荀彧", type: "谋士", desc: "曹操重要谋士,被称为"王佐之才"", rank: 75 },
{ id: "郭嘉", name: "郭嘉", type: "谋士", desc: "曹操重要谋士,有"鬼才"之称", rank: 70 },
{ id: "鲁肃", name: "鲁肃", type: "谋士", desc: "东吴战略家,促成孙刘联盟", rank: 65 },
{ id: "黄盖", name: "黄盖", type: "将领", desc: "东吴老将,赤壁之战献苦肉计", rank: 60 },
{ id: "马超", name: "马超", type: "将领", desc: "蜀汉名将,西凉军阀马腾之子", rank: 70 },
{ id: "黄忠", name: "黄忠", type: "将领", desc: "蜀汉老将,定军山之战斩杀夏侯渊", rank: 65 }
];
const links = [
{ source: "曹操", target: "荀彧", type: "君臣关系", desc: "主要谋士" },
{ source: "曹操", target: "郭嘉", type: "君臣关系", desc: "重要谋士" },
{ source: "曹操", target: "司马懿", type: "君臣关系", desc: "后期重要谋士" },
{ source: "刘备", target: "诸葛亮", type: "君臣关系", desc: "三顾茅庐" },
{ source: "刘备", target: "关羽", type: "君臣关系", desc: "结义兄弟" },
{ source: "刘备", target: "张飞", type: "君臣关系", desc: "结义兄弟" },
{ source: "刘备", target: "赵云", type: "君臣关系", desc: "主要将领" },
{ source: "孙权", target: "周瑜", type: "君臣关系", desc: "主要将领" },
{ source: "孙权", target: "鲁肃", type: "君臣关系", desc: "重要谋士" },
{ source: "孙权", target: "黄盖", type: "君臣关系", desc: "老将" },
{ source: "诸葛亮", target: "赵云", type: "合作关系", desc: "多次合作" },
{ source: "诸葛亮", target: "周瑜", type: "敌对关系", desc: "赤壁之战" },
{ source: "刘备", target: "孙权", type: "联盟关系", desc: "孙刘联盟" },
{ source: "曹操", target: "刘备", type: "敌对关系", desc: "汉中之战" },
{ source: "曹操", target: "孙权", type: "敌对关系", desc: "赤壁之战" },
{ source: "孙权", target: "孙策", type: "家族关系", desc: "兄弟" },
{ source: "袁绍", target: "袁术", type: "家族关系", desc: "兄弟" },
{ source: "吕布", target: "貂蝉", type: "亲属关系", desc: "夫妻" },
{ source: "吕布", target: "曹操", type: "敌对关系", desc: "被曹操所杀" },
{ source: "关羽", target: "曹操", type: "合作关系", desc: "曾为曹操效力" },
{ source: "周瑜", target: "黄盖", type: "君臣关系", desc: "部下" },
{ source: "刘备", target: "黄忠", type: "君臣关系", desc: "老将" },
{ source: "刘备", target: "马超", type: "君臣关系", desc: "名将" },
{ source: "诸葛亮", target: "司马懿", type: "敌对关系", desc: "北伐对手" }
];
// Color scale for relationship types
const colorScale = d3.scaleOrdinal()
.domain(["君臣关系", "联盟关系", "敌对关系", "家族关系", "师徒关系"])
.range(["#ff7f0e", "#1f77b4", "#2ca02c", "#d62728", "#9467bd"]);
// Set up SVG
const svg = d3.select("#graph")
.attr("width", "100%")
.attr("height", "100%");
// Create a group for zoom/pan
const g = svg.append("g");
// Set up zoom behavior
const zoom = d3.zoom()
.scaleExtent([0.1, 8])
.on("zoom", (event) => {
g.attr("transform", event.transform);
});
svg.call(zoom);
// Set up force simulation
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(window.innerWidth / 3, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(d => d.rank / 2 + 20));
// Draw links
const link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", d => colorScale(d.type))
.attr("stroke-width", 2);
// Draw nodes
const node = g.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(nodes)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Add 3D effect circles
node.append("circle")
.attr("class", "node")
.attr("r", d => d.rank / 3 + 10)
.attr("fill", d => {
if (d.type === "君主") return "rgba(200,50,50,0.8)";
if (d.type === "将领") return "rgba(50,50,200,0.8)";
if (d.type === "谋士") return "rgba(50,200,50,0.8)";
return "rgba(150,150,150,0.8)";
})
.on("mouseover", function(event, d) {
d3.select(this).attr("stroke", "gold").attr("stroke-width", 3);
// Show tooltip
const tooltip = d3.select("#tooltip");
tooltip.style("display", "block")
.html(`<strong>${d.name}</strong><br>${d.type}<br>${d.desc}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function() {
d3.select(this).attr("stroke", "#fff").attr("stroke-width", 1.5);
d3.select("#tooltip").style("display", "none");
})
.on("click", function(event, d) {
// Update info panel
d3.select("#selected-name").text(d.name);
d3.select("#selected-desc").html(`<p><strong>类型:</strong> ${d.type}</p><p><strong>描述:</strong> ${d.desc}</p>`);
// Update relations list
const relations = links.filter(l => l.source === d.id || l.target === d.id);
let relationsHtml = "<ul>";
relations.forEach(r => {
const otherNode = r.source === d.id ? r.target : r.source;
relationsHtml += `<li><strong>${otherNode}</strong>: ${r.type} (${r.desc})</li>`;
});
relationsHtml += "</ul>";
d3.select("#relations-list").html(relationsHtml);
});
// Add text labels
node.append("text")
.attr("dy", 4)
.text(d => d.name)
.attr("font-size", d => Math.min(14, Math.max(8, d.rank / 8)));
// Update positions on tick
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("transform", d => `translate(${d.x},${d.y})`);
});
// Drag functions
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;
}
// Layout switching
d3.select("#force-layout").on("click", () => {
simulation.force("center", d3.forceCenter(window.innerWidth / 3, window.innerHeight / 2))
.force("charge", d3.forceManyBody().strength(-500))
.alphaTarget(0.3)
.restart();
});
d3.select("#radial-layout").on("click", () => {
simulation.force("center", null)
.force("radial", d3.forceRadial(window.innerHeight / 3, window.innerWidth / 3, window.innerHeight / 2).strength(0.1))
.alphaTarget(0.3)
.restart();
});
d3.select("#circular-layout").on("click", () => {
simulation.force("center", d3.forceCenter(window.innerWidth / 3, window.innerHeight / 2))
.force("radial", null)
.force("charge", d3.forceManyBody().strength(-1000))
.force("x", d3.forceX().strength(0.1))
.force("y", d3.forceY().strength(0.1))
.alphaTarget(0.3)
.restart();
});
d3.select("#grid-layout").on("click", () => {
const cols = 4;
const rows = Math.ceil(nodes.length / cols);
const cellWidth = window.innerWidth / 3 / cols;
const cellHeight = window.innerHeight / rows;
nodes.forEach((d, i) => {
d.fx = (i % cols) * cellWidth + cellWidth / 2;
d.fy = Math.floor(i / cols) * cellHeight + cellHeight / 2;
});
simulation.alphaTarget(0).restart();
setTimeout(() => {
nodes.forEach(d => {
d.fx = null;
d.fy = null;
});
}, 1000);
});
// Handle window resize
window.addEventListener("resize", function() {
svg.attr("width", window.innerWidth * 0.75)
.attr("height", window.innerHeight - 100);
simulation.force("center", d3.forceCenter(window.innerWidth / 3, window.innerHeight / 2))
.restart();
});
</script>
</body>
</html>