| Cỡ chữ:   
<!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); }