<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/force-graph"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="controls">
<h2>Dạng Hình Thái</h2>
<div class="btn-group">
<button onclick="changeMode('centralized')" id="btn-centralized">1. Tập trung (Centralized)</button>
<button onclick="changeMode('decentralized')" id="btn-decentralized">2. Phi tập trung (Decentralized)</button>
<button onclick="changeMode('distributed')" id="btn-distributed" class="active">3. Phân tán (Distributed)</button>
</div>
<div id="info">Di chuột vào các "nút tư duy" để xem mối quan hệ. Kéo thả để thay đổi cấu trúc không gian.</div>
</div>
<div id="graph"></div>
<script src="script.js"></script>
</body>
</html>
body {
margin: 0;
background-color: #050505;
color: white;
font-family: 'Segoe UI', sans-serif; overflow: hidden;
}
#controls {
position: absolute; top: 20px; left: 20px; z-index: 10;
background: rgba(20, 20, 20, 0.85); padding: 20px; border-radius: 12px;
border: 1px solid #333; backdrop-filter: blur(5px);
}
h2 {
margin-top: 0;
font-size: 18px;
color: #00d4ff;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-group {
display: flex;
flex-direction: column;
gap: 10px;
}
button {
background: #222;
border: 1px solid #444;
color: #ccc;
padding: 10px;
cursor: pointer;
border-radius: 5px;
transition: 0.3s;
text-align: left;
}
button:hover {
background: #00d4ff;
color: #000;
border-color: #00d4ff;
}
button.active {
background: #00d4ff;
color: #000;
font-weight: bold;
}
#info {
margin-top: 15px;
font-size: 13px;
line-height: 1.4;
color: #888;
max-width: 250px;
}
// Dữ liệu mẫu mô phỏng các ghi chú (Nodes) và liên kết (Links)
const initData = {
nodes: [
{id: 'Root', name: 'TƯ DUY CỐT LÕI', group: 0, val: 20},
// Nhóm AI
{id: 'AI', name: 'Trí tuệ nhân tạo', group: 1, val: 15},
{id: 'ML', name: 'Machine Learning', group: 1, val: 10},
{id: 'LLM', name: 'Mô hình ngôn ngữ', group: 1, val: 8},
// Nhóm Nghệ thuật
{id: 'Art', name: 'Nghệ thuật thị giác', group: 2, val: 15},
{id: 'Color', name: 'Lý thuyết màu sắc', group: 2, val: 8},
{id: 'Design', name: 'Thiết kế hệ thống', group: 2, val: 10},
// Nhóm Triết học
{id: 'Phil', name: 'Triết học hiện đại', group: 3, val: 15},
{id: 'Logic', name: 'Logic học', group: 3, val: 8}
],
links: [
{source: 'AI', target: 'ML', label: 'bao gồm'},
{source: 'ML', target: 'LLM', label: 'phát triển thành'},
{source: 'Art', target: 'Color', label: 'sử dụng'},
{source: 'Design', target: 'Art', label: 'áp dụng'},
{source: 'AI', target: 'Design', label: 'tối ưu hóa'},
{source: 'Logic', target: 'AI', label: 'nền tảng'},
{source: 'Phil', target: 'Logic', label: 'nguồn gốc'},
{source: 'LLM', target: 'Phil', label: 'mô phỏng'}
]
};
const Graph = ForceGraph()(document.getElementById('graph'))
.graphData(initData)
.nodeId('id')
.nodeLabel('name')
.nodeAutoColorBy('group')
.nodeCanvasObject((node, ctx, globalScale) => {
const label = node.name;
const fontSize = 14/globalScale;
ctx.font = `${fontSize}px Sans-Serif`;
const textWidth = ctx.measureText(label).width;
const bckgDimensions = [textWidth, fontSize].map(v => v + fontSize * 0.2);
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2, ...bckgDimensions);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = node.color;
ctx.fillText(label, node.x, node.y);
})
.linkColor(() => '#444')
.linkDirectionalParticles(2)
.linkDirectionalParticleSpeed(d => 0.005)
.linkCanvasObjectMode(() => 'after')
.linkCanvasObject((link, ctx) => {
const MAX_FONT_SIZE = 4;
const LABEL_NODE_MARGIN = Graph.nodeRelSize() * 1.5;
const start = link.source;
const end = link.target;
if (typeof start !== 'object' || typeof end !== 'object') return;
const relLink = { x: end.x - start.x, y: end.y - start.y };
const maxTextLength = Math.sqrt(Math.pow(relLink.x, 2) + Math.pow(relLink.y, 2)) - LABEL_NODE_MARGIN * 2;
let fontSize = Math.min(MAX_FONT_SIZE, maxTextLength / link.label.length);
ctx.font = `${fontSize}px Sans-Serif`;
const textWidth = ctx.measureText(link.label).width;
const centerPos = { x: start.x + relLink.x / 2, y: start.y + relLink.y / 2 };
const angle = Math.atan2(relLink.y, relLink.x);
ctx.save();
ctx.translate(centerPos.x, centerPos.y);
ctx.rotate(angle);
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillText(link.label, 0, -2);
ctx.restore();
});
function changeMode(mode) {
// Cập nhật giao diện nút
document.querySelectorAll('button').forEach(btn => btn.classList.remove('active'));
document.getElementById(`btn-${mode}`).classList.add('active');
let newData = JSON.parse(JSON.stringify(initData));
if (mode === 'centralized') {
// Mọi nút kết nối về Root
newData.links = newData.nodes
.filter(n => n.id !== 'Root')
.map(n => ({ source: 'Root', target: n.id, label: 'liên kết' }));
} else if (mode === 'decentralized') {
// Kết nối theo nhóm (cluster)
newData.links = [
{source: 'Root', target: 'AI', label: 'nhánh 1'},
{source: 'Root', target: 'Art', label: 'nhánh 2'},
{source: 'Root', target: 'Phil', label: 'nhánh 3'},
{source: 'AI', target: 'ML', label: ''}, {source: 'ML', target: 'LLM', label: ''},
{source: 'Art', target: 'Color', label: '' }, { source: 'Art', target: 'Design', label: '' },
{source: 'Phil', target: 'Logic', label: '' }
];
}
// Mode Distributed giữ nguyên data ban đầu với các liên kết chéo
Graph.graphData(newData);
}