main
HuangHai 1 week ago
commit c27312ff0d

@ -0,0 +1,502 @@
<!DOCTYPE html>
<html lang="zh">
<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;
overflow: hidden;
background: linear-gradient(135deg, #1a237e, #0d47a1);
font-family: "Microsoft YaHei", sans-serif;
}
#title {
position: absolute;
top: 20px;
left: 0;
right: 0;
text-align: center;
color: white;
font-size: 32px;
font-weight: bold;
text-shadow: 0 0 10px rgba(255,255,255,0.5);
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.3), rgba(255,255,255,0));
padding: 10px 0;
}
#graph-container {
position: absolute;
top: 80px;
left: 0;
right: 300px;
bottom: 0;
}
#info-panel {
position: absolute;
top: 80px;
right: 0;
width: 300px;
bottom: 0;
background: rgba(255,255,255,0.9);
padding: 15px;
overflow-y: auto;
box-shadow: -2px 0 10px rgba(0,0,0,0.2);
}
#legend {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(255,255,255,0.8);
padding: 10px;
border-radius: 5px;
}
.legend-item {
display: flex;
align-items: center;
margin: 5px 0;
}
.legend-color {
width: 20px;
height: 20px;
margin-right: 10px;
border-radius: 50%;
}
.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: 12px;
font-weight: bold;
pointer-events: none;
}
.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;
opacity: 0;
transition: opacity 0.3s;
}
#layout-controls {
position: absolute;
top: 20px;
left: 20px;
z-index: 100;
}
button {
background: rgba(255,255,255,0.2);
border: 1px solid white;
color: white;
padding: 5px 10px;
margin-right: 5px;
cursor: pointer;
border-radius: 3px;
}
button:hover {
background: rgba(255,255,255,0.4);
}
</style>
</head>
<body>
<div id="title">秦汉时期主要人物关系图</div>
<div id="graph-container"></div>
<div id="info-panel">
<h3 id="selected-name">点击节点查看详情</h3>
<div id="selected-desc"></div>
<h4>关系列表</h4>
<ul id="relations-list"></ul>
</div>
<div id="legend">
<div class="legend-item"><div class="legend-color" style="background: #ff7f0e;"></div><span>敌对关系</span></div>
<div class="legend-item"><div class="legend-color" style="background: #1f77b4;"></div><span>合作关系</span></div>
<div class="legend-item"><div class="legend-color" style="background: #2ca02c;"></div><span>历史影响</span></div>
<div class="legend-item"><div class="legend-color" style="background: #9467bd;"></div><span>权力继承</span></div>
</div>
<div id="layout-controls">
<button id="force-layout">力导向布局</button>
<button id="radial-layout">辐射状布局</button>
<button id="circular-layout">环形布局</button>
<button id="grid-layout">网格布局</button>
</div>
<div class="tooltip"></div>
<script>
const data = {
"nodes": [
{
"id": "刘邦",
"group": 1,
"desc": "汉高祖刘邦,汉朝开国皇帝。从亭长起家,击败项羽统一天下。",
"importance": 5
},
{
"id": "秦始皇",
"group": 2,
"desc": "中国历史上第一个皇帝,统一六国,建立秦朝。",
"importance": 5
},
{
"id": "项羽",
"group": 3,
"desc": "西楚霸王,刘邦的主要对手,最终在垓下之战败亡。",
"importance": 4
},
{
"id": "张良",
"group": 1,
"desc": "刘邦的重要谋士,汉初三杰之一,善于运筹帷幄。",
"importance": 4
},
{
"id": "萧何",
"group": 1,
"desc": "刘邦的重要助手,汉初三杰之一,负责后勤和行政管理。",
"importance": 4
},
{
"id": "韩信",
"group": 1,
"desc": "刘邦的大将,汉初三杰之一,军事才能出众,后被贬为淮阴侯。",
"importance": 4
},
{
"id": "李斯",
"group": 2,
"desc": "秦始皇的丞相,协助统一六国,后被赵高陷害。",
"importance": 3
},
{
"id": "吕不韦",
"group": 2,
"desc": "秦始皇的仲父,商人出身,曾掌握秦国大权。",
"importance": 3
},
{
"id": "扶苏",
"group": 2,
"desc": "秦始皇的长子,因劝谏被贬边疆,后被胡亥赐死。",
"importance": 3
},
{
"id": "胡亥",
"group": 2,
"desc": "秦二世,秦始皇幼子,昏庸无能,加速秦朝灭亡。",
"importance": 3
},
{
"id": "吕后",
"group": 1,
"desc": "刘邦的皇后,在刘邦死后掌握朝政大权。",
"importance": 3
},
{
"id": "陈胜",
"group": 3,
"desc": "率先起义反秦,建立张楚政权,后被杀害。",
"importance": 2
},
{
"id": "吴广",
"group": 3,
"desc": "与陈胜一起起义反秦,后被杀害。",
"importance": 2
}
],
"links": [
{
"source": "刘邦",
"target": "秦始皇",
"value": 3,
"desc": "刘邦的命运与秦始皇关于天子气的预言相关联",
"type": "历史影响"
},
{
"source": "刘邦",
"target": "项羽",
"value": 5,
"desc": "楚汉争霸的主要对手",
"type": "敌对关系"
},
{
"source": "刘邦",
"target": "张良",
"value": 4,
"desc": "刘邦的重要谋士",
"type": "合作关系"
},
{
"source": "刘邦",
"target": "萧何",
"value": 4,
"desc": "刘邦的重要助手",
"type": "合作关系"
},
{
"source": "刘邦",
"target": "韩信",
"value": 4,
"desc": "刘邦的大将,后关系恶化",
"type": "合作关系"
},
{
"source": "刘邦",
"target": "吕后",
"value": 3,
"desc": "夫妻关系,后期政治对手",
"type": "权力继承"
},
{
"source": "秦始皇",
"target": "李斯",
"value": 4,
"desc": "君臣关系,协助统一六国",
"type": "合作关系"
},
{
"source": "秦始皇",
"target": "吕不韦",
"value": 3,
"desc": "疑似父子关系,后被废黜",
"type": "权力继承"
},
{
"source": "秦始皇",
"target": "扶苏",
"value": 3,
"desc": "父子关系,因政见不合被贬",
"type": "权力继承"
},
{
"source": "秦始皇",
"target": "胡亥",
"value": 3,
"desc": "父子关系,胡亥篡位",
"type": "权力继承"
},
{
"source": "项羽",
"target": "秦始皇",
"value": 2,
"desc": "项羽目睹秦始皇出巡后提出取代他的言论",
"type": "历史影响"
},
{
"source": "张良",
"target": "秦始皇",
"value": 2,
"desc": "张良曾试图刺杀秦始皇",
"type": "敌对关系"
},
{
"source": "陈胜",
"target": "吴广",
"value": 3,
"desc": "共同起义反秦",
"type": "合作关系"
},
{
"source": "陈胜",
"target": "秦始皇",
"value": 2,
"desc": "率先起义反抗秦朝统治",
"type": "敌对关系"
}
]
};
const width = window.innerWidth - 300;
const height = window.innerHeight - 80;
const svg = d3.select("#graph-container")
.append("svg")
.attr("width", width)
.attr("height", height);
const tooltip = d3.select(".tooltip");
const color = d3.scaleOrdinal()
.domain(["敌对关系", "合作关系", "历史影响", "权力继承"])
.range(["#ff7f0e", "#1f77b4", "#2ca02c", "#9467bd"]);
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(30));
const link = svg.append("g")
.attr("class", "links")
.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")
.attr("class", "nodes")
.selectAll("g")
.data(data.nodes)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("class", "node")
.attr("r", d => d.importance * 6)
.attr("fill", d => {
if (d.group === 1) return "#4caf50";
if (d.group === 2) return "#f44336";
return "#2196f3";
})
.on("mouseover", function(event, d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(`<strong>${d.id}</strong><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) {
updateInfoPanel(d);
});
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 updateInfoPanel(d) {
d3.select("#selected-name").text(d.id);
d3.select("#selected-desc").html(`<p>${d.desc}</p><p>重要性: ${d.importance}/5</p>`);
const relations = data.links.filter(link =>
link.source.id === d.id || link.target.id === d.id
);
const relationsList = d3.select("#relations-list").html("");
relations.forEach(rel => {
const otherNode = rel.source.id === d.id ? rel.target : rel.source;
const direction = rel.source.id === d.id ? "→" : "←";
relationsList.append("li")
.html(`<strong>${direction} ${otherNode}</strong>: ${rel.desc} (${rel.type})`)
.style("color", color(rel.type));
});
}
// 布局控制
d3.select("#force-layout").on("click", function() {
simulation.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))
.alphaTarget(0.3)
.restart();
});
d3.select("#radial-layout").on("click", function() {
const root = data.nodes.find(d => d.id === "秦始皇");
simulation.force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("x", d3.forceX().x(d => (d === root ? width/2 : width/2 + Math.cos((d.group-1)*Math.PI*2/3) * 200)))
.force("y", d3.forceY().y(d => (d === root ? height/2 : height/2 + Math.sin((d.group-1)*Math.PI*2/3) * 200)))
.alphaTarget(0.3)
.restart();
});
d3.select("#circular-layout").on("click", function() {
simulation.force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("x", d3.forceX().x(width/2).strength(0.05))
.force("y", d3.forceY().y(height/2).strength(0.05))
.force("radial", d3.forceRadial(200, width/2, height/2).strength(0.1))
.alphaTarget(0.3)
.restart();
});
d3.select("#grid-layout").on("click", function() {
const cols = Math.ceil(Math.sqrt(data.nodes.length));
const spacing = width / (cols + 1);
simulation.force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("x", d3.forceX().x((d, i) => (i % cols + 1) * spacing).strength(1))
.force("y", d3.forceY().y((d, i) => Math.floor(i / cols + 1) * spacing).strength(1))
.alphaTarget(0.3)
.restart();
});
window.addEventListener("resize", function() {
const newWidth = window.innerWidth - 300;
const newHeight = window.innerHeight - 80;
svg.attr("width", newWidth).attr("height", newHeight);
simulation.force("center", d3.forceCenter(newWidth / 2, newHeight / 2))
.restart();
});
</script>
</body>
</html>
Loading…
Cancel
Save