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…
Reference in new issue