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.

708 lines
24 KiB

<!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, #0a1f44, #1e3d7a);
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(66, 165, 245, 0.5), rgba(0,0,0,0));
margin-bottom: 20px;
}
#title {
font-size: 36px;
font-weight: bold;
background: linear-gradient(90deg, #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 {
flex: 3;
height: 100%;
}
#panel {
flex: 1;
padding: 20px;
background: rgba(15, 30, 60, 0.8);
border-left: 1px solid #2a4a7a;
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;
}
.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: 50%;
}
.tooltip {
position: absolute;
padding: 10px;
background: rgba(0, 0, 0, 0.8);
color: white;
border-radius: 5px;
pointer-events: none;
max-width: 300px;
font-size: 14px;
z-index: 10;
}
.panel-title {
font-size: 24px;
margin-bottom: 20px;
color: #4fc3f7;
border-bottom: 1px solid #2a4a7a;
padding-bottom: 10px;
}
.panel-content {
margin-bottom: 20px;
}
.panel-section {
margin-bottom: 15px;
}
.panel-section-title {
font-size: 18px;
color: #81d4fa;
margin-bottom: 5px;
}
.layout-controls {
margin-top: 20px;
}
.layout-btn {
background: #2a4a7a;
color: white;
border: none;
padding: 8px 12px;
margin-right: 5px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.layout-btn:hover {
background: #3a5a8a;
}
.layout-btn.active {
background: #4fc3f7;
color: #0a1f44;
}
</style>
</head>
<body>
<div id="header">
<div id="title">刘邦集团人物关系网络图</div>
</div>
<div id="container">
<div id="graph"></div>
<div id="panel">
<div class="panel-title">节点信息</div>
<div id="node-info" class="panel-content">
<div class="panel-section">
<div class="panel-section-title">请点击节点查看详情</div>
</div>
</div>
<div class="panel-section">
<div class="panel-section-title">关系类型</div>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #ff5252;"></div>
<div>敌对关系</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #4caf50;"></div>
<div>合作关系</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ffeb3b;"></div>
<div>亲属关系</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #9c27b0;"></div>
<div>上下级关系</div>
</div>
</div>
</div>
<div class="layout-controls">
<div class="panel-section-title">布局切换</div>
<button class="layout-btn active" data-layout="force">力导向</button>
<button class="layout-btn" data-layout="radial">辐射状</button>
<button class="layout-btn" data-layout="circular">环形</button>
<button class="layout-btn" data-layout="grid">网格</button>
</div>
</div>
</div>
<div class="tooltip"></div>
<script>
// Data definition
const data = {
"nodes": [
{
"id": "1",
"name": "刘邦",
"type": "帝王",
"desc": "汉朝开国皇帝,从平民起义到建立汉朝",
"importance": 10,
"group": 1
},
{
"id": "2",
"name": "吕后",
"type": "皇后",
"desc": "刘邦的妻子,汉朝第一位皇后,后期掌握大权",
"importance": 9,
"group": 1
},
{
"id": "3",
"name": "项羽",
"type": "诸侯",
"desc": "楚汉相争中刘邦的主要对手",
"importance": 9,
"group": 2
},
{
"id": "4",
"name": "韩信",
"type": "将军",
"desc": "汉初三大名将之一,为刘邦立下赫赫战功",
"importance": 8,
"group": 1
},
{
"id": "5",
"name": "张良",
"type": "谋士",
"desc": "刘邦的重要谋臣,汉初三杰之一",
"importance": 8,
"group": 1
},
{
"id": "6",
"name": "萧何",
"type": "丞相",
"desc": "刘邦的重要助手,汉初三杰之一",
"importance": 8,
"group": 1
},
{
"id": "7",
"name": "曹参",
"type": "将军",
"desc": "刘邦的重要将领,后接替萧何为相",
"importance": 7,
"group": 1
},
{
"id": "8",
"name": "英布",
"type": "诸侯",
"desc": "初为项羽部将,后归刘邦,最终反叛",
"importance": 7,
"group": 1
},
{
"id": "9",
"name": "彭越",
"type": "将军",
"desc": "刘邦的重要将领,后因谋反罪名被杀",
"importance": 7,
"group": 1
},
{
"id": "10",
"name": "项伯",
"type": "谋士",
"desc": "项羽的叔父,多次帮助刘邦",
"importance": 6,
"group": 2
},
{
"id": "11",
"name": "范增",
"type": "谋士",
"desc": "项羽的主要谋臣,被离间计陷害",
"importance": 6,
"group": 2
},
{
"id": "12",
"name": "戚夫人",
"type": "妃子",
"desc": "刘邦宠妃,与吕后争权失败被做成人彘",
"importance": 5,
"group": 1
},
{
"id": "13",
"name": "刘盈",
"type": "太子",
"desc": "刘邦与吕后之子,汉惠帝",
"importance": 5,
"group": 1
},
{
"id": "14",
"name": "刘如意",
"type": "诸侯",
"desc": "刘邦与戚夫人之子,赵王",
"importance": 5,
"group": 1
},
{
"id": "15",
"name": "审食其",
"type": "大臣",
"desc": "刘邦同乡,吕后信任的人",
"importance": 4,
"group": 1
},
{
"id": "16",
"name": "周勃",
"type": "将军",
"desc": "刘邦同乡,开国功臣",
"importance": 4,
"group": 1
}
],
"links": [
{
"source": "1",
"target": "2",
"type": "亲属关系",
"desc": "夫妻关系,后期关系紧张"
},
{
"source": "1",
"target": "3",
"type": "敌对关系",
"desc": "楚汉相争的主要对手"
},
{
"source": "1",
"target": "4",
"type": "上下级关系",
"desc": "刘邦重用韩信又猜忌他"
},
{
"source": "1",
"target": "5",
"type": "合作关系",
"desc": "张良是刘邦最重要的谋士"
},
{
"source": "1",
"target": "6",
"type": "合作关系",
"desc": "萧何是刘邦最重要的助手"
},
{
"source": "1",
"target": "7",
"type": "上下级关系",
"desc": "曹参是刘邦的重要将领"
},
{
"source": "1",
"target": "8",
"type": "上下级关系",
"desc": "英布先归顺后反叛刘邦"
},
{
"source": "1",
"target": "9",
"type": "上下级关系",
"desc": "彭越为刘邦立下战功后被处死"
},
{
"source": "1",
"target": "10",
"type": "合作关系",
"desc": "项伯多次在关键时刻帮助刘邦"
},
{
"source": "1",
"target": "12",
"type": "亲属关系",
"desc": "刘邦宠妃,引发与吕后的矛盾"
},
{
"source": "1",
"target": "13",
"type": "亲属关系",
"desc": "刘邦与吕后之子"
},
{
"source": "1",
"target": "14",
"type": "亲属关系",
"desc": "刘邦与戚夫人之子"
},
{
"source": "1",
"target": "15",
"type": "上下级关系",
"desc": "审食其是刘邦同乡,受吕后信任"
},
{
"source": "1",
"target": "16",
"type": "上下级关系",
"desc": "周勃是刘邦同乡,开国功臣"
},
{
"source": "2",
"target": "12",
"type": "敌对关系",
"desc": "吕后与戚夫人的权力斗争"
},
{
"source": "2",
"target": "14",
"type": "敌对关系",
"desc": "吕后毒杀刘如意"
},
{
"source": "2",
"target": "15",
"type": "合作关系",
"desc": "审食其是吕后最信任的人"
},
{
"source": "3",
"target": "10",
"type": "亲属关系",
"desc": "项羽与项伯是叔侄关系"
},
{
"source": "3",
"target": "11",
"type": "上下级关系",
"desc": "范增是项羽的主要谋士"
},
{
"source": "4",
"target": "6",
"type": "合作关系",
"desc": "萧何月下追韩信"
},
{
"source": "5",
"target": "10",
"type": "合作关系",
"desc": "张良与项伯有交情"
},
{
"source": "6",
"target": "7",
"type": "合作关系",
"desc": "萧何与曹参关系复杂"
},
{
"source": "8",
"target": "3",
"type": "上下级关系",
"desc": "英布曾是项羽部将"
}
]
};
// Color scale for different types
const color = d3.scaleOrdinal()
.domain(["帝王", "皇后", "诸侯", "将军", "谋士", "丞相", "妃子", "太子", "大臣"])
.range(["#e53935", "#8e24aa", "#3949ab", "#039be5", "#00897b", "#43a047", "#f4511e", "#fb8c00", "#6d4c41"]);
// Relationship type colors
const linkColor = d3.scaleOrdinal()
.domain(["敌对关系", "合作关系", "亲属关系", "上下级关系"])
.range(["#ff5252", "#4caf50", "#ffeb3b", "#9c27b0"]);
// Graph dimensions
const width = document.getElementById('graph').clientWidth;
const height = document.getElementById('graph').clientHeight;
// Create SVG
const svg = d3.select("#graph")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform);
}))
.append("g");
// Tooltip
const tooltip = d3.select(".tooltip");
// Simulation
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("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(50));
// 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;
}
// Create links
const link = svg.append("g")
.selectAll("line")
.data(data.links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", d => linkColor(d.type))
.attr("stroke-width", 2);
// Create nodes
const node = svg.append("g")
.selectAll("circle")
.data(data.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", d => 10 + d.importance * 2)
.attr("fill", d => color(d.type))
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseover", function(event, d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(`<strong>${d.name}</strong><br/>${d.type}<br/><br/>${d.desc}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function() {
tooltip.transition()
.duration(500)
.style("opacity", 0);
})
.on("click", function(event, d) {
updatePanel(d);
});
// Add labels
const labels = svg.append("g")
.selectAll("text")
.data(data.nodes)
.enter().append("text")
.attr("dy", -15)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("fill", "white")
.style("font-size", "12px")
.text(d => d.name);
// Update panel info
function updatePanel(d) {
const panel = d3.select("#node-info");
panel.html(`
<div class="panel-section">
<div class="panel-section-title">${d.name}</div>
<div>类型: ${d.type}</div>
<div>重要性: ${d.importance}/10</div>
</div>
<div class="panel-section">
<div class="panel-section-title">描述</div>
<div>${d.desc}</div>
</div>
<div class="panel-section">
<div class="panel-section-title">关系</div>
<div>${getRelationships(d.id)}</div>
</div>
`);
}
// Get relationships for a node
function getRelationships(nodeId) {
let relationships = [];
// Find links where this node is source or target
data.links.forEach(link => {
if (link.source.id === nodeId || link.source === nodeId) {
const targetNode = data.nodes.find(n => n.id === link.target || n.id === link.target.id);
relationships.push({
name: targetNode.name,
type: link.type,
desc: link.desc,
direction: "out"
});
}
if (link.target.id === nodeId || link.target === nodeId) {
const sourceNode = data.nodes.find(n => n.id === link.source || n.id === link.source.id);
relationships.push({
name: sourceNode.name,
type: link.type,
desc: link.desc,
direction: "in"
});
}
});
// Format the relationships for display
let html = '<ul style="padding-left: 20px;">';
relationships.forEach(r => {
const arrow = r.direction === "out" ? "→" : "←";
html += `<li><strong>${r.name}</strong> ${arrow} <span style="color: ${linkColor(r.type)}">${r.type}</span><br/><small>${r.desc}</small></li>`;
});
html += '</ul>';
return html;
}
// Update positions
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);
labels
.attr("x", d => d.x)
.attr("y", d => d.y);
});
// Layout controls
d3.selectAll(".layout-btn").on("click", function() {
d3.selectAll(".layout-btn").classed("active", false);
d3.select(this).classed("active", true);
const layout = d3.select(this).attr("data-layout");
changeLayout(layout);
});
// Change layout
function changeLayout(layout) {
switch(layout) {
case "force":
simulation
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.alphaTarget(0.3)
.restart();
break;
case "radial":
simulation
.force("radial", d3.forceRadial(width / 3, width / 2, height / 2).strength(0.1))
.force("center", null)
.alphaTarget(0.3)
.restart();
break;
case "circular":
const radius = Math.min(width, height) / 2 - 100;
const angle = 2 * Math.PI / data.nodes.length;
data.nodes.forEach((d, i) => {
d.fx = width / 2 + radius * Math.cos(i * angle);
d.fy = height / 2 + radius * Math.sin(i * angle);
});
simulation
.force("radial", null)
.force("center", null)
.alphaTarget(0)
.restart();
break;
case "grid":
const cols = Math.ceil(Math.sqrt(data.nodes.length));
const cellSize = Math.min(width, height) / cols;
data.nodes.forEach((d, i) => {
const row = Math.floor(i / cols);
const col = i % cols;
d.fx = col * cellSize + cellSize / 2;
d.fy = row * cellSize + cellSize / 2;
});
simulation
.force("radial", null)
.force("center", null)
.alphaTarget(0)
.restart();
break;
}
}
// Initial layout
changeLayout("force");
</script>
</body>
</html>