parent
29e5ed8b09
commit
086a8e627d
Binary file not shown.
@ -0,0 +1,374 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>秦汉时期人物关系图谱</title>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #0a1a3a, #1a3a6a);
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#title {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(90deg, #ff8a00, #e52e71);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-shadow: 0 0 10px rgba(255,255,255,0.3);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#graph-container {
|
||||
position: absolute;
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#info-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 30%;
|
||||
height: 100%;
|
||||
background: rgba(10, 26, 58, 0.8);
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
border-left: 1px solid #2a4a7a;
|
||||
}
|
||||
|
||||
#legend {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: rgba(10, 26, 58, 0.8);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #2a4a7a;
|
||||
}
|
||||
|
||||
.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;
|
||||
fill: white;
|
||||
font-size: 10px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.node.selected {
|
||||
stroke: gold;
|
||||
stroke-width: 3px;
|
||||
filter: drop-shadow(0 0 10px gold);
|
||||
}
|
||||
|
||||
#tooltip {
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
pointer-events: none;
|
||||
font-size: 12px;
|
||||
max-width: 200px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-btn {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 20px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(42, 74, 122, 0.7);
|
||||
color: white;
|
||||
border: 1px solid #4a8aff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.layout-btn:hover {
|
||||
background: rgba(74, 138, 255, 0.7);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="title">秦汉时期人物关系图谱</div>
|
||||
<div id="graph-container"></div>
|
||||
<div id="info-panel">
|
||||
<h2 id="selected-node">选择节点查看详情</h2>
|
||||
<div id="node-details"></div>
|
||||
<h3>相关关系</h3>
|
||||
<div id="node-relations"></div>
|
||||
</div>
|
||||
<div id="legend">
|
||||
<div><span style="color: #ff7f0e;">■</span> 政治关系</div>
|
||||
<div><span style="color: #1f77b4;">■</span> 军事关系</div>
|
||||
<div><span style="color: #2ca02c;">■</span> 个人关系</div>
|
||||
</div>
|
||||
<div id="tooltip"></div>
|
||||
|
||||
<button class="layout-btn" id="force-layout">力导向布局</button>
|
||||
<button class="layout-btn" id="radial-layout" style="top: 120px;">辐射状布局</button>
|
||||
<button class="layout-btn" id="circular-layout" style="top: 160px;">环形布局</button>
|
||||
<button class="layout-btn" id="grid-layout" style="top: 200px;">网格布局</button>
|
||||
|
||||
<script>
|
||||
// 数据定义
|
||||
const graphData = {
|
||||
"nodes": [
|
||||
{"id": "刘邦", "type": "person", "desc": "汉高祖,汉朝开国皇帝", "rank": 5},
|
||||
{"id": "秦始皇", "type": "person", "desc": "中国第一个皇帝,统一六国", "rank": 5},
|
||||
{"id": "项羽", "type": "person", "desc": "西楚霸王,刘邦的主要对手", "rank": 4},
|
||||
{"id": "张良", "type": "person", "desc": "汉初三杰之一,刘邦的重要谋士", "rank": 4},
|
||||
{"id": "萧何", "type": "person", "desc": "汉初三杰之一,刘邦的丞相", "rank": 4},
|
||||
{"id": "韩信", "type": "person", "desc": "汉初三杰之一,著名军事家", "rank": 4},
|
||||
{"id": "吕后", "type": "person", "desc": "刘邦的皇后,汉朝第一位皇后", "rank": 3},
|
||||
{"id": "李斯", "type": "person", "desc": "秦朝丞相,协助秦始皇统一六国", "rank": 3},
|
||||
{"id": "蒙恬", "type": "person", "desc": "秦朝名将,修筑长城", "rank": 3},
|
||||
{"id": "扶苏", "type": "person", "desc": "秦始皇长子,被胡亥害死", "rank": 2},
|
||||
{"id": "胡亥", "type": "person", "desc": "秦二世,秦始皇幼子", "rank": 2},
|
||||
{"id": "吕不韦", "type": "person", "desc": "秦朝丞相,传说为秦始皇生父", "rank": 2},
|
||||
{"id": "陈胜", "type": "person", "desc": "秦末农民起义领袖", "rank": 2},
|
||||
{"id": "吴广", "type": "person", "desc": "秦末农民起义领袖", "rank": 2},
|
||||
{"id": "项梁", "type": "person", "desc": "项羽的叔父,反秦领袖", "rank": 2}
|
||||
],
|
||||
"links": [
|
||||
{"source": "刘邦", "target": "秦始皇", "type": "political", "desc": "刘邦的命运与秦始皇关于天子气的预言相关联"},
|
||||
{"source": "刘邦", "target": "项羽", "type": "military", "desc": "楚汉相争的主要对手"},
|
||||
{"source": "刘邦", "target": "张良", "type": "personal", "desc": "君臣关系,张良为刘邦重要谋士"},
|
||||
{"source": "刘邦", "target": "萧何", "type": "personal", "desc": "君臣关系,萧何为刘邦丞相"},
|
||||
{"source": "刘邦", "target": "韩信", "type": "personal", "desc": "君臣关系,后猜忌韩信"},
|
||||
{"source": "刘邦", "target": "吕后", "type": "personal", "desc": "夫妻关系"},
|
||||
{"source": "秦始皇", "target": "李斯", "type": "political", "desc": "君臣关系,李斯为秦朝丞相"},
|
||||
{"source": "秦始皇", "target": "蒙恬", "type": "military", "desc": "君臣关系,蒙恬为秦朝名将"},
|
||||
{"source": "秦始皇", "target": "扶苏", "type": "personal", "desc": "父子关系"},
|
||||
{"source": "秦始皇", "target": "胡亥", "type": "personal", "desc": "父子关系"},
|
||||
{"source": "秦始皇", "target": "吕不韦", "type": "personal", "desc": "传说为父子关系"},
|
||||
{"source": "项羽", "target": "项梁", "type": "personal", "desc": "叔侄关系"},
|
||||
{"source": "项羽", "target": "刘邦", "type": "military", "desc": "楚汉相争的主要对手"},
|
||||
{"source": "张良", "target": "秦始皇", "type": "political", "desc": "张良曾试图刺杀秦始皇"},
|
||||
{"source": "陈胜", "target": "吴广", "type": "military", "desc": "共同领导农民起义"},
|
||||
{"source": "陈胜", "target": "刘邦", "type": "political", "desc": "刘邦响应陈胜起义"}
|
||||
]
|
||||
};
|
||||
|
||||
// 颜色映射
|
||||
const colorMap = {
|
||||
"person": "#e6550d",
|
||||
"organization": "#756bb1",
|
||||
"event": "#9ecae1"
|
||||
};
|
||||
|
||||
const linkColorMap = {
|
||||
"political": "#ff7f0e",
|
||||
"military": "#1f77b4",
|
||||
"personal": "#2ca02c"
|
||||
};
|
||||
|
||||
// 创建SVG画布
|
||||
const width = document.getElementById('graph-container').clientWidth;
|
||||
const height = window.innerHeight;
|
||||
|
||||
const svg = d3.select("#graph-container")
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
// 创建力导向布局
|
||||
const simulation = d3.forceSimulation(graphData.nodes)
|
||||
.force("link", d3.forceLink(graphData.links).id(d => d.id).distance(100))
|
||||
.force("charge", d3.forceManyBody().strength(-500))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||
.force("collision", d3.forceCollide().radius(d => Math.sqrt(d.rank) * 10 + 10));
|
||||
|
||||
// 绘制连线
|
||||
const link = svg.append("g")
|
||||
.selectAll("line")
|
||||
.data(graphData.links)
|
||||
.enter().append("line")
|
||||
.attr("class", "link")
|
||||
.attr("stroke", d => linkColorMap[d.type])
|
||||
.attr("stroke-width", 2);
|
||||
|
||||
// 创建节点组
|
||||
const node = svg.append("g")
|
||||
.selectAll("g")
|
||||
.data(graphData.nodes)
|
||||
.enter().append("g")
|
||||
.call(d3.drag()
|
||||
.on("start", dragstarted)
|
||||
.on("drag", dragged)
|
||||
.on("end", dragended))
|
||||
.on("click", nodeClicked)
|
||||
.on("mouseover", showTooltip)
|
||||
.on("mouseout", hideTooltip);
|
||||
|
||||
// 添加3D效果节点
|
||||
node.append("circle")
|
||||
.attr("r", d => Math.sqrt(d.rank) * 10 + 10)
|
||||
.attr("fill", d => colorMap[d.type])
|
||||
.attr("class", "node");
|
||||
|
||||
// 添加节点文字
|
||||
node.append("text")
|
||||
.attr("dy", 4)
|
||||
.text(d => d.id);
|
||||
|
||||
// 更新力导向布局
|
||||
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})`);
|
||||
});
|
||||
|
||||
// 拖拽函数
|
||||
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 nodeClicked(event, d) {
|
||||
// 移除之前选中的节点样式
|
||||
d3.selectAll(".node").classed("selected", false);
|
||||
|
||||
// 添加当前选中节点样式
|
||||
d3.select(event.currentTarget).select("circle").classed("selected", true);
|
||||
|
||||
// 更新右侧面板信息
|
||||
document.getElementById("selected-node").textContent = d.id;
|
||||
|
||||
let detailsHtml = `
|
||||
<p><strong>类型:</strong> ${d.type}</p>
|
||||
<p><strong>描述:</strong> ${d.desc}</p>
|
||||
<p><strong>重要性:</strong> ${d.rank}/5</p>
|
||||
`;
|
||||
document.getElementById("node-details").innerHTML = detailsHtml;
|
||||
|
||||
// 查找相关关系
|
||||
let relationsHtml = "";
|
||||
const relatedLinks = graphData.links.filter(link =>
|
||||
link.source.id === d.id || link.target.id === d.id
|
||||
);
|
||||
|
||||
relatedLinks.forEach(link => {
|
||||
const otherNode = link.source.id === d.id ? link.target : link.source;
|
||||
relationsHtml += `
|
||||
<div class="relation-item">
|
||||
<p><strong>与 ${otherNode} 的关系:</strong> ${link.desc}</p>
|
||||
<p><small>关系类型: ${link.type}</small></p>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById("node-relations").innerHTML = relationsHtml || "<p>无相关关系</p>";
|
||||
}
|
||||
|
||||
// 工具提示函数
|
||||
function showTooltip(event, d) {
|
||||
const tooltip = d3.select("#tooltip");
|
||||
tooltip
|
||||
.style("left", (event.pageX + 10) + "px")
|
||||
.style("top", (event.pageY - 10) + "px")
|
||||
.style("display", "block")
|
||||
.html(`
|
||||
<strong>${d.id}</strong><br/>
|
||||
${d.desc}
|
||||
`);
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
d3.select("#tooltip").style("display", "none");
|
||||
}
|
||||
|
||||
// 布局切换功能
|
||||
document.getElementById("force-layout").addEventListener("click", () => {
|
||||
simulation.force("center", d3.forceCenter(width / 2, height / 2))
|
||||
.force("charge", d3.forceManyBody().strength(-500))
|
||||
.alpha(1).restart();
|
||||
});
|
||||
|
||||
document.getElementById("radial-layout").addEventListener("click", () => {
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const radius = Math.min(width, height) / 3;
|
||||
|
||||
graphData.nodes.forEach((node, i) => {
|
||||
const angle = (i * 2 * Math.PI) / graphData.nodes.length;
|
||||
node.fx = centerX + radius * Math.cos(angle);
|
||||
node.fy = centerY + radius * Math.sin(angle);
|
||||
});
|
||||
|
||||
simulation.alpha(1).restart();
|
||||
});
|
||||
|
||||
document.getElementById("circular-layout").addEventListener("click", () => {
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const radius = Math.min(width, height) / 3;
|
||||
const rings = Math.ceil(Math.sqrt(graphData.nodes.length));
|
||||
|
||||
graphData.nodes.forEach((node, i) => {
|
||||
const ring = Math.floor(i / (graphData.nodes.length / rings)) + 1;
|
||||
const angle = (i * 2 * Math.PI) / (graphData.nodes.length / rings);
|
||||
node.fx = centerX + (radius * ring / rings) * Math.cos(angle);
|
||||
node.fy = centerY + (radius * ring / rings) * Math.sin(angle);
|
||||
});
|
||||
|
||||
simulation.alpha(1).restart();
|
||||
});
|
||||
|
||||
document.getElementById("grid-layout").addEventListener("click", () => {
|
||||
const cols = Math.ceil(Math.sqrt(graphData.nodes.length));
|
||||
const cellWidth = width / (cols + 1);
|
||||
const cellHeight = height / (Math.ceil(graphData.nodes.length / cols) + 1);
|
||||
|
||||
graphData.nodes.forEach((node, i) => {
|
||||
const row = Math.floor(i / cols);
|
||||
const col = i % cols;
|
||||
node.fx = (col + 1) * cellWidth;
|
||||
node.fy = (row + 1) * cellHeight;
|
||||
});
|
||||
|
||||
simulation.alpha(1).restart();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in new issue