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.

330 lines
13 KiB

```html
<!DOCTYPE html>
<html>
<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, #0a192f, #172a45);
font-family: "SimSun", serif;
color: #e6f1ff;
overflow: hidden;
}
#container {
display: flex;
height: 100vh;
}
#graph {
flex: 1;
position: relative;
}
#panel {
width: 300px;
background: rgba(10, 25, 47, 0.8);
border-left: 1px solid #64ffda;
padding: 20px;
overflow-y: auto;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
filter: drop-shadow(0 0 5px rgba(100, 255, 218, 0.5));
}
.link {
stroke: #495670;
stroke-opacity: 0.6;
}
.legend {
margin-bottom: 15px;
padding: 10px;
background: rgba(23, 42, 69, 0.5);
border-radius: 5px;
}
.legend-item {
display: flex;
align-items: center;
margin: 5px 0;
}
.legend-color {
width: 15px;
height: 15px;
margin-right: 10px;
border-radius: 3px;
}
.panel-title {
color: #64ffda;
border-bottom: 1px solid #64ffda;
padding-bottom: 10px;
margin-bottom: 15px;
}
.toggle-btn {
position: absolute;
top: 20px;
right: 20px;
background: rgba(100, 255, 218, 0.2);
color: #64ffda;
border: 1px solid #64ffda;
padding: 5px 10px;
cursor: pointer;
z-index: 100;
}
</style>
</head>
<body>
<div id="container">
<div id="graph"></div>
<div id="panel">
<h2 class="panel-title">人物详情</h2>
<div id="info-content">
<p>点击节点查看人物详情</p>
</div>
<div class="legend">
<h3>关系类型图例</h3>
<div class="legend-item">
<div class="legend-color" style="background-color: #ff5555;"></div>
<span>敌对关系</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #55ff55;"></div>
<span>联盟关系</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #5555ff;"></div>
<span>君臣关系</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ffff55;"></div>
<span>家族关系</span>
</div>
</div>
<div class="legend">
<h3>布局切换</h3>
<button onclick="changeLayout('force')">力导向布局</button>
<button onclick="changeLayout('radial')">辐射布局</button>
<button onclick="changeLayout('hierarchical')">层级布局</button>
<button onclick="changeLayout('circular')">环形布局</button>
</div>
</div>
</div>
<script>
const width = document.getElementById('graph').clientWidth;
const height = document.getElementById('graph').clientHeight;
const data = {
nodes: [
{ id: "刘邦", group: 1, size: 30, desc: "汉高祖刘邦,汉朝开国皇帝" },
{ id: "吕后", group: 1, size: 25, desc: "刘邦妻子,汉朝第一位皇后" },
{ id: "项羽", group: 2, size: 28, desc: "西楚霸王,刘邦主要对手" },
{ id: "韩信", group: 3, size: 22, desc: "汉初三杰之一,著名军事家" },
{ id: "萧何", group: 3, size: 22, desc: "汉初三杰之一,政治家" },
{ id: "张良", group: 3, size: 22, desc: "汉初三杰之一,谋略家" },
{ id: "项伯", group: 2, size: 18, desc: "项羽叔父,曾帮助刘邦" },
{ id: "曹无伤", group: 2, size: 15, desc: "刘邦部下,背叛刘邦" },
{ id: "项庄", group: 2, size: 16, desc: "项羽堂弟,鸿门宴舞剑" },
{ id: "叔孙通", group: 3, size: 18, desc: "制定汉朝礼仪制度" },
{ id: "刘姓诸侯", group: 4, size: 20, desc: "刘邦分封的同姓诸侯" },
{ id: "吕氏家族", group: 4, size: 20, desc: "吕后家族成员" }
],
links: [
{ source: "刘邦", target: "吕后", value: 3, type: "family" },
{ source: "刘邦", target: "项羽", value: 5, type: "enemy" },
{ source: "刘邦", target: "韩信", value: 4, type: "loyalty" },
{ source: "刘邦", target: "萧何", value: 4, type: "loyalty" },
{ source: "刘邦", target: "张良", value: 4, type: "loyalty" },
{ source: "刘邦", target: "项伯", value: 2, type: "alliance" },
{ source: "刘邦", target: "曹无伤", value: 1, type: "enemy" },
{ source: "刘邦", target: "叔孙通", value: 2, type: "loyalty" },
{ source: "刘邦", target: "刘姓诸侯", value: 3, type: "family" },
{ source: "吕后", target: "吕氏家族", value: 4, type: "family" },
{ source: "项羽", target: "项伯", value: 3, type: "family" },
{ source: "项羽", target: "项庄", value: 3, type: "family" },
{ source: "项羽", target: "曹无伤", value: 2, type: "alliance" },
{ source: "吕后", target: "韩信", value: 1, type: "enemy" },
{ source: "刘姓诸侯", target: "吕氏家族", value: 2, type: "enemy" }
]
};
const color = d3.scaleOrdinal()
.domain(["enemy", "alliance", "loyalty", "family"])
.range(["#ff5555", "#55ff55", "#5555ff", "#ffff55"]);
const svg = d3.select("#graph")
.append("svg")
.attr("width", width)
.attr("height", height);
const simulation = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.links).id(d => d.id).distance(d => d.value * 30))
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(d => d.size));
const link = svg.append("g")
.selectAll("line")
.data(data.links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", d => color(d.type))
.attr("stroke-width", d => Math.sqrt(d.value));
const node = svg.append("g")
.selectAll("circle")
.data(data.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", d => d.size)
.attr("fill", d => {
if (d.id === "刘邦") return "#ff6600";
if (d.group === 1) return "#33cc33";
if (d.group === 2) return "#cc3333";
if (d.group === 3) return "#3366cc";
return "#cccc33";
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseover", function(event, d) {
d3.select(this).attr("stroke", "#fff").attr("stroke-width", 2);
showInfoPanel(d);
})
.on("mouseout", function() {
d3.select(this).attr("stroke", null);
})
.on("click", function(event, d) {
showInfoPanel(d);
});
const label = svg.append("g")
.selectAll("text")
.data(data.nodes)
.enter().append("text")
.attr("dy", -15)
.attr("text-anchor", "middle")
.text(d => d.id)
.attr("fill", "#e6f1ff");
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);
label
.attr("x", d => d.x)
.attr("y", d => d.y);
});
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;
}
function showInfoPanel(d) {
const panel = document.getElementById('info-content');
panel.innerHTML = `
<h3>${d.id}</h3>
<p>${d.desc}</p>
<p><strong>重要性:</strong> ${d.size/5}星</p>
<p><strong>所属集团:</strong> ${getGroupName(d.group)}</p>
<h4>主要关系:</h4>
<ul>
${getRelationships(d.id)}
</ul>
`;
}
function getGroupName(group) {
switch(group) {
case 1: return "刘邦核心集团";
case 2: return "项羽集团";
case 3: return "刘邦功臣集团";
case 4: return "家族势力";
default: return "其他";
}
}
function getRelationships(id) {
let relationships = [];
data.links.forEach(link => {
if (link.source.id === id) {
relationships.push(`<li>与 <strong>${link.target.id}</strong> 的 ${getRelationshipType(link.type)}关系</li>`);
} else if (link.target.id === id) {
relationships.push(`<li>与 <strong>${link.source.id}</strong> 的 ${getRelationshipType(link.type)}关系</li>`);
}
});
return relationships.join('');
}
function getRelationshipType(type) {
switch(type) {
case "enemy": return "敌对";
case "alliance": return "联盟";
case "loyalty": return "君臣";
case "family": return "家族";
default: return "其他";
}
}
function changeLayout(type) {
simulation.force("link", d3.forceLink(data.links).id(d => d.id).distance(d => d.value * 30));
if (type === 'force') {
simulation.force("charge", d3.forceManyBody().strength(-500));
simulation.force("center", d3.forceCenter(width / 2, height / 2));
} else if (type === 'radial') {
simulation.force("radial", d3.forceRadial(height / 3, width / 2, height / 2).strength(0.1));
} else if (type === 'hierarchical') {
simulation.force("x", d3.forceX(width / 2).strength(0.1));
simulation.force("y", d3.forceY().y(d => {
if (d.group === 1) return height / 4;
if (d.group === 2) return height / 4 * 3;
return height / 2;
}).strength(0.2));
} else if (type === 'circular') {
simulation.force("x", null);
simulation.force("y", null);
simulation.force("radial", null);
data.nodes.forEach((d, i) => {
const angle = (i / data.nodes.length) * 2 * Math.PI;
d.x = width / 2 + Math.cos(angle) * width / 3;
d.y = height / 2 + Math.sin(angle) * height / 3;
});
}
simulation.alpha(0.3).restart();
}
window.addEventListener('resize', function() {
const newWidth = document.getElementById('graph').clientWidth;
const newHeight = document.getElementById('graph').clientHeight;
svg.attr("width", newWidth).attr("height", newHeight);
simulation.force("center", d3.forceCenter(newWidth / 2, newHeight / 2));
simulation.alpha(0.3).restart();
});
</script>
</body>
</html>
```